sql >> Database >  >> NoSQL >> Memcached

Python + Memcached:efficiënte caching in gedistribueerde applicaties

Bij het schrijven van Python-applicaties is caching belangrijk. Het gebruik van een cache om te voorkomen dat gegevens opnieuw worden berekend of om toegang te krijgen tot een trage database, kan u een geweldige prestatieverbetering opleveren.

Python biedt ingebouwde mogelijkheden voor caching, van een eenvoudig woordenboek tot een meer complete datastructuur zoals functools.lru_cache . De laatste kan elk item cachen met behulp van een minst recent gebruikt algoritme om de cachegrootte te beperken.

Die datastructuren zijn echter per definitie lokaal aan uw Python-proces. Wanneer meerdere exemplaren van uw toepassing over een groot platform worden uitgevoerd, staat het gebruik van een gegevensstructuur in het geheugen het delen van de inhoud in de cache niet toe. Dit kan een probleem zijn voor grootschalige en gedistribueerde toepassingen.

Daarom, wanneer een systeem over een netwerk wordt gedistribueerd, heeft het ook een cache nodig die over een netwerk wordt gedistribueerd. Tegenwoordig zijn er tal van netwerkservers die caching-mogelijkheden bieden - we hebben al besproken hoe Redis te gebruiken voor caching met Django.

Zoals je in deze tutorial zult zien, is memcached een andere geweldige optie voor gedistribueerde caching. Na een korte introductie tot het basisgebruik van geheugencaches, leer je over geavanceerde patronen zoals "cache en set" en het gebruik van fallback-caches om prestatieproblemen met koude cache te voorkomen.


Memcached installeren

Memcached is beschikbaar voor veel platforms:

  • Als je Linux . gebruikt , kunt u het installeren met apt-get install memcached of yum install memcached . Dit zal memcached installeren vanuit een vooraf gebouwd pakket, maar je kunt ook memcached bouwen vanaf de broncode, zoals hier wordt uitgelegd.
  • Voor macOS , is het gebruik van Homebrew de eenvoudigste optie. Voer gewoon brew install memcached uit nadat je de Homebrew-pakketbeheerder hebt geïnstalleerd.
  • Op Windows , je zou memcached zelf moeten compileren of vooraf gecompileerde binaire bestanden moeten vinden.

Eenmaal geïnstalleerd, memcached kan eenvoudig worden gestart door de memcached . aan te roepen commando:

$ memcached

Voordat je kunt communiceren met memcached van Python-land, moet je een memcached client installeren bibliotheek. U zult in het volgende gedeelte zien hoe u dit kunt doen, samen met enkele basisbewerkingen voor toegang tot de cache.



Waarden in cache opslaan en ophalen met Python

Als je memcached nooit hebt gebruikt , het is vrij eenvoudig te begrijpen. Het biedt in feite een gigantisch netwerk-beschikbaar woordenboek. Dit woordenboek heeft een paar eigenschappen die verschillen van een klassiek Python-woordenboek, voornamelijk:

  • Sleutels en waarden moeten bytes zijn
  • Sleutels en waarden worden automatisch verwijderd na verloop van tijd

Daarom zijn de twee basishandelingen voor interactie met memcached zijn set en get . Zoals je misschien al geraden had, worden ze respectievelijk gebruikt om een ​​waarde aan een sleutel toe te kennen of om een ​​waarde uit een sleutel te halen.

Mijn favoriete Python-bibliotheek voor interactie met memcached is pymemcache —Ik raad aan om het te gebruiken. Je kunt het eenvoudig installeren met pip:

$ pip install pymemcache

De volgende code laat zien hoe u verbinding kunt maken met memcached en gebruik het als een netwerk-gedistribueerde cache in uw Python-toepassingen:

>>> 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'

memcached netwerkprotocol is heel eenvoudig en de implementatie ervan extreem snel, wat het handig maakt om gegevens op te slaan die anders traag zouden zijn om op te halen uit de canonieke gegevensbron of om opnieuw te berekenen:

Hoewel eenvoudig genoeg, maakt dit voorbeeld het mogelijk om sleutel/waarde-tupels over het netwerk op te slaan en deze te openen via meerdere, gedistribueerde, actieve kopieën van uw toepassing. Dit is simplistisch, maar krachtig. En het is een geweldige eerste stap naar het optimaliseren van uw applicatie.



Automatisch verlopen gegevens in cache

Bij het opslaan van gegevens in memcached , kunt u een vervaltijd instellen:een maximum aantal seconden voor memcached om de sleutel en waarde rond te houden. Na die vertraging, memcached verwijdert de sleutel automatisch uit de cache.

Waar moet je deze cachetijd op instellen? Er is geen magisch getal voor deze vertraging en het hangt volledig af van het type gegevens en applicatie waarmee u werkt. Het kan een paar seconden zijn, of een paar uur.

Cache ongeldig maken , die bepaalt wanneer de cache moet worden verwijderd omdat deze niet synchroon loopt met de huidige gegevens, is ook iets dat uw toepassing moet afhandelen. Vooral als gegevens worden gepresenteerd die te oud of verouderd zijn te vermijden is.

Ook hier is er geen magisch recept; het hangt af van het type toepassing dat u aan het bouwen bent. Er zijn echter een aantal perifere gevallen die moeten worden afgehandeld, die we in het bovenstaande voorbeeld nog niet hebben behandeld.

Een caching-server kan niet oneindig groeien - geheugen is een eindige bron. Daarom worden sleutels door de caching-server weggespoeld zodra deze meer ruimte nodig heeft om andere dingen op te slaan.

Sommige sleutels kunnen ook verlopen zijn omdat ze hun vervaltijd hebben bereikt (ook wel de "time-to-live" of TTL genoemd). In die gevallen gaan de gegevens verloren en moet de canonieke gegevensbron opnieuw worden opgevraagd.

Dit klinkt ingewikkelder dan het in werkelijkheid is. Je kunt over het algemeen met het volgende patroon werken als je met memcached in Python werkt:

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)

Opmerking: Het verwerken van ontbrekende sleutels is verplicht vanwege de normale doorspoelhandelingen. Het is ook verplicht om het koude cache-scenario af te handelen, d.w.z. wanneer memcached is net gestart. In dat geval is de cache volledig leeg en moet de cache volledig opnieuw worden gevuld, één verzoek per keer.

Dit betekent dat u alle gegevens in de cache als kortstondig moet beschouwen. En je mag nooit verwachten dat de cache een waarde bevat die je er eerder naar hebt geschreven.



Een koude cache opwarmen

Sommige koude cache-scenario's kunnen niet worden voorkomen, bijvoorbeeld een memcached Botsing. Maar sommige kunnen, bijvoorbeeld migreren naar een nieuwe memcached server.

Wanneer het mogelijk is om te voorspellen dat er een cold-cache-scenario zal plaatsvinden, is het beter om dit te vermijden. Een cache die moet worden bijgevuld, betekent dat de canonieke opslag van de gegevens in de cache ineens enorm wordt getroffen door alle cachegebruikers die geen cachegegevens hebben (ook bekend als het donderende kuddeprobleem.)

pymemcache biedt een klasse met de naam FallbackClient dat helpt bij het implementeren van dit scenario, zoals hier wordt aangetoond:

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)

De FallbackClient query's die de oude cache aan zijn constructor heeft doorgegeven, met inachtneming van de volgorde. In dit geval wordt altijd eerst de nieuwe cacheserver opgevraagd en in het geval van een cachemisser wordt de oude opgevraagd, waardoor een mogelijke terugkeer naar de primaire gegevensbron wordt vermeden.

Als er een sleutel is ingesteld, wordt deze alleen ingesteld op de nieuwe cache. Na enige tijd kan de oude cache buiten gebruik worden gesteld en de FallbackClient kan worden vervangen door de new_cache klant.



Controleren en instellen

Bij communicatie met een externe cache komt het gebruikelijke gelijktijdigheidsprobleem terug:er kunnen meerdere clients tegelijkertijd toegang proberen te krijgen tot dezelfde sleutel. memcached biedt een controle en set operatie, afgekort tot CAS , wat helpt om dit probleem op te lossen.

Het eenvoudigste voorbeeld is een applicatie die het aantal gebruikers wil tellen. Elke keer dat een bezoeker verbinding maakt, wordt een teller met 1 verhoogd. Met behulp van memcached , zou een eenvoudige implementatie zijn:

def on_visit(client):
    result = client.get('visitors')
    if result is None:
        result = 1
    else:
        result += 1
    client.set('visitors', result)

Wat gebeurt er echter als twee instanties van de toepassing deze teller tegelijkertijd proberen bij te werken?

De eerste oproep client.get('visitors') zal hetzelfde aantal bezoekers teruggeven voor beiden, laten we zeggen dat het 42 is. Dan tellen beiden 1, berekenen 43, en stellen het aantal bezoekers in op 43. Dat aantal is onjuist, en het resultaat zou 44 moeten zijn, d.w.z. 42 + 1 + 1.

Om dit gelijktijdigheidsprobleem op te lossen, is de CAS-bewerking van memcached is handig. Het volgende fragment implementeert een correcte oplossing:

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

De gets methode retourneert de waarde, net als de get methode, maar het retourneert ook een CAS-waarde .

Wat in deze waarde staat is niet relevant, maar wordt gebruikt voor de volgende methode cas telefoongesprek. Deze methode is gelijk aan de set bewerking, behalve dat het mislukt als de waarde is gewijzigd sinds de gets operatie. Bij succes wordt de lus verbroken. Anders wordt de bewerking vanaf het begin hervat.

In het scenario waarin twee instanties van de toepassing de teller tegelijkertijd proberen bij te werken, slaagt er slechts één in om de teller van 42 naar 43 te verplaatsen. De tweede instantie krijgt een False waarde geretourneerd door de client.cas aanroepen en de lus opnieuw moeten proberen. Het zal deze keer 43 als waarde ophalen, het verhogen naar 44, en zijn cas oproep zal slagen, waardoor ons probleem wordt opgelost.

Het verhogen van een teller is interessant als voorbeeld om uit te leggen hoe CAS werkt omdat het simplistisch is. Echter, memcached biedt ook de incr en decr methoden om een ​​geheel getal in een enkel verzoek te verhogen of te verlagen, in plaats van meerdere gets te doen /cas belt. In echte toepassingen gets en cas worden gebruikt voor complexere gegevenstypes of bewerkingen

De meeste externe cachingservers en gegevensopslag bieden een dergelijk mechanisme om gelijktijdigheidsproblemen te voorkomen. Het is van cruciaal belang om op de hoogte te zijn van deze gevallen om goed gebruik te kunnen maken van hun functies.



Voorbij caching

De eenvoudige technieken die in dit artikel worden geïllustreerd, hebben je laten zien hoe gemakkelijk het is om gebruik te maken van memcached om de prestaties van uw Python-toepassing te versnellen.

Alleen al door de twee basisbewerkingen "set" en "get" te gebruiken, kunt u het ophalen van gegevens vaak versnellen of voorkomen dat u de resultaten steeds opnieuw moet berekenen. Met memcached kun je de cache delen over een groot aantal gedistribueerde nodes.

Andere, meer geavanceerde patronen die je in deze tutorial hebt gezien, zoals de Check And Set (CAS) bewerking stelt u in staat om gegevens die in de cache zijn opgeslagen gelijktijdig bij te werken over meerdere Python-threads of -processen, terwijl gegevenscorruptie wordt voorkomen.

Als je meer wilt weten over geavanceerde technieken om snellere en schaalbare Python-applicaties te schrijven, bekijk dan Scaling Python. Het behandelt veel geavanceerde onderwerpen zoals netwerkdistributie, wachtrijsystemen, gedistribueerde hashing en codeprofilering.



  1. Apache HBase I/O – HFile

  2. Mongoose -- Naam verzameling forceren

  3. hoe een sleutel van een kaart in REDIS laten verlopen?

  4. Een gids voor query's in Spring Data MongoDB