När du skriver Python-applikationer är cachning viktigt. Att använda en cache för att undvika att beräkna data eller komma åt en långsam databas kan ge dig en stor prestandaökning.
Python erbjuder inbyggda möjligheter för cachelagring, från en enkel ordbok till en mer komplett datastruktur som functools.lru_cache
. Den senare kan cache vilket objekt som helst med hjälp av en algoritm som har använts minst nyligen för att begränsa cachestorleken.
Dessa datastrukturer är dock per definition lokala till din Python-process. När flera kopior av din applikation körs över en stor plattform, tillåter användning av en datastruktur i minnet inte delning av det cachade innehållet. Detta kan vara ett problem för storskaliga och distribuerade applikationer.
Därför, när ett system distribueras över ett nätverk, behöver det också en cache som är distribuerad över ett nätverk. Nuförtiden finns det gott om nätverksservrar som erbjuder cachning - vi har redan tagit upp hur man använder Redis för cachning med Django.
Som du kommer att se i den här handledningen är memcached ett annat bra alternativ för distribuerad cachning. Efter en snabb introduktion till grundläggande memcachad användning kommer du att lära dig om avancerade mönster som "cache and set" och att använda reservcacher för att undvika problem med prestanda i kall cache.
Installerar memcached
Memcachad är tillgänglig för många plattformar:
- Om du kör Linux , du kan installera det med
apt-get install memcached
elleryum install memcached
. Detta kommer att installera memcached från ett förbyggt paket men du kan också bygga memcached från källan, som förklaras här. - För macOS , att använda Homebrew är det enklaste alternativet. Kör bara
brew install memcached
efter att du har installerat Homebrew-pakethanteraren. - På Windows , du skulle behöva kompilera memcached själv eller hitta förkompilerade binärer.
När den är installerad, memcachad kan helt enkelt startas genom att anropa memcached
kommando:
$ memcached
Innan du kan interagera med memcached från Python-land måste du installera en memcachad klient bibliotek. Du kommer att se hur du gör detta i nästa avsnitt, tillsammans med några grundläggande cacheåtkomstoperationer.
Lagra och hämta cachade värden med Python
Om du aldrig använt memcached , det är ganska lätt att förstå. Det ger i princip en gigantisk nätverkstillgänglig ordbok. Denna ordbok har några egenskaper som skiljer sig från en klassisk Python-ordbok, främst:
- Nycklar och värden måste vara byte
- Nycklar och värden raderas automatiskt efter en utgångstid
Därför är de två grundläggande operationerna för att interagera med memcached är set
och get
. Som du kanske har gissat används de för att tilldela ett värde till en nyckel respektive för att få ett värde från en nyckel.
Mitt föredragna Python-bibliotek för att interagera med memcached är pymemcache
– Jag rekommenderar att du använder den. Du kan helt enkelt installera den med pip:
$ pip install pymemcache
Följande kod visar hur du kan ansluta till memcached och använd den som en nätverksdistribuerad cache i dina Python-applikationer:
>>> from pymemcache.client import base
# Don't forget to run `memcached' before running this next line:
>>> client = base.Client(('localhost', 11211))
# Once the client is instantiated, you can access the cache:
>>> client.set('some_key', 'some value')
# Retrieve previously set data again:
>>> client.get('some_key')
'some value'
memcachad nätverksprotokoll är verkligen enkelt och dess implementering extremt snabbt, vilket gör det användbart att lagra data som annars skulle vara långsamma att hämta från den kanoniska datakällan eller att beräkna igen:
Även om det är okomplicerat, tillåter det här exemplet att lagra nyckel-/värde-tupler över nätverket och komma åt dem genom flera, distribuerade, löpande kopior av din applikation. Detta är förenklat, men ändå kraftfullt. Och det är ett bra första steg mot att optimera din applikation.
Cachad data som löper ut automatiskt
När du lagrar data i memcached , du kan ställa in en utgångstid – ett maximalt antal sekunder för memcachad att hålla nyckeln och värdet runt. Efter den förseningen, memcachad tar automatiskt bort nyckeln från dess cache.
Vad ska du ställa in denna cachetid på? Det finns inget magiskt tal för denna fördröjning, och det beror helt på vilken typ av data och applikation du arbetar med. Det kan ta några sekunder eller några timmar.
Invalidering av cache , som definierar när cachen ska tas bort eftersom den inte är synkroniserad med aktuell data, är också något som din applikation måste hantera. Speciellt om data som är för gammal eller inaktuell presenteras är att undvika.
Även här finns det inget magiskt recept; det beror på vilken typ av applikation du bygger. Det finns dock flera yttre fall som bör hanteras – som vi ännu inte har täckt i exemplet ovan.
En cachningsserver kan inte växa i det oändliga – minnet är en ändlig resurs. Därför kommer nycklar att spolas ut av cachingservern så fort den behöver mer utrymme för att lagra andra saker.
Vissa nycklar kan också förfalla eftersom de nått sin utgångstid (kallas även ibland "time-to-live" eller TTL.) I dessa fall går data förlorad och den kanoniska datakällan måste frågas igen.
Det här låter mer komplicerat än vad det egentligen är. Du kan i allmänhet arbeta med följande mönster när du arbetar med memcached i Python:
from pymemcache.client import base
def do_some_query():
# Replace with actual querying code to a database,
# a remote REST API, etc.
return 42
# Don't forget to run `memcached' before running this code
client = base.Client(('localhost', 11211))
result = client.get('some_key')
if result is None:
# The cache is empty, need to get the value
# from the canonical source:
result = do_some_query()
# Cache the result for next time:
client.set('some_key', result)
# Whether we needed to update the cache or not,
# at this point you can work with the data
# stored in the `result` variable:
print(result)
Obs! Hantering av saknade nycklar är obligatorisk på grund av normala utspolningsoperationer. Det är också obligatoriskt att hantera scenariot med kall cache, d.v.s. när memcachad har precis påbörjats. I så fall kommer cachen att vara helt tom och cachen måste fyllas på helt igen, en begäran i taget.
Detta betyder att du bör se all cachad data som tillfällig. Och du ska aldrig förvänta dig att cachen ska innehålla ett värde som du tidigare skrivit till den.
Värmar upp en kall cache
Vissa av de kalla cache-scenarierna kan inte förhindras, till exempel en memcachad krascha. Men vissa kan, till exempel migrera till en ny memcachad server.
När det är möjligt att förutsäga att ett kallt cache-scenario kommer att inträffa är det bättre att undvika det. En cache som behöver fyllas på betyder att helt plötsligt kommer den kanoniska lagringen av cachelagrade data att drabbas massivt av alla cacheanvändare som saknar cachedata (även känt som det åskande flockproblemet.)
pymemcache tillhandahåller en klass som heter FallbackClient
som hjälper till att implementera detta scenario som visas här:
from pymemcache.client import base
from pymemcache import fallback
def do_some_query():
# Replace with actual querying code to a database,
# a remote REST API, etc.
return 42
# Set `ignore_exc=True` so it is possible to shut down
# the old cache before removing its usage from
# the program, if ever necessary.
old_cache = base.Client(('localhost', 11211), ignore_exc=True)
new_cache = base.Client(('localhost', 11212))
client = fallback.FallbackClient((new_cache, old_cache))
result = client.get('some_key')
if result is None:
# The cache is empty, need to get the value
# from the canonical source:
result = do_some_query()
# Cache the result for next time:
client.set('some_key', result)
print(result)
FallbackClient
frågor som den gamla cachen skickade till dess konstruktor, med respekt för ordningen. I det här fallet kommer den nya cacheservern alltid att frågas först, och i händelse av en cachemiss kommer den gamla att efterfrågas – vilket undviker en möjlig återresa till den primära datakällan.
Om någon nyckel är inställd kommer den bara att ställas in på den nya cachen. Efter en tid kan den gamla cachen tas ur drift och FallbackClient
kan ersättas riktad med new_cache
klient.
Kontrollera och ställ in
När du kommunicerar med en fjärrcache kommer det vanliga samtidighetsproblemet tillbaka:det kan finnas flera klienter som försöker komma åt samma nyckel samtidigt. memcachad ger en kontroll och ställ in operation, förkortad till CAS , vilket hjälper till att lösa detta problem.
Det enklaste exemplet är en applikation som vill räkna antalet användare den har. Varje gång en besökare ansluter ökas en räknare med 1. Med memcached , en enkel implementering skulle vara:
def on_visit(client):
result = client.get('visitors')
if result is None:
result = 1
else:
result += 1
client.set('visitors', result)
Men vad händer om två instanser av programmet försöker uppdatera denna räknare samtidigt?
Det första anropet client.get('visitors')
kommer att returnera samma antal besökare för dem båda, låt oss säga att det är 42. Sedan lägger båda till 1, beräknar 43 och ställer in antalet besökare till 43. Det siffran är fel, och resultatet bör vara 44, dvs. 42 + 1 + 1.
För att lösa detta samtidighetsproblem, CAS-driften för memcached är praktiskt. Följande kodavsnitt implementerar en korrekt lösning:
def on_visit(client):
while True:
result, cas = client.gets('visitors')
if result is None:
result = 1
else:
result += 1
if client.cas('visitors', result, cas):
break
gets
metod returnerar värdet, precis som get
metod, men den returnerar också ett CAS-värde .
Vad som finns i detta värde är inte relevant, men det används för nästa metod cas
ringa upp. Denna metod är likvärdig med set
operation, förutom att den misslyckas om värdet har ändrats sedan gets
drift. Vid framgång bryts slingan. Annars startas operationen om från början.
I scenariot där två instanser av applikationen försöker uppdatera räknaren samtidigt, lyckas bara en flytta räknaren från 42 till 43. Den andra instansen får en False
värde som returneras av client.cas
ring och måste försöka slingan igen. Den kommer att hämta 43 som värde den här gången, öka den till 44 och dess cas
samtalet kommer att lyckas, vilket löser vårt problem.
Att öka en räknare är intressant som ett exempel för att förklara hur CAS fungerar eftersom det är förenklat. Dock memcachad tillhandahåller även incr
och decr
metoder för att öka eller minska ett heltal i en enda begäran, istället för att göra flera gets
/cas
samtal. I verkliga applikationer gets
och cas
används för mer komplexa datatyper eller operationer
De flesta fjärrcacheserver och datalager tillhandahåller en sådan mekanism för att förhindra samtidighetsproblem. Det är viktigt att vara medveten om dessa fall för att kunna använda deras funktioner på rätt sätt.
Utöver cachelagring
De enkla teknikerna som illustreras i den här artikeln visade hur lätt det är att utnyttja memcached för att påskynda prestandan för din Python-applikation.
Bara genom att använda de två grundläggande "set" och "get" operationerna kan du ofta påskynda datahämtning eller undvika att beräkna resultat om och om igen. Med memcached kan du dela cachen över ett stort antal distribuerade noder.
Andra, mer avancerade mönster du såg i den här handledningen, som Kontrollera och ställ in (CAS) operation gör att du kan uppdatera data som lagras i cachen samtidigt över flera Python-trådar eller processer samtidigt som du undviker datakorruption.
Om du är intresserad av att lära dig mer om avancerade tekniker för att skriva snabbare och mer skalbara Python-applikationer, kolla in Scaling Python. Den täcker många avancerade ämnen som nätverksdistribution, kösystem, distribuerad hashning och kodprofilering.