Patroon Cache-Aside

Cache voor Redis

Gegevens op verzoek in een cache laden vanuit een gegevensarchief. Dit kan de prestaties verbeteren en bevordert tevens het handhaven van de consistentie tussen gegevens die zijn opgeslagen in de cache en de gegevens in het onderliggende gegevensarchief.

Context en probleem

Toepassingen maken gebruik van een cache om herhaalde toegang tot in een gegevensarchief opgeslagen gegevens te vereenvoudigen. Het is echter niet praktisch te verwachten dat gegevens in de cache altijd volledig consistent zijn met de gegevens in het gegevensarchief. Voor toepassingen dient er een strategie te worden geïmplementeerd waardoor de gegevens in de cache zo actueel mogelijk zijn, maar ook situaties kunnen detecteren en afhandelen die optreden als de gegevens in de cache verouderen.

Oplossing

Veel commerciële cachesystemen bieden read-through-bewerkingen en write-through-/schrijfbewerkingen. In deze systemen worden de gegevens opgehaald door naar de cache te verwijzen. Als de gegevens zich niet in de cache bevinden, worden ze opgehaald uit het gegevensarchief en aan de cache toegevoegd. Wijzigingen die zijn aangebracht aan gegevens in de cache, worden ook automatisch teruggeschreven naar het gegevensarchief.

Voor caches die deze functionaliteit niet bieden, zijn de toepassingen die gebruikmaken van de cache verantwoordelijk voor het onderhouden van de cache.

Een toepassing kan de functionaliteit van read-through caching emuleren door de cache-aside-strategie te implementeren. Met deze strategie worden gegevens op verzoek in de cache geladen. In de afbeelding wordt het gebruik van het cache-aside-patroon geïllustreerd voor het laden van gegevens in de cache.

Het cache-aside-patroon gebruiken voor het laden van gegevens in de cache

Als informatie wordt bijgewerkt, kan de write-through-strategie worden gevolgd door de wijziging aan het gegevensarchief aan te brengen en door het overeenkomstige item in de cache ongeldig te maken.

Als het item vervolgens nodig is, dan worden bij gebruik van de cache-aside-strategie de bijgewerkte gegevens opgehaald uit het gegevensarchief en opnieuw aan de cache toegevoegd.

Problemen en overwegingen

Beschouw de volgende punten als u besluit hoe u dit patroon wilt implementeren:

Levensduur van gegevens in de cache. Voor veel caches is een verloopbeleid geïmplementeerd waardoor gegevens ongeldig worden gemaakt en uit de cache verwijderd als ze gedurende een bepaalde periode niet zijn opgevraagd. Wil de cache-aside-strategie effectief zijn, dan dient u ervoor te zorgen dat het verloopbeleid overeenkomt met het toegangspatroon voor toepassingen die van de gegevens gebruikmaken. Maak de verloopperiode niet te kort, omdat dit ervoor kan zorgen dat toepassingen voortdurend gegevens uit het gegevensarchief ophalen en aan de cache toevoegen. Maak de verloopperiode ook niet zo lang dat de kans bestaat dat de gegevens verouderen. Gegevens opslaan in de cache is het meest effectief bij relatief statische gegevens of gegevens die regelmatig worden gelezen.

Gegevens onbeschikbaar maken. De meeste caches hebben een beperkte grootte in vergelijking tot het gegevensarchief waar de gegevens uit afkomstig zijn en de gegevens in de cache worden zo nodig onbeschikbaar gemaakt. De meeste caches gebruiken een beleid waarbij de minst recent gebruikte items onbeschikbaar worden gemaakt. Dit kan echter worden aangepast. Configureer de globale verloopeigenschap en andere eigenschappen van de cache en de verloopeigenschap van elk item in de cache om ervoor te zorgen dat de cache rendabel is. Het is niet altijd het beste om een globaal beleid van onbeschikbaar maken toe te passen op elk item in de cache. Als het bijvoorbeeld erg kostbaar is om een item in de cache uit het gegevensarchief te halen, kan het voordelig zijn het item in de cache te houden ten koste van items die vaker worden geopend maar die minder kosten.

Cache voorbereiden. In veel oplossingen wordt de cache vooraf gevuld met de gegevens die een toepassing waarschijnlijk nodig heeft als onderdeel van het startproces. Het cache-aside-patroon kan ook hier nuttig zijn als een deel van die gegevens verloopt of onbeschikbaar is gemaakt.

Consistentie. Het implementeren van het cache-aside-patroon biedt geen garantie voor de consistentie tussen het gegevensarchief en de cache. Een item in het gegevensarchief kan op elk moment door een extern proces worden gewijzigd. Deze wijziging wordt mogelijk pas van kracht in de cache totdat het volgende item wordt geladen. In een systeem waarbij gegevens in gegevensarchieven worden gerepliceerd, kan dit een ernstig probleem worden als er regelmatig wordt gesynchroniseerd.

Opslaan in lokale cache (in-memory). Een cache kan lokaal zijn voor een instantie van een toepassing en in-memory worden opgeslagen. Cache-aside kan in een dergelijke omgeving handig zijn als een toepassing bij herhaling dezelfde gegevens ophaalt. Een lokale cache is echter privé, dus verschillende toepassingsinstanties kunnen elk een kopie van dezelfde gegevens in de cache hebben. De gegevens in de verschillende caches kunnen snel inconsistent worden ten opzichte van elkaar, dus het kan noodzakelijk zijn om gegevens in een privécache te laten verlopen en ze vaker te vernieuwen. In deze scenario's kunt u het gebruik van een gedeeld of gedistribueerd mechanisme voor opslaan in de cache overwegen.

Wanneer dit patroon gebruiken

Gebruik dit patroon wanneer:

  • Een cache biedt geen systeemeigen read-through- en write-through-bewerkingen.
  • De vraag naar resources is onvoorspelbaar. Dit patroon stelt toepassingen in staat op aanvraag gegevens te laden. Het doet geen aannames over welke gegevens een toepassing van tevoren nodig heeft.

Dit patroon kan ongeschikt zijn:

  • Als de gegevensset in de cache statisch is. Als de gegevens passen in de beschikbare cacheruimte, moet de cache bij het starten worden voorbereid met de gegevens en een beleid toegepast dat voorkomt dat de gegevens verlopen.
  • Bij het opslaan van informatie over de sessiestatus in de cache in een webtoepassing die wordt gehost in een webfarm. In deze omgeving dient u het introduceren van afhankelijkheden op basis van client-serveraffiniteit te vermijden.

Voorbeeld

In Microsoft Azure kunt u Azure Cache voor Redis gebruiken om een gedistribueerde cache te maken die kan worden gedeeld door meerdere exemplaren van een toepassing.

In deze volgende codevoorbeelden wordt de StackExchange.Redis-client gebruikt. Dit is een Redis-clientbibliotheek die is geschreven voor .NET. Als u verbinding wilt maken met een Azure Cache voor Redis exemplaar, roept u de statische ConnectionMultiplexer.Connect methode aan en geeft u de connection string door. Met deze methode wordt een ConnectionMultiplexer geretourneerd die de verbinding representeert. U kunt een exemplaar van ConnectionMultiplexer in uw toepassing delen door een statische eigenschap in te stellen die een verbonden exemplaar retourneert, zoals in het volgende voorbeeld. Deze benadering biedt een thread-veilige manier om slechts één verbonden exemplaar te initialiseren.

private static ConnectionMultiplexer Connection;

// Redis connection string information
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
    string cacheConnection = ConfigurationManager.AppSettings["CacheConnection"].ToString();
    return ConnectionMultiplexer.Connect(cacheConnection);
});

public static ConnectionMultiplexer Connection => lazyConnection.Value;

De GetMyEntityAsync methode in het volgende codevoorbeeld toont een implementatie van het Cache-Aside patroon. Met deze methode wordt een object opgehaald uit de cache met behulp van de leesmethode.

Een object wordt geïdentificeerd aan de hand van een sleutel (een geheeltallige id). Met de GetMyEntityAsync-methode wordt met deze sleutel een item uit de cache opgehaald. Als er een overeenkomend item wordt gevonden, wordt het geretourneerd. Als er geen match is, wordt met de GetMyEntityAsync-methode het object uit een gegevensarchief gehaald, toegevoegd aan de cache en vervolgens geretourneerd. De code die de gegevens in het gegevensarchief feitelijk leest, wordt hier niet getoond, omdat deze afhankelijk is van het gegevensarchief. Het item in de cache wordt geconfigureerd om te verlopen om te voorkomen dat het verouderd als het ergens anders wordt bijgewerkt.

// Set five minute expiration as a default
private const double DefaultExpirationTimeInMinutes = 5.0;

public async Task<MyEntity> GetMyEntityAsync(int id)
{
  // Define a unique key for this method and its parameters.
  var key = $"MyEntity:{id}";
  var cache = Connection.GetDatabase();

  // Try to get the entity from the cache.
  var json = await cache.StringGetAsync(key).ConfigureAwait(false);
  var value = string.IsNullOrWhiteSpace(json)
                ? default(MyEntity)
                : JsonConvert.DeserializeObject<MyEntity>(json);

  if (value == null) // Cache miss
  {
    // If there's a cache miss, get the entity from the original store and cache it.
    // Code has been omitted because it is data store dependent.
    value = ...;

    // Avoid caching a null value.
    if (value != null)
    {
      // Put the item in the cache with a custom expiration time that
      // depends on how critical it is to have stale data.
      await cache.StringSetAsync(key, JsonConvert.SerializeObject(value)).ConfigureAwait(false);
      await cache.KeyExpireAsync(key, TimeSpan.FromMinutes(DefaultExpirationTimeInMinutes)).ConfigureAwait(false);
    }
  }

  return value;
}

In de voorbeelden wordt Azure Cache voor Redis gebruikt om toegang te krijgen tot het archief en informatie op te halen uit de cache. Zie Using Azure Cache voor Redis and How to create a Web App with Azure Cache voor Redis (Een web-app maken met Azure Cache voor Redis) voor meer informatie.

De hieronder getoonde UpdateEntityAsync-methode laat zien hoe een object in de cache ongeldig moet worden gemaakt als de waarde door de toepassing wordt gewijzigd. Met de code wordt het oorspronkelijke gegevensarchief bijgewerkt en vervolgens wordt het item uit de cache verwijderd.

public async Task UpdateEntityAsync(MyEntity entity)
{
    // Update the object in the original data store.
    await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);

    // Invalidate the current cache object.
    var cache = Connection.GetDatabase();
    var id = entity.Id;
    var key = $"MyEntity:{id}"; // The key for the cached object.
    await cache.KeyDeleteAsync(key).ConfigureAwait(false); // Delete this key from the cache.
}

Notitie

De volgorde van de stappen is van belang. Werk het gegevensarchief bij voordat u het item uit de cache verwijdert. Als u het cache-item eerst verwijdert, is er een korte periode gedurende welke een client het item kan ophalen voordat het gegevensarchief wordt bijgewerkt. Dit leidt tot een cachemisser (omdat het item uit de cache is verwijderd), waardoor de eerdere versie van het item uit het gegevensarchief moet worden gehaald en weer toegevoegd aan de cache. Dit resulteert in verouderde cache-gegevens.

De volgende informatie is mogelijk relevant bij het implementeren van dit patroon:

  • Richtlijnen voor caching. Biedt aanvullende informatie over hoe u in geval van een cloudoplossing gegevens in de cache kunt plaatsen, en over de problemen waar u rekening mee dient te houden als u een cache implementeert.

  • Inleiding tot gegevensconsistentie. Cloudtoepassingen maken gewoonlijk gebruik van gegevens die over gegevensarchieven zijn verspreid. Het beheren en handhaven van gegevensconsistentie in deze omgeving is een kritisch aspect van het systeem, met name omdat er problemen met gelijktijdigheid en beschikbaarheid op kunnen treden. In de inleiding worden problemen beschreven met consistentie van gedistribueerde gegevens en er wordt een samenvatting gegeven van hoe een toepassing uiteindelijk consistentie kan implementeren om de beschikbaarheid van gegevens te handhaven.