Richtlijnen voor caching

Azure Cache for Redis

Caching (in de cache opslaan van gegevens) is een veelvoorkomende techniek die als doel heeft de prestaties en schaalbaarheid van een systeem te verbeteren. Hiermee worden gegevens in de cache opgeslagen door tijdelijk veelgebruikte gegevens te kopiëren naar snelle opslag die zich dicht bij de toepassing bevindt. Als deze snelle gegevensopslag zich dichter bij de toepassing bevindt dan de oorspronkelijke bron, dan kan caching de reactietijden voor clienttoepassingen significant verbeteren doordat er meer gegevens kunnen worden verwerkt.

Caching is het effectiefst als een clientexemplaar dezelfde gegevens leest, en vooral als de volgende voorwaarden van toepassing zijn op de oorspronkelijke gegevensopslag:

  • De gegevensopslag blijft relatief statisch.
  • De gegevensopslag is traag vergeleken met de snelheid van de cache.
  • De gegevensopslag is onderhevig aan een hoog conflictenniveau.
  • De gegevensopslag is ver weg als netwerklatentie de toegang kan vertragen.

Caching in gedistribueerde toepassingen

Gedistribueerde toepassingen implementeren gewoonlijk een van de volgende strategieën (of beide) als er gegevens in de cache worden opgeslagen:

  • Ze gebruiken een privécache, waarbij gegevens lokaal worden bewaard op de computer waarop een exemplaar van een toepassing of service wordt uitgevoerd.
  • Ze gebruiken een gedeelde cache, die fungeert als een gemeenschappelijke bron die toegankelijk is voor meerdere processen en machines.

In beide gevallen kan caching worden uitgevoerd aan de clientzijde en aan de serverzijde. Caching aan de clientzijde wordt uitgevoerd door het proces dat de gebruikersinterface voor een systeem levert, bijvoorbeeld een webbrowser of bureaubladtoepassing. Caching aan de serverzijde wordt uitgevoerd door het proces dat de bedrijfsservices levert die extern worden uitgevoerd.

Gegevens opslaan in een privécache

Het meest eenvoudige type cache is een in-memory-opslag. Deze bevindt zich in de adresruimte van één proces en wordt rechtstreeks geopend door de code die voor dat proces wordt uitgevoerd. Dit type cache is snel toegankelijk. Het kan ook een effectieve manier bieden voor het opslaan van bescheiden hoeveelheden statische gegevens. De grootte van een cache wordt doorgaans beperkt door de hoeveelheid geheugen die beschikbaar is op de computer waarop het proces wordt gehost.

Als u meer gegevens in de cache wilt opslaan dan qua geheugen fysiek mogelijk is, kunt u gegevens in de cache wegschrijven naar het lokale bestandssysteem. Dit proces verloopt langzamer dan gegevens die in het geheugen zijn opgeslagen, maar het moet nog steeds sneller en betrouwbaarder zijn dan het ophalen van gegevens in een netwerk.

Als u beschikt over meerdere exemplaren van een toepassing die gelijktijdig worden uitgevoerd en die gebruikmaken van dit model, dan heeft elk exemplaar van de toepassing een eigen, onafhankelijke cache waarin een eigen kopie van de gegevens worden bewaard.

Denk aan een cache als een momentopname van de oorspronkelijke gegevens op een bepaald moment in het verleden. Als deze gegevens niet statisch zijn, is het waarschijnlijk dat verschillende toepassingsexemplaren verschillende versies van de gegevens in hun cache bevatten. Daarom kan dezelfde query die door deze exemplaren wordt uitgevoerd, verschillende resultaten retourneren, zoals weergegeven in afbeelding 1.

The results of using an in-memory cache in different instances of an application

Afbeelding 1: Een cache in het geheugen gebruiken in verschillende exemplaren van een toepassing.

Gedeelde cache

Als u een gedeelde cache gebruikt, kan dit helpen bij het verlichten van problemen die gegevens in elke cache kunnen verschillen, wat kan gebeuren met cacheopslag in het geheugen. Een gedeelde cache garandeert dat verschillende exemplaren van een toepassing dezelfde weergave van de gegevens in de cache zien. De cache wordt op een afzonderlijke locatie gevonden, die doorgaans wordt gehost als onderdeel van een afzonderlijke service, zoals wordt weergegeven in afbeelding 2.

The results of using a shared cache

Afbeelding 2: Een gedeelde cache gebruiken.

Een belangrijk voordeel van het gebruik van een gedeelde cache is dat deze schaalbaarheid mogelijk maakt. Veel gedeelde cacheservices worden geïmplementeerd met behulp van een cluster servers en software gebruiken om de gegevens transparant over het cluster te verdelen. Een exemplaar van de toepassing verzendt gewoon een aanvraag naar de cacheservice. De onderliggende infrastructuur bepaalt de locatie van de gegevens in de cache in het cluster. U kunt de cache gemakkelijk schalen door meer servers toe te voegen.

Het gebruik van een gedeelde cache kent twee belangrijke nadelen:

  • De cache is langzamer voor toegang omdat deze niet langer lokaal wordt bewaard voor elk toepassingsexemplaren.
  • De vereiste dat er een afzonderlijke cacheservice moet worden geïmplementeerd, kan de oplossing complexer maken.

Overwegingen bij het gebruik van caches

In de volgende secties worden in meer detail de overwegingen beschreven bij het ontwerpen en gebruiken van caches.

Wanneer heeft het opslaan van gegevens in de cache de voorkeur?

Door gebruik te maken van een cache kunnen de prestaties, de schaalbaarheid en de beschikbaarheid drastisch worden verbeterd. Hoe meer gegevens u hebt en hoe groter het aantal gebruikers die deze gegevens nodig hebben, hoe groter de voordelen zijn van het gebruik van een cache. Caching vermindert de latentie en conflicten die zijn gekoppeld aan het verwerken van grote hoeveelheden gelijktijdige aanvragen in het oorspronkelijke gegevensarchief.

Zo kan bijvoorbeeld een database slechts een beperkt aantal gelijktijdige verbindingen ondersteunen. Door gegevens uit een gedeelde cache op te halen in plaats van uit de onderliggende database, wordt er echter voor gezorgd dat een clienttoepassing deze gegevens kan openen, ook als er op dat moment geen verbindingen meer beschikbaar zijn. Bovendien, als de database niet langer beschikbaar is, kunnen clienttoepassingen de gegevens blijven gebruiken die in de cache worden bewaard.

Neem bijvoorbeeld het in de cache opslaan van gegevens die regelmatig worden gelezen maar niet vaak worden gewijzigd (bijvoorbeeld gegevens waarvan het aantal leesbewerkingen groter is dan schrijfbewerkingen). U wordt echter niet aangeraden de cache te gebruiken als de gezaghebbende bewaarplaats van kritische gegevens. Zorg er in plaats daarvan voor dat alle wijzigingen die uw toepassing zich niet kan veroorloven, altijd worden opgeslagen in een permanent gegevensarchief. Als de cache niet beschikbaar is, kan uw toepassing nog steeds blijven werken met behulp van het gegevensarchief en verliest u geen belangrijke informatie.

Gegevens efficiënt in de cache opslaan

De belangrijkste factor bij het efficiënt gebruiken van de cache is het bepalen van de juiste gegevens die erin moeten worden opgeslagen en het juiste tijdstip. De gegevens kunnen op aanvraag worden toegevoegd aan de cache wanneer deze voor het eerst worden opgehaald door een toepassing. De toepassing moet de gegevens slechts eenmaal ophalen uit het gegevensarchief en dat er aan de volgende toegang kan worden voldaan met behulp van de cache.

Een alternatief is dat de cache vooraf gedeeltelijk of geheel met gegevens kan worden gevuld, gewoonlijk wanneer de toepassing wordt gestart (een benadering die seeding wordt genoemd). Het wordt echter niet aangeraden seeding te implementeren bij een grote cache, omdat dit een plotselinge, hoge belasting kan veroorzaken voor het oorspronkelijke gegevensarchief als de toepassing wordt uitgevoerd.

Een analyse van gebruikspatronen kan u doen besluiten of u een cache vooraf gedeeltelijk of geheel wilt vullen en welke gegevens u daarvoor wilt gebruiken. U kunt de cache bijvoorbeeld seeden met de statische gebruikersprofielgegevens voor klanten die de toepassing regelmatig gebruiken (misschien elke dag), maar niet voor klanten die de toepassing slechts één keer per week gebruiken.

Caching werkt gewoonlijk goed voor gegevens die onveranderlijk zijn of niet vaak worden gewijzigd. Voorbeelden zijn referentiegegevens, zoals product- en prijsgegevens in een toepassing voor e-commerce, of gedeelde, statische bronnen waarvoor de ontwikkelkosten hoog zijn. Deze gegevens kunnen (deels) bij het starten van de toepassing in de cache worden geladen om de vraag naar resources te minimaliseren en de prestaties te verhogen. Mogelijk wilt u ook een achtergrondproces hebben waarmee de referentiegegevens in de cache periodiek worden bijgewerkt om ervoor te zorgen dat deze up-to-date zijn. Of het achtergrondproces kan de cache vernieuwen wanneer de verwijzingsgegevens worden gewijzigd.

Caching is minder geschikt voor dynamische gegevens, hoewel er hierop uitzonderingen bestaan (zie de sectie Zeer dynamische gegevens in de cache opslaan verderop in dit artikel voor meer informatie). Wanneer de oorspronkelijke gegevens regelmatig worden gewijzigd, wordt de informatie in de cache snel verlopen of vermindert de overhead van het synchroniseren van de cache met het oorspronkelijke gegevensarchief de effectiviteit van caching.

Een cache hoeft de volledige gegevens voor een entiteit niet op te nemen. Als een gegevensitem bijvoorbeeld een object met meerdere waarden vertegenwoordigt, zoals een bankklant met een naam, adres en rekeningsaldo, kunnen sommige van deze elementen statisch blijven, zoals de naam en het adres. Andere elementen, zoals het rekeningsaldo, zijn mogelijk dynamischer. In deze situaties kan het handig zijn om de statische delen van de gegevens in de cache op te plaatsen en alleen de resterende gegevens op te halen (of te berekenen) wanneer deze nodig zijn.

Het is raadzaam om prestatietests en gebruiksanalyses uit te voeren om te bepalen of het vooraf invullen of laden van de cache op aanvraag, of een combinatie van beide, geschikt is. Dit besluit dient te worden gebaseerd op de volatiliteit en het gebruikspatroon van de gegevens. Cachegebruik en prestatieanalyse zijn belangrijk in toepassingen die zware belastingen tegenkomen en zeer schaalbaar moeten zijn. In zeer schaalbare scenario's kunt u de cache bijvoorbeeld seeden om de belasting van het gegevensarchief op piekmomenten te verminderen.

Caching kan ook worden gebruikt om herhaalde berekeningen te voorkomen terwijl de toepassing wordt uitgevoerd. Als door een bewerking gegevens worden getransformeerd of als er een gecompliceerde berekening wordt uitgevoerd, kunnen de resultaten in de cache worden opgeslagen. Als later dezelfde berekening moet worden uitgevoerd, kunnen de gegevens gewoon uit de cache worden opgehaald.

De gegevens in de cache kunnen door een toepassing worden gewijzigd. Beschouw de cache liever als een tijdelijk gegevensarchief die op elk moment kan verdwijnen. Sla geen waardevolle gegevens alleen op in de cache; zorg ervoor dat u ook de informatie in het oorspronkelijke gegevensarchief onderhoudt. Dat betekent dat als de cache niet meer beschikbaar is, de kans op gegevensverlies minimaal is.

Zeer dynamische gegevens in de cache opslaan

Wanneer u snel veranderende informatie opslaat in een permanent gegevensarchief, kan dit een overhead voor het systeem betekenen. Neem bijvoorbeeld een apparaat dat voortdurend de status of een andere meting rapporteert. Als ervoor wordt gekozen deze gegevens niet in de cache op te slaan omdat dergelijke gegevens in de cache vrijwel altijd verouderd zullen zijn, dan geldt deze overweging ook als deze gegevens in het gegevensarchief moeten worden opgeslagen en eruit opgehaald. In de tijd die het kost om deze gegevens op te slaan en op te halen, kunnen ze inmiddels gewijzigd zijn.

In een dergelijke situatie dient u de voordelen van het rechtstreeks opslaan van dynamische gegevens in de cache te vergelijken met het opslaan ervan in een permanent gegevensarchief. Als de gegevens niet kritiek zijn en geen controle vereist, maakt het niet uit of de incidentele wijziging verloren gaat.

Verloop van gegevens in de cache beheren

In de meeste gevallen zijn gegevens die in de cache worden bewaard een kopie van gegevens die in het oorspronkelijke gegevensarchief worden bewaard. De gegevens in het oorspronkelijke gegevensarchief kunnen worden gewijzigd nadat ze in de cache zijn opgeslagen, waardoor deze laatste dus verouderd zijn. Voor diverse cachingsystemen is het mogelijk de cache te configureren, zodat gegevens verlopen en de periode gedurende welke ze verouderd zijn, in te korten.

Wanneer de gegevens in de cache verlopen, worden deze verwijderd uit de cache en moet de toepassing de gegevens ophalen uit het oorspronkelijke gegevensarchief (de zojuist opgehaalde gegevens kunnen weer in de cache worden geplaatst). U kunt een standaardbeleid voor het verlopen van gegevens instellen als u de cache configureert. In veel cachingservices kunt u ook de verloopperiode voor afzonderlijke objecten instellen als u ze programmatisch in de cache opslaat. Met sommige caches kunt u de verloopperiode opgeven als een absolute waarde of als een schuifwaarde die ervoor zorgt dat het item uit de cache wordt verwijderd als het niet binnen de opgegeven tijd wordt geopend. Deze instelling overschrijft elk beleid dat op de cache als geheel van toepassing is, maar alleen voor bepaalde objecten.

Notitie

Laten we de verloopperiode voor de cache en de objecten die deze bevat eens nauwkeuriger bekijken. Als de periode te kort is, verlopen de objecten te snel waardoor het voordeel van het gebruik van de cache afneemt. Als de periode te lang is, riskeert u dat de gegevens in de cache verouderen.

Het is ook mogelijk dat de cache vol raakt als gegevens er te lang in mogen worden bewaard. In dat geval kan het gebeuren dat bij een aanvraag om nieuwe items toe te voegen, sommige items gedwongen verwijderd worden. Cachingservices verwijderen gegevens gewoonlijk op basis van een beleid waarbij de minst recent gebruikte items worden verwijderd. U kunt dit beleid meestal overschrijven, zodat de items nog steeds beschikbaar blijven. Als u echter voor deze aanpak kiest, bestaat de kans dat het geheugen van de cache vol raakt. Als er een item aan de cache wordt toegevoegd, wordt er een uitzonderingsfout weergegeven.

Sommige implementaties van de cache kunnen aanvullende verwijderingsbeleid bieden. Er zijn diverse soorten verwijderingsbeleid. Deze omvatten:

  • Een meest recent gebruikt beleid (in de verwachting dat de gegevens niet opnieuw vereist zijn).
  • Beleid op basis van first-in-first-out (oudste gegevens worden het eerst verwijderd).
  • Expliciet verwijderingsbeleid op basis van een geactiveerde gebeurtenis (zoals gewijzigde gegevens).

Gegevens in een cache aan de clientzijde ongeldig maken

Gegevens die in een cache aan de clientzijde worden bewaard, worden over het algemeen beschouwd als buiten de verantwoordelijkheid van de service die de gegevens voor de client levert. Een service kan een client niet rechtstreeks dwingen om gegevens toe te voegen aan of te verwijderen uit een cache aan de clientzijde.

Dat betekent dat een client met een slecht geconfigureerde cache verouderde gegevens kan blijven gebruiken. Als bijvoorbeeld het verloopbeleid voor de cache niet correct is geïmplementeerd, kan een client verouderde gegevens gebruiken die lokaal in de cache is opgeslagen wanneer de gegevens in het oorspronkelijke gegevensarchief zijn gewijzigd.

Als u een webtoepassing bouwt die gegevens via een HTTP-verbinding bedient, kunt u impliciet afdwingen dat een webclient (zoals een browser of webproxy) de meest recente informatie ophaalt. U kunt dit doen als een resource wordt bijgewerkt door een wijziging in de URI van die resource. Webclients maken gewoonlijk gebruik van de URI van een resource als de sleutel in de cache aan de clientzijde. Dus als de URI wordt gewijzigd, negeert de webclient eerdere versies van een resource in de cache en haalt de nieuwe versie op.

Gelijktijdigheid in een cache beheren

Caches zijn meestal ontworpen om te worden gedeeld door meerdere exemplaren van een toepassing. Elk exemplaar van de toepassing kan gegevens in de cache lezen en wijzigen. Dezelfde problemen met gelijktijdigheid die optreden met een gedeeld gegevensarchief kunnen dus ook optreden met een cache. In een situatie waarin een toepassing gegevens moet wijzigen die in de cache zijn opgeslagen, moet u er mogelijk voor zorgen dat updates die zijn aangebracht door één exemplaar van de toepassing, de wijzigingen die door een ander exemplaar zijn aangebracht, niet overschrijven.

Afhankelijk van de aard van de gegevens en de kans op conflicten, kunt u een van de volgende twee benaderingen kiezen in geval van gelijktijdigheid:

  • Optimistisch. Onmiddellijk voordat de gegevens worden bijgewerkt, wordt gecontroleerd of de gegevens in de cache zijn gewijzigd sinds ze zijn opgehaald. Als de gegevens nog dezelfde zijn, kan de wijziging worden uitgevoerd. Anders moet de toepassing besluiten of de gegevens moeten worden bijgewerkt. (De bedrijfslogica die deze beslissing aanstuurt, is toepassingsspecifiek.) Deze methode is geschikt voor situaties waarin updates niet vaak voorkomen of wanneer conflicten waarschijnlijk niet voorkomen.
  • Pessimistisch. Als de gegevens worden opgehaald, worden ze door de toepassing in de cache vergrendeld om te voorkomen dat ze door een ander exemplaar van de toepassing worden gewijzigd. Dit proces zorgt ervoor dat conflicten niet kunnen optreden, maar ze kunnen ook andere exemplaren blokkeren die dezelfde gegevens moeten verwerken. Pessimistische gelijktijdigheid kan de schaalbaarheid van een oplossing beïnvloeden en wordt alleen aangeraden bij kortstondige bewerkingen. Deze benadering kan geschikt zijn in situaties waarbij de kans op conflicten groter is, met name als door een toepassing meerdere items in de cache worden bijgewerkt en ervoor moet worden gezorgd dat deze wijzigingen consequent worden toegepast.

Hoge beschikbaarheid en schaalbaarheid implementeren en prestaties verbeteren

Vermijd het gebruik van de cache als de primaire opslagplaats van gegevens. Dit is de rol van het oorspronkelijke gegevensarchief van waaruit de cache wordt gevuld. Het oorspronkelijke gegevensarchief is verantwoordelijk voor de persistentie van de gegevens.

Zorg ervoor dat u geen kritische afhankelijkheden van de beschikbaarheid van een gedeelde cacheservice in uw oplossingen introduceert. Een toepassing moet kunnen blijven werken als de service die de gedeelde cache levert, niet beschikbaar is. De toepassing moet niet meer reageren of mislukken terwijl wordt gewacht totdat de cacheservice is hervat.

Daarom moet de toepassing worden voorbereid op het detecteren van de beschikbaarheid van de cacheservice en terugvallen op het oorspronkelijke gegevensarchief als de cache niet toegankelijk is. Het stroomonderbrekerpatroon is nuttig bij het afhandelen van dit scenario. De service die de cache levert kan worden hersteld. Zodra deze beschikbaar is, kan de cache opnieuw worden gevuld terwijl de gegevens uit het oorspronkelijke gegevensarchief worden gelezen, in navolging van een strategie als het patroon Cache-Aside.

De schaalbaarheid van het systeem kan echter worden beïnvloed als de toepassing terugvalt op het oorspronkelijke gegevensarchief wanneer de cache tijdelijk niet beschikbaar is. Tijdens het herstel van het gegevensarchief, kan het originele gegevensarchief worden overladen met aanvragen om gegevens, met time-outs en mislukte verbindingen tot gevolg.

U kunt eventueel een lokale privécache implementeren in elk exemplaar van een toepassing, in combinatie met de gedeelde cache waartoe alle toepassingen toegang hebben. Als er een item wordt opgehaald, kan eerst de lokale cache hierop worden gecontroleerd, vervolgens de gedeelde cache en ten slotte het oorspronkelijke gegevensarchief. Als de gedeelde cache niet beschikbaar is, kan de lokale cache worden ingevuld met de gegevens uit de gedeelde cache of database.

Voor deze aanpak is een zorgvuldige configuratie vereist om te voorkomen dat de lokale cache al te zeer verouderd in vergelijking met de gedeelde cache. De lokale cache fungeert echter als een buffer als de gedeelde cache niet bereikbaar is. Deze structuur wordt in afbeelding 3 getoond.

Using a local private cache with a shared cache

Afbeelding 3: Een lokale privécache gebruiken met een gedeelde cache.

Ter ondersteuning van grote caches waarin relatief langlevende gegevens worden bewaard, bieden sommige cachingservices een optie voor hoge beschikbaarheid die automatische failover implementeert als de cache onbereikbaar wordt. Deze benadering omvat het repliceren van de gegevens in de cache die zijn opgeslagen op een primaire cacheserver voor een secundaire cacheserver, alsmede het overschakelen naar de secundaire server als de primaire server stilvalt of als de verbinding wordt verbroken.

Voor het verminderen van de latentie die gepaard gaat met het schrijven naar meerdere doelen, kan de replicatie van de secundaire server asynchroon worden uitgevoerd als er gegevens naar de cache op de primaire server worden geschreven. Deze benadering leidt tot de mogelijkheid dat bepaalde gegevens in de cache verloren kunnen gaan als er een fout opgetreden is, maar het aandeel van deze gegevens moet klein zijn, vergeleken met de totale grootte van de cache.

Als een gedeelde cache groot is, kan het voordelig zijn de gegevens in de cache over knooppunten te partitioneren om de kans op conflicten te verminderen en de schaalbaarheid te vergroten. Veel gedeelde caches ondersteunen het dynamisch toevoegen (en verwijderen) van knooppunten en het opnieuw in balans brengen van gegevens in de partities. Hierbij is mogelijk clustering vereist, waarbij de verzameling knooppunten naadloos aan de clienttoepassingen wordt aangeboden als één cache. Intern worden de gegevens echter verdeeld over de knooppunten volgens een vooraf gedefinieerde strategie die de belasting evenredig verdeelt. Zie richtlijnen voor gegevenspartitionering voor meer informatie over mogelijke partitioneringsstrategieën.

Door clustering uit te voeren, kan de beschikbaarheid van de cache worden verhoogd. Als er een fout in een knooppunt optreedt, is de rest van de cache nog steeds beschikbaar. Clustering wordt vaak gebruikt in combinatie met replicatie en failover. Elk knooppunt kan worden gerepliceerd en de replica kan snel online worden gebracht als er een fout in het knooppunt optreedt.

Bij veel lees- en schrijfbewerkingen zijn enkelvoudige gegevenswaarden of objecten betrokken. Het kan af en toe echter noodzakelijk zijn grote gegevensvolumes snel op te slaan of op te halen. Bij het uitvoeren van seeding voor een cache worden mogelijk honderden of duizenden items naar de cache geschreven. Het kan ook voorkomen dat een toepassing een groot aantal gerelateerde items uit de cache moet ophalen als onderdeel van dezelfde aanvraag.

Diverse grootschalige caches bieden batchbewerkingen voor dergelijke doeleinden. Hierdoor kan een clienttoepassing een groot volume aan items tot één aanvraag verpakken. Dit vermindert de overhead die gepaard gaat met het uitvoeren van een groot aantal kleine aanvragen.

Caching en uiteindelijke consistentie

Wil het patroon Cache-Aside werken, dan moet het exemplaar van de toepassing dat de cache vult, toegang hebben tot de meest recente en consistente versie van de gegevens. In een systeem dat uiteindelijke consistentie implementeert (zoals een gerepliceerd gegevensarchief) is dit mogelijk niet het geval.

Het ene exemplaar van een toepassing kan bijvoorbeeld een gegevensitem kunnen wijzigen en de versie van dat item in de cache ongeldig maken. Een ander exemplaar van de toepassing kan een poging doen het item vanuit een cache te lezen, waarna een cache-misser optreedt, zodat de gegevens in het gegevensarchief worden gelezen en aan de cache worden toegevoegd. Als het gegevensarchief echter niet volledig is gesynchroniseerd met de andere replica's, kan het toepassingsexemplaren de cache lezen en vullen met de oude waarde.

Zie de Inleiding tot gegevensconsistentie voor meer informatie over het afhandelen van gegevensconsistentie.

Gegevens in de cache beveiligen

Kijk hoe u de in de cache opgeslagen gegevens kunt beveiligen tegen onbevoegde toegang, ongeacht de gebruikte cachingservice. Er zijn twee zaken waar u rekening mee dient te houden:

  • De privacy van de gegevens in de cache.
  • De privacy van de gegevensstroom tussen de cache en de toepassing die van de cache gebruikmaakt.

Als u gegevens in de cache wilt beveiligen, kan de cacheservice een verificatiemechanisme implementeren, waarbij vereist is dat de toepassingen de volgende gegevens opgeven:

  • Welke identiteiten hebben toegang tot gegevens in de cache.
  • Welke bewerkingen (lezen en schrijven) mogen deze identiteiten uitvoeren.

Om de met het lezen en schrijven gepaard gaande overhead te verminderen, kan een identiteit, nadat deze lees-en/of schrijftoegang voor de cache is verleend, gegevens in de cache gebruiken.

Als u de toegang tot subsets van de gegevens in de cache wilt beperken, kunt u dat op een van de volgende manieren doen:

  • Splits de cache in partities (door verschillende cacheservers te gebruiken) en verleen identiteiten alleen toegang tot de partities die ze mogen gebruiken.
  • Versleutel de gegevens in elke subset door verschillende sleutels te gebruiken en geef de versleutelingssleutels alleen op aan identiteiten die toegang tot een bepaalde subset moeten hebben. Een clienttoepassing kan nog steeds alle gegevens uit de cache ophalen, maar kan alleen de gegevens ontsleutelen als deze de sleutels heeft.

U dient ook de gegevens te beveiligen terwijl ze in en uit de cache stromen. Daartoe maakt u gebruik van de beveiligingsfuncties die worden geleverd door de netwerkinfrastructuur die door de clienttoepassingen worden gebruikt om verbinding te maken met de cache. Als de cache wordt geïmplementeerd met een on-site server binnen dezelfde organisatie die de clienttoepassingen host, dan hoeft u vanwege de isolering van het netwerk zelf geen aanvullende stappen te ondernemen. Als de cache zich op een externe locatie bevindt en er een TCP- of HTTP-verbinding via een openbaar netwerk (zoals internet) is vereist, kunt u overwegen SSL te implementeren.

Overwegingen voor het implementeren van caching in Azure

Azure Cache voor Redis is een implementatie van de open source Redis-cache die wordt uitgevoerd als een service in een Azure-datacenter. De service biedt een cachingservice die vanuit elke Azure-toepassing toegankelijk is, of de toepassing nu als een cloudservice, een website of binnen een virtuele machine van Azure is geïmplementeerd. Caches kunnen worden gedeeld door clienttoepassingen die de juiste toegangssleutel hebben.

Azure Cache voor Redis is een krachtige cachingoplossing die beschikbaarheid, schaalbaarheid en beveiliging biedt. Deze wordt doorgaans uitgevoerd als een service die verdeeld is over een of meer daartoe aangewezen machines. Er wordt getracht zoveel mogelijk informatie in-memory op te slaan om snelle toegang mogelijk te maken. Deze architectuur is bedoeld om lage latentie en hoge doorvoersnelheid te bieden door de noodzaak trage I/O-bewerkingen uit te voeren, te verminderen.

Azure Cache voor Redis is compatibel met veel van de verschillende API's die worden gebruikt door clienttoepassingen. Als u bestaande toepassingen hebt die al gebruikmaken van Azure Cache voor Redis die on-premises worden uitgevoerd, biedt de Azure Cache voor Redis een snel migratiepad voor caching in de cloud.

Functies van Redis

Redis is meer dan een eenvoudige cacheserver. Het biedt een gedistribueerde in-memory database met een uitgebreide opdrachtenset die ondersteuning biedt voor veelvoorkomende scenario's. Deze worden verderop in dit document beschreven, in de sectie Redis-caching gebruiken. Deze sectie bevat een overzicht van enkele van de belangrijkste functies die Redis biedt.

Redis als in-memory database

Redis ondersteunt zowel lees- als schrijfbewerkingen. In Redis worden schrijfbewerkingen beveiligd tegen systeemfouten doordat het periodiek in een lokaal momentopnamebestand is opgeslagen of in een logboekbestand waaraan alleen kan worden toegevoegd. Deze situatie is niet het geval in veel caches, die als transitief gegevensarchieven moeten worden beschouwd.

Alle schrijfbewerkingen zijn asynchroon en blokkeren clients niet om gegevens te lezen en te schrijven. Als Redis wordt gestart, worden de gegevens in het momentopnamebestand of het logboekbestand gelezen en gebruikt om de in-memory cache te bouwen. Zie Redis persistence (Persistentie van Redis) voor meer informatie.

Notitie

Redis garandeert niet dat alle schrijfbewerkingen worden opgeslagen als er sprake is van een catastrofale fout, maar in het slechtste geval verliest u slechts een paar seconden aan gegevens. Een cache is niet bedoeld als gezaghebbende gegevensbron en is de verantwoordelijkheid van de toepassingen die gebruikmaken van de cache om ervoor te zorgen dat kritieke gegevens worden opgeslagen in een geschikt gegevensarchief. Zie het cache-aside-patroon voor meer informatie.

Redis-gegevenstypen

Redis is een sleutel-waardearchief, waar waarden eenvoudige typen of complexe gegevensstructuren kunnen bevatten, zoals hashes, lijsten en sets. Redis biedt ondersteuning voor een aantal atomische bewerkingen op deze gegevenstypen. Sleutels kunnen permanent zijn of gelabeld met een beperkte levensduur, waarna de sleutel en de bijbehorende waarde automatisch uit de cache worden verwijderd. Ga naar de pagina An introduction to Redis data types and abstractions (Inleiding tot Redis-gegevenstypen en -abstracties) op de Redis-website voor meer informatie over sleutels en waarden van Redis.

Replicatie en clustering met Redis

Redis ondersteunt primaire/onderliggende replicatie om de beschikbaarheid te garanderen en doorvoer te onderhouden. Schrijfbewerkingen naar een primair Redis-knooppunt worden gerepliceerd naar een of meer onderliggende knooppunten. Leesbewerkingen kunnen worden uitgevoerd door de primaire of een van de onderliggende elementen.

Als u een netwerkpartitie hebt, kunnen ondergeschikten gegevens blijven verwerken en vervolgens transparant opnieuw synchroniseren met de primaire wanneer de verbinding opnieuw tot stand is gebracht. Ga naar de pagina Replication (Replicatie) op de website van Redis voor meer informatie.

Redis biedt ook clustering, waardoor u op transparante wijze gegevens over shards op servers kunt partitioneren en de belasting verdelen. Dankzij deze functie wordt de schaalbaarheid verbeterd, omdat er nieuwe Redis-servers kunnen worden toegevoegd en de gegevens opnieuw kunnen worden gepartitioneerd als de grootte van de cache toeneemt.

Bovendien kan elke server in het cluster worden gerepliceerd met behulp van primaire/onderliggende replicatie. Hierdoor wordt de beschikbaarheid voor elk knooppunt in het cluster gegarandeerd. Ga naar de pagina Redis cluster tutorial (Zelfstudie Redis-clusters) op de website van Redis voor meer informatie over clustering en sharding.

Gebruik van Redis-geheugen

Een Redis-cache heeft een eindige grootte. Deze is afhankelijk van de resources op de hostcomputer. Als u een Redis-server configureert, kunt u de maximale hoeveelheid geheugen opgeven die deze kan gebruiken. U kunt ook een sleutel in een Redis-cache zo configureren dat deze een verlooptijd heeft, waarna deze automatisch uit de cache wordt verwijderd. Dankzij deze functie wordt voorkomen dat de in-memory cache wordt gevuld met oude of verlopen gegevens.

Naarmate het geheugen wordt gevuld, kunnen automatisch sleutels en de bijbehorende waarden worden verwijderd door een aantal beleidsregels te volgen. De standaardwaarde is LRU (minst recent gebruikt), maar u kunt ook andere beleidsregels selecteren, zoals het willekeurig verwijderen van sleutels of het uitschakelen van verwijderingen (in dat geval wordt geprobeerd items aan de cache toe te voegen als deze vol is). Op de pagina Using Redis as an LRU cache (Redis gebruiken als LRU-cache) vindt u meer informatie.

Transacties en batches met Redis

Met Redis kan een clienttoepassing een reeks bewerkingen verzenden die gegevens in de cache lezen en schrijven als een atomische transactie. Alle opdrachten in de transactie worden gegarandeerd achtereenvolgens uitgevoerd en er worden geen opdrachten van andere, gelijktijdig uitgevoerde clients tussendoor ingevoerd.

Dit zijn echter geen echte transacties als een relationele database deze zou uitvoeren. Het verwerken van transacties bestaat uit twee fasen. In de eerste fase worden de opdrachten in de wachtrij gezet en in de tweede fase worden de opdrachten uitgevoerd. Tijdens het in de wachtrij zetten van de opdrachten, worden de opdrachten die de transactie vormen door de client verzonden. Als er op dit punt een fout optreedt (een syntaxisfout of een verkeerd aantal parameters) weigert Redis de hele transactie te verwerken, waarna deze wordt verwijderd.

Tijdens de uitvoeringsfase wordt elke opdracht in de wachtrij achtereenvolgens uitgevoerd. Als een opdracht mislukt tijdens deze fase, gaat Redis verder met de volgende opdracht in de wachtrij en wordt de effecten van opdrachten die al zijn uitgevoerd, niet teruggedraaid. Dankzij deze vereenvoudigde vorm van transacties worden de prestaties gehandhaafd en prestatieproblemen voorkomen die het gevolg kunnen zijn van conflicten.

Redis implementeert een soort optimistische vergrendeling om de consistentie te kunnen handhaven. Ga naar de pagina Transactions (Transacties) op de website van Redis voor meer informatie over transacties en vergrendeling met Redis.

Redis biedt ook ondersteuning voor niet-transactionele batchverwerking van aanvragen. Het Redis-protocol dat clients gebruiken om opdrachten te verzenden naar een Redis-server, stelt clients in staat een reeks bewerkingen als onderdeel van dezelfde aanvraag te verzenden. Dit draagt bij tot het verminderen van pakketfragmentatie op het netwerk. Wanneer de batch wordt verwerkt, wordt elke opdracht uitgevoerd. Als een van deze opdrachten ongeldig is, worden deze geweigerd (wat niet gebeurt met een transactie), maar worden de resterende opdrachten uitgevoerd. Er is ook geen garantie over de volgorde waarin de opdrachten in de batch worden verwerkt.

Veiligheid van Redis

Redis richt zich puur op het bieden van snelle toegang en is ontworpen om in een vertrouwde omgeving te worden uitgevoerd die alleen voor vertrouwde clients toegankelijk is. Redis ondersteunt een beperkt beveiligingsmodel op basis van wachtwoordverificatie. (Het is mogelijk om verificatie volledig te verwijderen, hoewel dit niet wordt aanbevolen.)

Alle geverifieerde clients delen hetzelfde globale wachtwoord en hebben toegang tot dezelfde resources. Als u uitgebreidere beveiliging voor aanmelden wilt, dient u uw eigen beveiligingslaag vóór de Redis-server te implementeren. Alle clientaanvragen moeten via deze laag lopen. Redis mag niet rechtstreeks worden blootgesteld aan niet-vertrouwde of niet-geverifieerde clients.

U kunt toegang tot de opdrachten beperken door ze uit te schakelen of ze een andere naam te geven (en door alleen bevoegde clients van nieuwe namen te voorzien).

Redis ondersteunt geen enkele vorm van gegevensversleuteling, dus alle codering moet worden uitgevoerd door clienttoepassingen. Daarnaast biedt Redis geen vorm van transportbeveiliging. Als u gegevens wilt beveiligen terwijl deze via het netwerk worden verzonden, wordt u aangeraden een SSL-proxy te implementeren.

Ga naar de pagina op de website van Redis Redis security (Beveiliging van Redis) voor meer informatie.

Notitie

Azure Cache voor Redis biedt een eigen beveiligingslaag waarmee clients verbinding maken. De onderliggende Redis-servers worden niet blootgesteld aan het openbare netwerk.

Azure Redis-cache

Azure Cache voor Redis biedt toegang tot Redis-servers die worden gehost in een Azure-datacenter. Het fungeert als een gevel die toegangsbeheer en beveiliging biedt. U kunt een cache inrichten via Azure Portal.

De portal biedt een aantal vooraf gedefinieerde configuraties. Het bereik van deze configuraties loopt van een cache van 53 GB, die wordt uitgevoerd als een speciale service en ondersteuning biedt voor SSL-communicatie (vanwege privacy) en hoofd-/onderliggende replicatie met een SLA met 99,9% beschikbaarheid, tot een cache van 250 GB zonder replicatie (geen beschikbaarheidsgaranties) die op gedeelde hardware wordt uitgevoerd.

Op Azure Portal kunt u het verwijderingsbeleid van de cache configureren en de toegang tot de cache beheren door gebruikers toe te voegen aan de opgegeven rollen. Deze rollen, waarmee de bewerkingen worden gedefinieerd die leden kunnen uitvoeren, omvatten Eigenaar, Inzender en Lezer. Zo kunnen leden van de rol Eigenaar volledige controle krijgen over de cache (waaronder beveiliging) en de inhoud ervan. Leden van de rol Inzender kunnen gegevens in de cache lezen en schrijven. Leden van de rol Lezer kunnen alleen gegevens uit de cache ophalen.

De meeste beheertaken worden uitgevoerd via Azure Portal. Daarom zijn veel van de beheeropdrachten die beschikbaar zijn in de standaardversie van Redis niet beschikbaar, inclusief de mogelijkheid om de configuratie programmatisch te wijzigen, de Redis-server af te sluiten, extra ondergeschikten te configureren of gegevens geforceerd op schijf op te slaan.

Azure Portal bevat een handige grafische weergave waarmee u de prestaties van de cache kunt controleren. Zo kunt u een overzicht krijgen van het aantal verbindingen dat wordt gemaakt, het aantal verzoeken dat wordt uitgevoerd plus het aantal lees- en schrijfbewerkingen, en het aantal cache-hits versus cachemissers. Met deze informatie kunt u de effectiviteit van de cache vaststellen en zo nodig overschakelen naar een andere configuratie of het verwijderingsbeleid wijzigen.

Bovendien kunt u waarschuwingen opstellen waardoor e-mailberichten naar een beheerder worden verzonden als een of meer cruciale metrische gegevens buiten een verwacht bereik vallen. U kunt bijvoorbeeld een beheerder waarschuwen als het aantal cachemissers het afgelopen uur een bepaalde waarde overschrijdt. Dit betekent namelijk dat de cache mogelijk te klein is of dat gegevens te snel worden verwijderd.

U kunt ook de CPU, het geheugen en het netwerkgebruik voor de cache in de gaten houden.

Voor meer informatie en voorbeelden die laten zien hoe u een Azure Cache voor Redis maakt en configureert, gaat u naar de pagina Lap rond Azure Cache voor Redis in de Azure-blog.

Sessiestatus van de cache en HTML-uitvoer

Als u ASP.NET webtoepassingen bouwt die worden uitgevoerd met behulp van Azure-webrollen, kunt u sessiestatusgegevens en HTML-uitvoer opslaan in een Azure Cache voor Redis. Met de sessiestatusprovider voor Azure Cache voor Redis kunt u sessiegegevens delen tussen verschillende exemplaren van een ASP.NET-webtoepassing. Dit is erg handig in situaties met webfarms waarin client-serveraffiniteit niet beschikbaar is en het opslaan van sessiegegevens in het geheugen niet geschikt zou zijn.

Het gebruik van de sessiestatusprovider met Azure Cache voor Redis biedt verschillende voordelen, waaronder:

  • Delen van de sessiestatus met een groot aantal exemplaren van ASP.NET-webtoepassingen.
  • Verbeterde schaalbaarheid.
  • Ondersteuning van beheerde, gelijktijdige toegang tot dezelfde gegevens over de sessiestatus voor meerdere lezers en één schrijver.
  • Gebruik van compressie om geheugen te besparen en de netwerkprestaties te verbeteren.

Zie ASP.NET sessiestatusprovider voor Azure Cache voor Redis voor meer informatie.

Notitie

Gebruik de sessiestatusprovider niet voor Azure Cache voor Redis met ASP.NET toepassingen die buiten de Azure-omgeving worden uitgevoerd. De latentie van het openen van de cache van buiten Azure kan de prestatievoordelen van gegevens in de cache elimineren.

Op dezelfde manier kunt u met de uitvoercacheprovider voor Azure Cache voor Redis de HTTP-antwoorden opslaan die worden gegenereerd door een ASP.NET-webtoepassing. Het gebruik van de uitvoercacheprovider met Azure Cache voor Redis kan de reactietijden verbeteren van toepassingen die complexe HTML-uitvoer genereren. Toepassingsexemplaren die vergelijkbare antwoorden genereren, kunnen gebruikmaken van de gedeelde uitvoerfragmenten in de cache in plaats van deze HTML-uitvoer afresh te genereren. Zie ASP.NET uitvoercacheprovider voor Azure Cache voor Redis voor meer informatie.

Aangepaste Redis-cache bouwen

Azure Cache voor Redis fungeert als een gevel voor de onderliggende Redis-servers. Als u een geavanceerde configuratie nodig hebt die niet wordt gedekt door de Azure Redis-cache (zoals een cache die groter is dan 53 GB), kunt u uw eigen Redis-servers bouwen en hosten met behulp van virtuele Azure-machines.

Dit is een mogelijk complex proces omdat u mogelijk meerdere VM's moet maken om te fungeren als primaire en onderliggende knooppunten als u replicatie wilt implementeren. Als u bovendien een cluster wilt maken, hebt u meerdere primaries en onderliggende servers nodig. Een minimale geclusterde replicatietopologie die een hoge mate van beschikbaarheid en schaalbaarheid biedt, bestaat uit ten minste zes VM's die zijn georganiseerd als drie paren primaire/onderliggende servers (een cluster moet ten minste drie primaire knooppunten bevatten).

Elk primair/ondergeschikt paar moet zich dicht bij elkaar bevinden om de latentie te minimaliseren. Als u gegevens in de cache in de buurt wilt hebben van de toepassingen die er gebruik van gaan maken, kunt u elk paar echter laten uitvoeren in verschillende Azure-datacenters die in verschillende regio's zijn ondergebracht. Zie Running Redis on a CentOS Linux VM in Azure (Redis uitvoeren op een Centos Linux-VM in Azure) voor een voorbeeld van het bouwen en configureren van een Redis-knooppunt dat als een VM in Azure wordt uitgevoerd.

Notitie

Als u uw eigen Redis-cache op deze manier implementeert, bent u verantwoordelijk voor het bewaken, beheren en beveiligen van de service.

Redis-cache partitioneren

Voor het partitioneren van de cache dient u de cache over meerdere computers te verdelen. Deze structuur biedt enkele voordelen boven het gebruik van één cacheserver, zoals:

  • Er kan een cache worden gemaakt met meer ruimte dan op één server.
  • Mogelijkheid tot het distribueren van gegevens over servers, wat de beschikbaarheid verbetert. Als er een server uitvalt of ontoegankelijk wordt, zijn de gegevens erop niet beschikbaar, maar de gegevens op de overige servers zijn dat wel. Voor een cache is dit niet cruciaal omdat de gegevens in de cache slechts een tijdelijke kopie zijn van de gegevens die in een database zijn opgeslagen. Gegevens in de cache op een server die ontoegankelijk wordt, kunnen in de cache op een andere server worden opgeslagen.
  • Mogelijkheid tot het verdelen van de belasting over meerdere servers, waardoor de prestaties en schaalbaarheid groter worden.
  • Gegevens kunnen worden ondergebracht op locaties dichtbij de gebruikers die er toegang toe hebben, waardoor de latentie wordt verminderd.

Sharding is de meestgebruikte vorm van partitioneren van een cache. Bij deze strategie is elke partitie (of shard) een op zichzelf staande Redis-cache. Gegevens worden door middel van sharding-logica doorgestuurd naar een specifieke partitie. De gegevens kunnen op verschillende manieren worden verdeeld. Op de pagina Sharding-patroon vindt u meer informatie over het implementeren van sharding.

Als u partitionering wilt implementeren in een Redis-cache, kunt u dat op een van de volgende manieren doen:

  • Query-routering aan de serverzijde. Bij deze techniek verzendt een clienttoepassing een aanvraag naar een van de Redis-servers waarvan de cache deel uitmaakt (waarschijnlijk de dichtstbijzijnde server). Op elke Redis-server bevat metagegevens met een beschrijving van de partitie die erop aanwezig is en tevens informatie over welke partities op andere servers aanwezig zijn. De Redis-server onderzoekt de aanvraag van de client. Als de aanvraag lokaal kan worden omgezet, kan de aangevraagde bewerking worden uitgevoerd. Zo niet, dan wordt de aanvraag doorgestuurd naar een geschikte server. Dit model is geïmplementeerd door Redis-clustering en wordt in meer detail beschreven op de pagina Redis cluster tutorial (Zelfstudie Redis-clusters) op de website van Redis. Redis-clustering is transparant voor clienttoepassingen en er kunnen extra Redis-servers worden toegevoegd aan het cluster (en de gegevens kunnen opnieuw worden gepartitioneerd) zonder dat u de clients opnieuw hoeft te configureren.
  • Partitionering aan de clientzijde. In dit model bevat de clienttoepassing logica (mogelijk in de vorm van een bibliotheek) waarmee aanvragen naar een geschikte Redis-server wordt doorgestuurd. Deze methode kan worden gebruikt met Azure Cache voor Redis. Maak meerdere Azure Cache voor Redis (één voor elke gegevenspartitie) en implementeer de logica aan de clientzijde waarmee de aanvragen naar de juiste cache worden gerouteerd. Als het partitioneringsschema wordt gewijzigd (als er bijvoorbeeld extra Azure Cache voor Redis worden gemaakt), moeten clienttoepassingen mogelijk opnieuw worden geconfigureerd.
  • Partitionering met behulp van proxy's. In dit schema verzenden clienttoepassingen aanvragen naar een intermediaire proxyservice. Deze weet hoe de gegevens zijn gepartitioneerd en stuurt de aanvraag door naar de desbetreffende Redis-server. Deze benadering kan ook worden gebruikt met Azure Cache voor Redis; de proxyservice kan worden geïmplementeerd als een Azure-cloudservice. Deze aanpak vereist een extra complexiteitsniveau om de service te implementeren; aanvragen kunnen er langer over doen om te worden uitgevoerd dan bij het gebruik van partitionering aan de clientzijde.

Op de pagina Partitioning: how to split data among multiple Redis instances (Partitionering: het splitsen van gegevens tussen meerdere exemplaren van Redis) op de Redis-website vindt u meer informatie over het implementeren van partitioneren met Redis.

Clienttoepassingen met Redis-cache implementeren

Redis ondersteunt clienttoepassingen die in diverse programmeertalen zijn geschreven. Als u nieuwe toepassingen bouwt met behulp van .NET Framework, raden we u aan de clientbibliotheek StackExchange.Redis te gebruiken. Deze bibliotheek bevat een .NET Framework-objectmodel dat de details isoleert om verbinding te maken met een Redis-server, opdrachten te verzenden en antwoorden te ontvangen. Het is beschikbaar in Visual Studio als een NuGet-pakket. U kunt dezelfde bibliotheek gebruiken om verbinding te maken met een Azure Cache voor Redis of een aangepaste Redis-cache die wordt gehost op een virtuele machine.

Als u verbinding wilt maken met een Redis-server, gebruikt u de statische Connect-methode van de ConnectionMultiplexer-klasse. De verbinding die met deze methode wordt gemaakt, is ontworpen om te worden gebruikt gedurende de hele levensduur van de clienttoepassing; dezelfde verbinding kan door meerdere gelijktijdige threads worden gebruikt. Maak niet opnieuw verbinding en verbreek de verbinding niet telkens wanneer u een Redis-bewerking uitvoert, omdat dit de prestaties kan verminderen.

U kunt de verbindingsparameters opgeven, zoals het adres van de Redis-host en het wachtwoord. Als u Azure Cache voor Redis gebruikt, is het wachtwoord de primaire of secundaire sleutel die wordt gegenereerd voor Azure Cache voor Redis met behulp van Azure Portal.

Nadat u verbinding hebt gemaakt met de Redis-server, kunt u een ingang op de Redis-database verkrijgen die als de cache fungeert. De Redis-verbinding bevat de GetDatabase-methode om dit te doen. Vervolgens kunt u items uit de cache ophalen en gegevens opslaan in de cache met behulp van de StringGet- en StringSet-methoden. Bij deze methoden wordt een sleutel als parameter verwacht en wordt het item geretourneerd naar de cache met een overeenkomende waarde (StringGet), of het item wordt toegevoegd aan de cache met deze sleutel (StringSet).

Afhankelijk van de locatie van de Redis-server kan voor veel bewerkingen enige vertraging optreden als een aanvraag naar de server wordt verzonden en een antwoord naar de client wordt geretourneerd. De StackExchange-bibliotheek biedt asynchrone versies van veel van deze methoden, die worden gepresenteerd zodat clienttoepassingen responsief blijven. Deze methoden ondersteunen het Asynchrone patroon op basis van taken in .NET Framework.

Het volgende codefragment bevat een methode met de naam RetrieveItem. Het illustreert een implementatie van het cache-aside-patroon op basis van Redis en de StackExchange-bibliotheek. De methode neemt een waarde van de tekenreekssleutel en probeert het bijbehorende item uit de Redis-cache op te halen door het aanroepen van de methode StringGetAsync (de asynchrone versie van StringGet).

Als het item niet wordt gevonden, wordt het opgehaald uit de onderliggende gegevensbron met behulp van de GetItemFromDataSourceAsync methode (dit is een lokale methode en geen onderdeel van de StackExchange-bibliotheek). Het item wordt vervolgens toegevoegd aan de cache via de methode StringSetAsync, zodat het de volgende keer sneller kan worden opgehaald.

// Connect to the Azure Redis cache
ConfigurationOptions config = new ConfigurationOptions();
config.EndPoints.Add("<your DNS name>.redis.cache.windows.net");
config.Password = "<Redis cache key from management portal>";
ConnectionMultiplexer redisHostConnection = ConnectionMultiplexer.Connect(config);
IDatabase cache = redisHostConnection.GetDatabase();
...
private async Task<string> RetrieveItem(string itemKey)
{
    // Attempt to retrieve the item from the Redis cache
    string itemValue = await cache.StringGetAsync(itemKey);

    // If the value returned is null, the item was not found in the cache
    // So retrieve the item from the data source and add it to the cache
    if (itemValue == null)
    {
        itemValue = await GetItemFromDataSourceAsync(itemKey);
        await cache.StringSetAsync(itemKey, itemValue);
    }

    // Return the item
    return itemValue;
}

De StringGet methoden en StringSet methoden zijn niet beperkt tot het ophalen of opslaan van tekenreekswaarden. Elk teken dat is geserialiseerd als een bytematrix kan er door worden gebruikt. Als u een .NET-object wilt opslaan, kunt u het als een bytestream serialiseren en de methode StringSet gebruiken om het naar de cache te schrijven.

U kunt ook een object in de cache lezen door de methode StringGet te gebruiken en het object te deserialiseren als een .NET-object. De volgende code toont een set uitbreidingsmethoden voor de IDatabase-interface (de methode GetDatabase van een Redis-verbinding retourneert een IDatabase-object), en voorbeeldcode die gebruikmaakt van deze methoden om een BlogPost-object te lezen en naar de cache te schrijven:

public static class RedisCacheExtensions
{
    public static async Task<T> GetAsync<T>(this IDatabase cache, string key)
    {
        return Deserialize<T>(await cache.StringGetAsync(key));
    }

    public static async Task<object> GetAsync(this IDatabase cache, string key)
    {
        return Deserialize<object>(await cache.StringGetAsync(key));
    }

    public static async Task SetAsync(this IDatabase cache, string key, object value)
    {
        await cache.StringSetAsync(key, Serialize(value));
    }

    static byte[] Serialize(object o)
    {
        byte[] objectDataAsStream = null;

        if (o != null)
        {
            var jsonString = JsonSerializer.Serialize(o);
            objectDataAsStream = Encoding.ASCII.GetBytes(jsonString);
        }

        return objectDataAsStream;
    }

    static T Deserialize<T>(byte[] stream)
    {
        T result = default(T);

        if (stream != null)
        {
            var jsonString = Encoding.ASCII.GetString(stream);
            result = JsonSerializer.Deserialize<T>(jsonString);
        }

        return result;
    }
}

De volgende code illustreert een methode met de naam RetrieveBlogPost, die gebruikmaakt van deze uitbreidingsmethoden om een serialiseerbaar BlogPost-object te lezen en naar de cache te schrijven aan de hand van het cache-aside-patroon:

// The BlogPost type
public class BlogPost
{
    private HashSet<string> tags;

    public BlogPost(int id, string title, int score, IEnumerable<string> tags)
    {
        this.Id = id;
        this.Title = title;
        this.Score = score;
        this.tags = new HashSet<string>(tags);
    }

    public int Id { get; set; }
    public string Title { get; set; }
    public int Score { get; set; }
    public ICollection<string> Tags => this.tags;
}
...
private async Task<BlogPost> RetrieveBlogPost(string blogPostKey)
{
    BlogPost blogPost = await cache.GetAsync<BlogPost>(blogPostKey);
    if (blogPost == null)
    {
        blogPost = await GetBlogPostFromDataSourceAsync(blogPostKey);
        await cache.SetAsync(blogPostKey, blogPost);
    }

    return blogPost;
}

Redis ondersteunt command pipelining als een clienttoepassing meerdere asynchrone aanvragen verzendt. Redis kan de aanvragen multiplexen door dezelfde verbinding te gebruiken in plaats van in een strikte volgorde opdrachten te ontvangen en erop te reageren.

Hiermee wordt de latentie verminderd door efficiënter gebruik te maken van het netwerk. Het volgende codefragment toont een voorbeeld waarmee de gegevens van twee klanten gelijktijdig worden opgehaald. De code doet twee aanvragen en voert een ander proces uit (niet weergegeven) voordat het op de resultaten wacht. De methode Wait van het cacheobject is soortgelijk aan de .NET Framework-methode Task.Wait:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
var task1 = cache.StringGetAsync("customer:1");
var task2 = cache.StringGetAsync("customer:2");
...
var customer1 = cache.Wait(task1);
var customer2 = cache.Wait(task2);

Zie de Azure Cache voor Redis documentatie voor meer informatie over het schrijven van clienttoepassingen die de Azure Cache voor Redis kunnen gebruiken. Op StackExchange.Redis is meer informatie beschikbaar.

Op de pagina Pipelines and Multiplexers (Pijplijnen en multiplexers) op dezelfde website vindt u meer informatie over asynchrone bewerkingen en pipelining met Redis en de StackExchange-bibliotheek.

De Redis-cache gebruiken

Bij het eenvoudigste gebruik van Redis voor caching wordt gebruikgemaakt van sleutel-/waardeparen, waarbij de waarde een niet-geïnterpreteerde tekenreeks van willekeurige lengte is die binaire gegevens kan bevatten. (Het is in feite een matrix van bytes die als een tekenreeks kunnen worden behandeld). Dit scenario is geïllustreerd in de sectie Clienttoepassingen met Redis-cache implementeren eerder in dit artikel.

Houd er rekening mee dat sleutels ook niet-geïnterpreteerde gegevens bevatten, zodat u binaire gegevens als de sleutel kunt gebruiken. Hoe langer de sleutel, hoe meer opslagruimte het kost en hoe langer het duurt om opzoekbewerkingen uit te voeren. Ontwerp vanwege het gebruiks- en onderhoudsgemak uw keyspace zorgvuldig en gebruik zinvolle (maar geen wijdlopige) sleutels.

Gebruik bijvoorbeeld gestructureerde sleutels, zoals 'klant: 100', als sleutel voor een klant met id 100 in plaats van gewoon '100'. In dit schema kunt u eenvoudig onderscheid maken tussen waarden waarin verschillende gegevenstypen worden opgeslagen. U kunt bijvoorbeeld ook de sleutel 'orders:100' gebruiken als sleutel voor een order met id 100.

Afgezien van eendimensionale, binaire tekenreeksen, kan een waarde in een Redis-sleutel-/waardepaar ook meer gestructureerde gegevens bevatten, waaronder lijsten, sets (gesorteerd en ongesorteerd) en hashes. Redis biedt een uitgebreide opdrachtenset die met deze typen kan omgaan. Veel van deze opdrachten zijn beschikbaar voor .NET Framework-toepassingen via een clientbibliotheek als StackExchange. De pagina An introduction to Redis data types and abstractions (Inleiding tot Redis-gegevenstypen en -abstracties) op de Redis-website biedt een meer gedetailleerd overzicht van deze typen en de opdrachten die u kunt gebruiken om ze te manipuleren.

In deze sectie wordt een overzicht gegeven van enkele veelvoorkomende use cases voor deze gegevenstypen en opdrachten.

Atomische en batchbewerkingen uitvoeren

Redis ondersteunt een aantal atomische get- en set-bewerkingen op tekenreekswaarden. Dankzij deze bewerkingen worden de mogelijke racevoorwaarden geëlimineerd die kunnen optreden bij gebruik van de afzonderlijke opdrachten GET en SET. De beschikbare bewerkingen zij onder meer:

  • INCR, INCRBY, DECR en DECRBY, die atomische verhogings- verlagingsbewerking uitvoeren op geheeltallige numerieke gegevenswaarden. De StackExchange-bibliotheek biedt overbelaste versies van de methoden IDatabase.StringIncrementAsync en IDatabase.StringDecrementAsync voor het uitvoeren van deze bewerkingen en het retourneren van de resulterende waarde die is opgeslagen in de cache. Het volgende codefragment toont hoe u deze methoden kunt gebruiken:

    ConnectionMultiplexer redisHostConnection = ...;
    IDatabase cache = redisHostConnection.GetDatabase();
    ...
    await cache.StringSetAsync("data:counter", 99);
    ...
    long oldValue = await cache.StringIncrementAsync("data:counter");
    // Increment by 1 (the default)
    // oldValue should be 100
    
    long newValue = await cache.StringDecrementAsync("data:counter", 50);
    // Decrement by 50
    // newValue should be 50
    
  • GETSET: hiermee wordt de waarde die is gekoppeld aan een sleutel, opgehaald en gewijzigd in een nieuwe waarde. De StackExchange-bibliotheek maakt deze bewerking beschikbaar via de methode IDatabase.StringGetSetAsync. Het onderstaande codefragment toont een voorbeeld van deze methode. Deze code retourneert de huidige waarde die is gekoppeld aan de sleutel 'data:counter' in het vorige voorbeeld. Vervolgens wordt de waarde voor deze sleutel op nieuw ingesteld op nul, dit alles als onderdeel van dezelfde bewerking:

    ConnectionMultiplexer redisHostConnection = ...;
    IDatabase cache = redisHostConnection.GetDatabase();
    ...
    string oldValue = await cache.StringGetSetAsync("data:counter", 0);
    
  • MGET en MSET: hiermee kan een set tekenreekswaarden als één bewerking worden geretourneerd of gewijzigd. De methoden IDatabase.StringGetAsync en IDatabase.StringSetAsync worden overbelast om deze functionaliteit te kunnen ondersteunen, zoals u in het volgende voorbeeld kunt zien:

    ConnectionMultiplexer redisHostConnection = ...;
    IDatabase cache = redisHostConnection.GetDatabase();
    ...
    // Create a list of key-value pairs
    var keysAndValues =
        new List<KeyValuePair<RedisKey, RedisValue>>()
        {
            new KeyValuePair<RedisKey, RedisValue>("data:key1", "value1"),
            new KeyValuePair<RedisKey, RedisValue>("data:key99", "value2"),
            new KeyValuePair<RedisKey, RedisValue>("data:key322", "value3")
        };
    
    // Store the list of key-value pairs in the cache
    cache.StringSet(keysAndValues.ToArray());
    ...
    // Find all values that match a list of keys
    RedisKey[] keys = { "data:key1", "data:key99", "data:key322"};
    // values should contain { "value1", "value2", "value3" }
    RedisValue[] values = cache.StringGet(keys);
    
    

U kunt ook meerdere bewerkingen in één Redis-transactie combineren, zoals beschreven in de sectie Transacties en batches met Redis eerder in dit artikel. De StackExchange-bibliotheek biedt ondersteuning voor transacties via de interface ITransaction.

U maakt een ITransaction-object met behulp van de methode IDatabase.CreateTransaction. U roept opdrachten voor de transactie aan via de methoden die door het ITransaction-object worden aangedragen.

De interface ITransaction biedt toegang tot een aantal methoden die vergelijkbaar zijn met degene die door de interface IDatabase worden geopend, behalve dat alle methoden asynchroon zijn. Dit betekent dat ze alleen worden uitgevoerd wanneer de ITransaction.Execute methode wordt aangeroepen. De waarde die wordt geretourneerd door de methode ITransaction.Execute geeft aan of de transactie is gemaakt (waar) of niet (onwaar).

Het volgende codefragment toont een voorbeeld waarin twee tellers worden verhoogd of verlaagd als onderdeel van dezelfde transactie:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
ITransaction transaction = cache.CreateTransaction();
var tx1 = transaction.StringIncrementAsync("data:counter1");
var tx2 = transaction.StringDecrementAsync("data:counter2");
bool result = transaction.Execute();
Console.WriteLine("Transaction {0}", result ? "succeeded" : "failed");
Console.WriteLine("Result of increment: {0}", tx1.Result);
Console.WriteLine("Result of decrement: {0}", tx2.Result);

Denk eraan dat Redis-transacties in relationele databases niet hetzelfde zijn als transacties. Met de methode Execute worden alle opdrachten die bestaan uit de uit te voeren transactie, in de wachtrij gezet, en de transactie wordt gestopt als een van de opdrachten is niet goed is ingedeeld. Als alle opdrachten in de wachtrij zijn geplaatst, wordt elke opdracht asynchroon uitgevoerd.

Als een opdracht mislukt, worden de andere gewoon verwerkt. Als u wilt controleren of een opdracht is voltooid, moet u de resultaten van de opdracht ophalen met behulp van de eigenschap Result van de bijbehorende taak, zoals weergegeven in het bovenstaande voorbeeld. Als de eigenschap Result wordt gelezen, wordt de aanroepende thread geblokkeerd totdat de taak is voltooid.

Zie Transacties in Redis voor meer informatie.

Bij het uitvoeren van batchbewerkingen kunt u de interface IBatch van de StackExchange-bibliotheek gebruiken. De interface biedt toegang tot een aantal methoden die vergelijkbaar zijn met degene die door de interface IDatabase worden geopend, behalve dat alle methoden asynchroon zijn.

U maakt een IBatch-object met behulp van de methode IDatabase.CreateBatch en voert vervolgens de batch uit met behulp van de methode IBatch.Execute, zoals weergegeven in het volgende voorbeeld. Deze code stelt gewoon een tekenreekswaarde in, verhoogt of verlaagt dezelfde tellers als in het vorige voorbeeld en geeft de resultaten weer:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
IBatch batch = cache.CreateBatch();
batch.StringSetAsync("data:key1", 11);
var t1 = batch.StringIncrementAsync("data:counter1");
var t2 = batch.StringDecrementAsync("data:counter2");
batch.Execute();
Console.WriteLine("{0}", t1.Result);
Console.WriteLine("{0}", t2.Result);

Het is belangrijk om te begrijpen dat, in tegenstelling tot een transactie, als een opdracht in een batch mislukt omdat deze onjuist is, de andere opdrachten mogelijk nog steeds worden uitgevoerd. De IBatch.Execute methode retourneert geen indicatie van slagen of mislukken.

Fire- en Forget-cachebewerkingen uitvoeren

Redis ondersteunt Fire- en Forget-bewerkingen door gebruik te maken van opdrachtvlaggen. In deze situatie start de client gewoon een bewerking, maar heeft geen interesse in het resultaat en wacht niet totdat de opdracht is voltooid. In het volgende voorbeeld ziet u hoe de opdracht INCR wordt uitgevoerd als een Fire- en Forget-bewerking:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
await cache.StringSetAsync("data:key1", 99);
...
cache.StringIncrement("data:key1", flags: CommandFlags.FireAndForget);

Automatisch verlopende sleutels opgeven

Wanneer u een item in een Redis-cache opslaat, kunt u een time-out opgeven, waarna het item automatisch uit de cache wordt verwijderd. U kunt met de opdracht TTL ook een query uitvoeren om erachter te komen hoeveel meer tijd een sleutel heeft voordat deze verloopt. Deze opdracht is beschikbaar voor StackExchange-toepassingen via de methode IDatabase.KeyTimeToLive.

Het volgende codefragment toont hoe een verlooptijd van 20 seconden voor een sleutel wordt ingesteld en hoe er een query wordt uitgevoerd naar de resterende levensduur van de sleutel:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
// Add a key with an expiration time of 20 seconds
await cache.StringSetAsync("data:key1", 99, TimeSpan.FromSeconds(20));
...
// Query how much time a key has left to live
// If the key has already expired, the KeyTimeToLive function returns a null
TimeSpan? expiry = cache.KeyTimeToLive("data:key1");

U kunt ook een verlooptijd op een specifieke datum en tijd instellen met de opdracht EXPIRE. Deze is beschikbaar in de StackExchange-bibliotheek als de methode KeyExpireAsync:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
// Add a key with an expiration date of midnight on 1st January 2015
await cache.StringSetAsync("data:key1", 99);
await cache.KeyExpireAsync("data:key1",
    new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc));
...

Tip

U kunt een item handmatig uit de cache verwijderen met de opdracht DEL. Deze is beschikbaar in de StackExchange-bibliotheek als de methode IDatabase.KeyDeleteAsync.

Tags gebruiken om items in de cache kruislings te correleren

Een Redis-set is een verzameling van meerdere items die één sleutel delen. U kunt een set maken met de opdracht SADD. U kunt de items in een set ophalen met de opdracht SMEMBERS. De StackExchange-bibliotheek implementeert de opdracht SADD met de methode IDatabase.SetAddAsync en de opdracht SMEMBERS met de methode IDatabase.SetMembersAsync.

U kunt ook bestaande sets combineren om nieuwe sets maken met de opdrachten SDIFF (set difference; verschil van de sets), SINTER (set intersection; doorsnede van de sets) en SUNION (set union; vereniging van de sets). De StackExchange-bibliotheek verenigt deze bewerkingen met de methode IDatabase.SetCombineAsync. De eerste parameter voor deze methode bepaalt de set-bewerking die moet worden uitgevoerd.

De volgende codefragmenten laten de voordelen van sets zien bij het snel opslaan en ophalen van verzamelingen verwante items. Deze code gebruikt het type BlogPost, dat is beschreven in de sectie Clienttoepassingen met Redis-cache implementeren eerder in dit artikel.

Een BlogPost-object bevat vier velden: een id, een titel, een rangorde en een verzameling tags. Het eerste codefragment hieronder bevat de voorbeeldgegevens die wordt gebruikt voor het vullen van een C#-lijst met BlogPost-objecten:

List<string[]> tags = new List<string[]>
{
    new[] { "iot","csharp" },
    new[] { "iot","azure","csharp" },
    new[] { "csharp","git","big data" },
    new[] { "iot","git","database" },
    new[] { "database","git" },
    new[] { "csharp","database" },
    new[] { "iot" },
    new[] { "iot","database","git" },
    new[] { "azure","database","big data","git","csharp" },
    new[] { "azure" }
};

List<BlogPost> posts = new List<BlogPost>();
int blogKey = 0;
int numberOfPosts = 20;
Random random = new Random();
for (int i = 0; i < numberOfPosts; i++)
{
    blogKey++;
    posts.Add(new BlogPost(
        blogKey,                  // Blog post ID
        string.Format(CultureInfo.InvariantCulture, "Blog Post #{0}",
            blogKey),             // Blog post title
        random.Next(100, 10000),  // Ranking score
        tags[i % tags.Count]));   // Tags--assigned from a collection
                                  // in the tags list
}

U kunt de tags voor elk BlogPost-object in een Redis-cache opslaan als een set en elke set koppelen aan de id van het BlogPost-object. Hierdoor kan een toepassing snel alle tags vinden die deel uitmaken van een specifieke blogpost. Als u in de tegengestelde richting wilt zoeken en alle blogposts wilt vinden die een specifieke tag delen, kunt u een andere set met blogposts maken waarvan de sleutel naar de tag-id verwijst:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
// Tags are easily represented as Redis Sets
foreach (BlogPost post in posts)
{
    string redisKey = string.Format(CultureInfo.InvariantCulture,
        "blog:posts:{0}:tags", post.Id);
    // Add tags to the blog post in Redis
    await cache.SetAddAsync(
        redisKey, post.Tags.Select(s => (RedisValue)s).ToArray());

    // Now do the inverse so we can figure out which blog posts have a given tag
    foreach (var tag in post.Tags)
    {
        await cache.SetAddAsync(string.Format(CultureInfo.InvariantCulture,
            "tag:{0}:blog:posts", tag), post.Id);
    }
}

Met deze structuren kunt u zeer efficiënt veel algemene query's uitvoeren. U kunt bijvoorbeeld alle tags voor blogbericht 1 als volgt vinden en weergeven:

// Show the tags for blog post #1
foreach (var value in await cache.SetMembersAsync("blog:posts:1:tags"))
{
    Console.WriteLine(value);
}

U kunt alle tags vinden die zowel in blogpost 1 als blogpost 2 voorkomen door een set-doorsnedebewerking (SINTER) uit te voeren. Dit gaat als volgt:

// Show the tags in common for blog posts #1 and #2
foreach (var value in await cache.SetCombineAsync(SetOperation.Intersect, new RedisKey[]
    { "blog:posts:1:tags", "blog:posts:2:tags" }))
{
    Console.WriteLine(value);
}

U kunt ook alle blogposts vinden die een bepaalde tag bevatten:

// Show the ids of the blog posts that have the tag "iot".
foreach (var value in await cache.SetMembersAsync("tag:iot:blog:posts"))
{
    Console.WriteLine(value);
}

Recentelijk geopende items zoeken

Een algemene taak die door vele toepassingen wordt vereist, bestaat uit het zoeken naar de laatst gebruikte items. Bijvoorbeeld: op een site met een blog moet informatie worden weergegeven over de laatst gelezen blogposts.

U kunt deze functionaliteit implementeren met behulp van een Redis-lijst. Een Redis-lijst bevat meerdere items met dezelfde sleutel. De lijst fungeert als een tweezijdige wachtrij. U kunt items naar beide zijden van de lijst pushen met de opdrachten LPUSH (linker push) en RPUSH (rechter push). U kunt items aan beide zijden van de lijst ophalen met de opdrachten LPOP en RPOP. U kunt ook een set elementen retourneren met de opdrachten LRANGE en RRANGE.

De onderstaande codefragmenten laten zien hoe u deze bewerkingen kunt uitvoeren met behulp van de StackExchange-bibliotheek. Deze code gebruikt het type BlogPost uit de eerdere voorbeelden. Als een blogpost door een gebruiker wordt gelezen, pusht de methode IDatabase.ListLeftPushAsync de titel van de blogpost naar een lijst die is gekoppeld aan de sleutel 'blog:recent_posts' in de Redis-cache.

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
string redisKey = "blog:recent_posts";
BlogPost blogPost = ...; // Reference to the blog post that has just been read
await cache.ListLeftPushAsync(
    redisKey, blogPost.Title); // Push the blog post onto the list

Als er meer blogposts worden gelezen, worden de titels naar dezelfde lijst gepusht. De lijst wordt gesorteerd op de volgorde waarin de titels zijn toegevoegd. De meest recent gelezen blogberichten bevinden zich aan de linkerkant van de lijst. (Als dezelfde blogpost vaker wordt gelezen, bevat de lijst hiervoor meerdere vermeldingen.)

U kunt de titels van de laatst gelezen posts weergeven met de methode IDatabase.ListRange. Deze methode gebruikt de sleutel die de lijst, een beginpunt en een eindpunt bevat. Met de volgende code worden de titels van de tien blogberichten (items 0 tot en met 9) uiterst links in de lijst opgehaald:

// Show latest ten posts
foreach (string postTitle in await cache.ListRangeAsync(redisKey, 0, 9))
{
    Console.WriteLine(postTitle);
}

Houd er rekening mee dat met de ListRangeAsync methode geen items uit de lijst worden verwijderd. Als u dit wilt doen, gebruikt u de methoden IDatabase.ListLeftPopAsync en IDatabase.ListRightPopAsync.

Om te voorkomen dat de lijst voor onbeperkt toeneemt, kunt u items verwijderen door de lijst regelmatig in te korten. Het onderstaande codefragment toont hoe u alle items uit de lijst kunt verwijderen, behalve de vijf uiterst links:

await cache.ListTrimAsync(redisKey, 0, 5);

Een ranglijst implementeren

Standaard worden de items in een set niet in een specifieke volgorde bewaard. U kunt een geordende set maken met de opdracht ZADD (de methode IDatabase.SortedSetAdd in de StackExchange-bibliotheek). De items worden gerangschikt met behulp van een numerieke waarde, de zogenaamde score, die als parameter aan de opdracht wordt opgegeven.

Met het volgende codefragment wordt de titel van een blogpost aan een geordende lijst toegevoegd. In dit voorbeeld heeft elke blogpost ook een scoreveld met de positie van de blogpost.

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
string redisKey = "blog:post_rankings";
BlogPost blogPost = ...; // Reference to a blog post that has just been rated
await cache.SortedSetAddAsync(redisKey, blogPost.Title, blogPost.Score);

U kunt de titels en scores van de blogpost ophalen in de volgorde van oplopende score met de methode IDatabase.SortedSetRangeByRankWithScores:

foreach (var post in await cache.SortedSetRangeByRankWithScoresAsync(redisKey))
{
    Console.WriteLine(post);
}

Notitie

De StackExchange-bibliotheek biedt ook de IDatabase.SortedSetRangeByRankAsync methode die de gegevens in scorevolgorde retourneert, maar de scores worden niet geretourneerd.

U kunt ook items op aflopende volgorde van score ophalen en het aantal items dat wordt geretourneerd, beperken door extra parameters voor de methode IDatabase.SortedSetRangeByRankWithScoresAsync op te geven. In het volgende voorbeeld worden de titels en scores van de bovenste tien gerangschikte blogposts weergegeven:

foreach (var post in await cache.SortedSetRangeByRankWithScoresAsync(
                               redisKey, 0, 9, Order.Descending))
{
    Console.WriteLine(post);
}

In het volgende voorbeeld wordt de methode IDatabase.SortedSetRangeByScoreWithScoresAsync gebruikt. Deze kunt u gebruiken om het aantal items dat wordt geretourneerd, te beperken tot de items die binnen een bepaald scorebereik vallen:

// Blog posts with scores between 5000 and 100000
foreach (var post in await cache.SortedSetRangeByScoreWithScoresAsync(
                               redisKey, 5000, 100000))
{
    Console.WriteLine(post);
}

Berichten verzenden door middel van kanalen

Een Redis-server fungeert niet alleen als gegevenscache, maar u kunt er ook berichten mee verzenden via een geavanceerd mechanisme voor publiceren en verzenden. Clienttoepassingen kunnen zich abonneren op een kanaal en andere toepassingen of services kunnen berichten op het kanaal publiceren. Abonneertoepassingen ontvangen deze berichten en kunnen ze verwerken.

Redis biedt de opdracht SUBSCRIBE, waarmee door clienttoepassingen een abonnement op een kanaal kan worden genomen. Met deze opdracht wordt de naam van een of meer kanalen verwacht waarop de toepassing berichten accepteert. De StackExchange-bibliotheek bevat de interface ISubscription, waarmee een .NET Framework-toepassing een abonnement kan nemen op een kanaal en erop kan publiceren.

U maakt een ISubscription-object met behulp van de methode GetSubscriber van de verbinding met de Redis-server. Vervolgens luister u naar berichten op een kanaal met de methode SubscribeAsync van dit object. De volgende voorbeeldcode laat zien hoe het abonneren op een kanaal met de naam 'messages: blogPosts' in zijn werk gaat:

ConnectionMultiplexer redisHostConnection = ...;
ISubscriber subscriber = redisHostConnection.GetSubscriber();
...
await subscriber.SubscribeAsync("messages:blogPosts", (channel, message) => Console.WriteLine("Title is: {0}", message));

De eerste parameter voor de methode Subscribe is de naam van het kanaal. Deze naam volgt dezelfde conventies die worden gebruikt door sleutels in de cache. De naam kan binaire gegevens bevatten, maar we raden u aan relatief korte, zinvolle tekenreeksen te gebruiken om goede prestaties en onderhoudbaarheid te garanderen.

Merk ook op dat de naamruimte die door kanalen wordt gebruikt, gescheiden is van de naamruimte die door sleutels wordt gebruikt. Dit betekent dat er kanalen en sleutels kunnen zijn met dezelfde naam, hoewel hierdoor uw toepassingscode moeilijker is te onderhouden.

De tweede parameter is een gedelegeerde voor acties. Deze gedelegeerde wordt asynchroon uitgevoerd wanneer een nieuw bericht wordt weergegeven op het kanaal. In dit voorbeeld wordt het bericht gewoon op de console weergegeven (het bericht bevat de titel van een blogpost).

Als u op een kanaal wilt publiceren, kan de Redis-opdracht PUBLISH worden gebruikt. De StackExchange-bibliotheek bevat de methode IServer.PublishAsync voor deze bewerking. Het volgende codefragment toont hoe een bericht op het kanaal 'messages: blogPosts' wordt gepubliceerd:

ConnectionMultiplexer redisHostConnection = ...;
ISubscriber subscriber = redisHostConnection.GetSubscriber();
...
BlogPost blogPost = ...;
subscriber.PublishAsync("messages:blogPosts", blogPost.Title);

Er zijn enkele punten die u moet weten over het mechanisme voor publiceren/abonneren:

  • Meerdere abonnees kunnen zich abonneren op hetzelfde kanaal en ze ontvangen allemaal de berichten die naar dat kanaal zijn gepubliceerd.
  • Abonnees ontvangen alleen berichten die zijn gepubliceerd nadat ze zich hebben geabonneerd. Kanalen worden niet gebufferd en zodra een bericht is gepubliceerd, wordt het bericht door de Redis-infrastructuur naar elke abonnee gepusht en vervolgens verwijderd.
  • Berichten worden standaard ontvangen door abonnees in de volgorde waarin ze worden verzonden. In een zeer actief systeem met een groot aantal berichten en veel abonnees en uitgevers, kan gegarandeerde opeenvolgende levering van berichten de prestaties van het systeem vertragen. Als elk bericht onafhankelijk is en de volgorde niet belangrijk, kunt u gelijktijdige verwerking door het Redis-systeem inschakelen. Hierdoor kan het reactievermogen worden verbeterd. U kunt dit doen in een StackExchange-client door PreserveAsyncOrder van de verbinding die door de abonnee wordt gebruikt op 'onwaar' in te stellen:
ConnectionMultiplexer redisHostConnection = ...;
redisHostConnection.PreserveAsyncOrder = false;
ISubscriber subscriber = redisHostConnection.GetSubscriber();

Serialisatie

Als u een serialisatie-indeling kiest, houd u dan rekening met de balans tussen de prestaties, de interoperabiliteit, het versiebeheer, de compatibiliteit met bestaande systemen, de gegevenscompressie en geheugenoverhead. Wanneer u de prestaties evalueert, moet u er rekening mee houden dat benchmarks zeer afhankelijk zijn van context. Mogelijk komen deze niet overeen met de werkelijke werkbelasting en wordt geen rekening gehouden met nieuwe bibliotheken of versies. Er is geen 'snelste' serializer voor alle scenario's.

Enkele opties om te overwegen zijn onder meer:

  • Protocol Buffers (ook wel protobuf genoemd) zijn een serialisatie-indeling die zijn ontwikkeld door Google voor het efficiënt serialiseren van gestructureerde gegevens. Er worden sterk getypte definitiebestanden gebruikt om berichtstructuren te definiëren. Deze definitiebestanden worden vervolgens gecompileerd in taalspecifieke code voor het serialiseren en deserialiseren van berichten. Protobuf kan via bestaande RPC-mechanismen worden gebruikt, maar het kan ook een RPC-service genereren.

  • Apache Thrift maakt gebruik van een soortgelijke benadering met sterk getypeerde definitiebestanden en een compilatiestap voor het genereren van de serialisatiecode en RPC-services.

  • Apache Avro biedt vergelijkbare functionaliteit als ProtocolBuffers en Thrift, maar er is geen compilatiestap. Hierin bevatten geserialiseerde gegevens altijd een schema waarmee de structuur wordt beschreven.

  • JSON is een open standaard die gebruikmaakt van voor mensen leesbare tekstvelden. Het biedt brede, platformoverschrijdende ondersteuning. JSON maakt geen gebruik van berichtschema's. Als tekstopmaak is het niet erg efficiënt via de kabel. In sommige gevallen kunnen items in de cache rechtstreeks via HTTP naar een client worden geretourneerd. In dat geval kan het opslaan van JSON de kosten besparen van het deserialiseren vanuit een andere indeling en vervolgens serialiseren naar JSON.

  • BSON is een binaire serialisatie-indeling die gebruikmaakt van een structuur die vergelijkbaar is met JSON. BSON is zodanig ontworpen dat het in vergelijking met JSON snel en gemakkelijk te scannen is en snel te serialiseren en deserialiseren. Nettoladingen zijn vergelijkbaar in grootte met die van JSON. Afhankelijk van de gegevens kan een BSON-nettolading kleiner of groter zijn dan een JSON-nettolading. BSON heeft een aantal extra gegevenstypen die niet beschikbaar zijn in JSON, met name BinData (voor bytematrices) en Date.

  • MessagePack is een binaire serialisatie-indeling met een compact ontwerp voor verzending via de kabel. Er zijn geen berichtschema's of controles van berichttypen.

  • Bond is een platformoverschrijdend framework voor het werken met geschematiseerde gegevens. Het ondersteunt serialisatie en deserialisatie in meerdere talen. Opvallende verschillen ten opzichte van andere hier vermelde systemen zijn ondersteuning voor overname, typealiassen en algemene typen.

  • gRPC is een opensource RPC-systeem dat is ontwikkeld door Google. Standaard wordt gebruikgemaakt van Protocol Buffers als definitietaal en onderliggende indeling voor berichtenuitwisseling.

Volgende stappen

De volgende patronen zijn mogelijk ook relevant voor uw scenario wanneer u caching in uw toepassingen implementeert:

  • Cache-aside-patroon: dit patroon beschrijft hoe u gegevens op aanvraag vanuit een gegevensarchief in een cache laadt. Dit patroon bevordert ook het handhaven van de consistentie tussen gegevens in de cache en gegevens in het oorspronkelijke gegevensarchief.

  • Het sharding-patroon bevat informatie over het implementeren van horizontale partitionering om de schaalbaarheid te verbeteren bij het opslaan en openen van grote hoeveelheden gegevens.