Zwischenspeicherungsleitfaden

Zwischenspeichern ist eine gängige Methode zur Verbesserung der Leistung und Skalierbarkeit eines Systems. Das Zwischenspeichern kopiert vorübergehend häufig verwendete Daten in den Speicher, der sich näher an der Anwendung befindet als die ursprüngliche Quelle. Dieser Ansatz kann die Reaktionszeiten für Clientanwendungen erheblich verbessern, indem Daten schneller bereitgestellt werden.

Die Zwischenspeicherung ist am effektivsten, wenn eine Clientinstanz wiederholt dieselben Daten liest, insbesondere, wenn alle folgenden Bedingungen für den ursprünglichen Datenspeicher gelten:

  • Der Datenspeicher bleibt relativ statisch.
  • Er ist langsam verglichen mit der Geschwindigkeit des Caches.
  • Er unterliegt einem hohen Maß an Streitigkeiten.
  • Es ist weit genug von Clients entfernt, dass die Netzwerklatenz von Bedeutung ist.

Zwischenspeichern in verteilten Anwendungen

Verteilte Anwendungen implementieren in der Regel eine oder beide der folgenden Strategien beim Zwischenspeichern von Daten:

  • Verwenden Sie einen privaten Cache, in dem Sie Daten lokal auf dem Computer speichern, auf dem eine Anwendung oder ein Dienst ausgeführt wird.

  • Verwenden Sie einen freigegebenen Cache, der als gemeinsame Quelle dient, auf die mehrere Prozesse und Computer zugreifen können.

In beiden Fällen können Sie zwischenspeichern auf clientseitiger und serverseitiger Seite ausführen. Der Prozess, der die Benutzeroberfläche für ein System bereitstellt, z. B. einen Webbrowser oder eine Desktopanwendung, behandelt die clientseitige Zwischenspeicherung. Serverseitige Zwischenspeicherung erfolgt remote durch den Prozess, der die Geschäftsdienste bereitstellt.

Private Zwischenspeicherung

Der grundlegendste Cachetyp ist ein Speicher im Arbeitsspeicher. Er befindet sich im Adressbereich eines einzelnen Prozesses, und der Code, der in diesem Prozess ausgeführt wird, greift direkt auf den Cache zu. Dieser Cachetyp kann schnell zugegriffen werden. Sie kann auch ein effektives Mittel zum Speichern bescheidener Mengen statischer Daten bieten. Die auf dem Computer verfügbare Arbeitsspeichermenge schränkt in der Regel die Größe eines Caches ein.

Wenn Sie mehr Informationen zwischenspeichern müssen, als physisch im Arbeitsspeicher möglich sind, können Sie zwischengespeicherte Daten in das lokale Dateisystem schreiben. Der Zugriff auf Daten aus dem Dateisystem dauert länger, als wenn die Daten im Arbeitsspeicher gespeichert sind, aber es sollte immer noch schneller und zuverlässiger sein, als Daten in einem Netzwerk abzurufen.

Wenn Sie über mehrere Instanzen einer Anwendung verfügen, die dieses Modell gleichzeitig verwendet, verfügt jede Anwendungsinstanz über einen eigenen unabhängigen Cache, der eine eigene Kopie der Daten enthält.

Stellen Sie sich einen Cache als Momentaufnahme der ursprünglichen Daten in der Vergangenheit vor. Wenn diese Daten nicht statisch sind, ist es wahrscheinlich, dass unterschiedliche Anwendungsinstanzen unterschiedliche Versionen der Daten in ihren Caches enthalten. Daher kann dieselbe Abfrage, die von diesen Instanzen ausgeführt wird, unterschiedliche Ergebnisse zurückgeben, wie im folgenden Diagramm dargestellt.

Diagramm, das die Ergebnisse der Verwendung eines Speichercaches in verschiedenen Instanzen einer Anwendung zeigt.

Diagramm mit Zwischenspeicherinkonsistenz in mehreren Anwendungsinstanzen, die mit einer freigegebenen SQL-Datenbank verbunden sind. Rechts befindet sich eine SQL-Datenbank, die als Zylinder dargestellt wird. Oben links befindet sich ein großer Kreis mit der Bezeichnung Anwendungsinstanz A, und unten links befindet sich ein ähnlich großer Kreis mit der Bezeichnung Anwendungsinstanz B. In der Anwendungsinstanz A befindet sich ein Zahnradsymbol, das den Anwendungsprozess darstellt. Unterhalb des Zahnradsymbols befindet sich eine Rastertabelle, die einen Speichercache darstellt. Außerhalb der Begrenzung befindet sich eine Beschriftung mit dem Text „Der Cache ist eine Momentaufnahme der Daten zum Zeitpunkt X“. In der Anwendungsinstanz B befindet sich ein weiteres Zahnradsymbol. Unterhalb des Zahnradsymbols befindet sich eine Rastertabelle, die einen Speichercache darstellt. Außerhalb der Begrenzung befindet sich eine Beschriftung mit dem Text: „Der Cache ist eine Momentaufnahme der Daten zum Zeitpunkt Y.“ Von der SQL-Datenbank verläuft eine Linie zu Anwendungsinstanz A, mit der Beschriftung: „Anwendungsinstanz A ruft die Daten zum Zeitpunkt X ab und zwischenspeichert sie im Arbeitsspeicher.“ Eine zweite Zeile erstreckt sich von der SQL-Datenbank in Richtung Anwendungsinstanz B, mit der Beschriftung „Anwendungsinstanz B ruft Daten zum Zeitpunkt Y ab und speichert sie im Speicher zwischen.“ Zwischen diesen beiden Anmerkungszeilen befindet sich eine Notiz, dass sich die Informationen in der Datenbank zwischen Zeitpunkt X und Zeitpunkt Y ändern.

Gemeinsames Caching

Mithilfe eines freigegebenen Caches können Sie sicherstellen, dass alle Anwendungsinstanzen dieselbe Ansicht der zwischengespeicherten Daten sehen. Der Cache befindet sich an einem separaten Speicherort, der in der Regel als Teil eines separaten Diensts gehostet wird, wie im folgenden Diagramm dargestellt.

Diagramm, das die Ergebnisse von Anwendungen mit einem freigegebenen Cache zeigt.

Diagramm, das zeigt, wie ein gemeinsam genutzter Cache-Dienst Cache-Inkonsistenzen bei mehreren Anwendungsinstanzen auflöst, die mit einer SQL-Datenbank verbunden sind. Ganz rechts ist eine SQL-Datenbank, die als Zylinder dargestellt wird. In der Mitte befindet sich ein Kasten mit der Beschriftung „Shared Cache Service“, der eine Gittertabelle enthält, die den gemeinsam genutzten Cache darstellt. Ganz links befindet sich ein Zahnradsymbol mit der Bezeichnung Anwendungsinstanz A. Darunter befindet sich ein zweites Zahnradsymbol mit der Bezeichnung Anwendungsinstanz B. Ein schwarzer Pfeil zeigt von der SQL-Datenbank auf das Raster des freigegebenen Cachediensts, der angibt, dass die Datenbank den freigegebenen Cache auffüllt. Vom gemeinsam genutzten Cache-Dienst gehen zwei blaue Pfeile nach außen, einer in Richtung Anwendungsinstanz A und einer in Richtung Anwendungsinstanz B. Ganz links erstreckt sich vertikal eine geschwungene Klammer zwischen den beiden Anwendungsinstanzen; daneben steht die Beschriftung „Beide Anwendungsinstanzen greifen auf dieselben zwischengespeicherten Daten zu.“ Diese Beschriftung und Klammer heben den Hauptvorteil der Architektur mit gemeinsam genutztem Cache hervor: Im Gegensatz zu einem In-Memory-Cache pro Instanz rufen sowohl die Anwendungsinstanzen A als auch B Daten aus demselben zentralen Cache ab.

Ein wichtiger Vorteil des gemeinsamen Caching-Ansatzes ist skalierbarkeit. Viele gemeinsam genutzte Cachedienste werden mithilfe eines Clusters von Servern implementiert und verwenden Software, um die Daten transparent über den Cluster zu verteilen. Eine Anwendungsinstanz sendet eine Anforderung an den Cachedienst. Die zugrunde liegende Infrastruktur bestimmt den Speicherort der zwischengespeicherten Daten im Cluster. Sie können den Cache ganz einfach skalieren, indem Sie weitere Server hinzufügen.

Es gibt zwei Hauptnachteile des Gemeinsamen Caching-Ansatzes:

  • Der Cache ist langsamer für den Zugriff, da er nicht mehr lokal mit jeder Anwendungsinstanz gespeichert wird.
  • Durch die Implementierung eines separaten Cachediensts kann die Lösung komplexer werden.

Überlegungen zur Verwendung der Zwischenspeicherung

In den folgenden Abschnitten werden Überlegungen zum Entwerfen und Verwenden eines Caches beschrieben.

Entscheiden, wann Daten zwischengespeichert werden sollen

Zwischenspeichern kann die Leistung, Skalierbarkeit und Verfügbarkeit erheblich verbessern. Je mehr Daten Sie haben und je größer die Anzahl der Benutzer ist, die auf diese Daten zugreifen müssen, desto größer sind die Vorteile des Zwischenspeicherns. Die Zwischenspeicherung reduziert die Latenz und Konflikte, die mit der Verarbeitung großer Mengen an zeitgleichen Anforderungen im ursprünglichen Datenspeicher verbunden sind.

Beispielsweise kann eine Datenbank eine begrenzte Anzahl gleichzeitiger Verbindungen unterstützen. Durch das Abrufen von Daten aus einem freigegebenen Cache und nicht aus der zugrunde liegenden Datenbank ist es jedoch möglich, dass eine Clientanwendung auf diese Daten zugreifen kann, auch wenn die Anzahl der verfügbaren Verbindungen zurzeit erschöpft ist. Wenn die Datenbank nicht verfügbar ist, können Clientanwendungen möglicherweise weiterhin die Daten im Cache verwenden.

Erwägen Sie das Zwischenspeichern von Daten, die häufig gelesen, aber selten geändert werden. Speichern Sie z. B. Daten, die einen höheren Anteil an Lesevorgängen aufweisen, als Schreibvorgänge. Verwenden Sie den Cache jedoch nicht als maßgebliche Quelle für kritische Informationen. Speichern Sie stattdessen alle wichtigen Änderungen in einem beständigen Datenspeicher. Wenn der Cache nicht verfügbar ist, kann Ihre Anwendung weiterhin mit dem Datenspeicher arbeiten, und Sie verlieren keine wichtigen Informationen.

Ermitteln, wie Daten effektiv zwischengespeichert werden

Um einen Cache effektiv zu verwenden, bestimmen Sie die am besten geeigneten Daten zum Zwischenspeichern, und speichern Sie ihn zur richtigen Zeit zwischen. Sie können dem Cache Daten hinzufügen, wenn eine Anwendung sie zum ersten Mal abruft. Die Anwendung muss die Daten nur einmal aus dem Datenspeicher abrufen, und anschließend kann der nachfolgende Zugriff mit dem Cache erfüllt werden.

Alternativ können Sie einen Cache teilweise oder vollständig mit Daten im Voraus auffüllen, in der Regel, wenn die Anwendung gestartet wird. Dieser Ansatz wird als Seeding bezeichnet. Es ist jedoch möglicherweise nicht ratsam, Seeding für einen großen Cache zu implementieren, da dieser Ansatz eine plötzliche, hohe Auslastung des ursprünglichen Datenspeichers beim Starten der Anwendung aufzwingen kann.

Analysieren Sie Verwendungsmuster, um zu entscheiden, ob sie einen Cache vollständig oder teilweise auffüllen möchten, und wählen Sie aus, welche Daten zwischengespeichert werden sollen. Sie können beispielsweise den Cache mit den statischen Daten des Benutzerprofils für Kunden vorab befüllen, die die Anwendung täglich verwenden, aber nicht für Kunden, die die Anwendung nur einmal pro Woche verwenden.

Das Zwischenspeichern funktioniert in der Regel gut mit Daten, die unveränderlich sind oder sich selten ändern. Beispiele umfassen Referenzinformationen wie Produkt- und Preisinformationen in einer E-Commerce-Anwendung oder geteilte statische Ressourcen, deren Erstellung kostspielig ist. Laden Sie einige oder alle diese Daten beim Start der Anwendung in den Cache, um den Bedarf an Ressourcen zu minimieren und die Leistung zu verbessern. Möglicherweise möchten Sie auch einen Hintergrundprozess verwenden, der die Referenzdaten im Cache regelmäßig aktualisiert, um sicherzustellen, dass es up-to-date ist. Oder der Hintergrundprozess kann den Cache aktualisieren, wenn sich die Referenzdaten ändern.

Das Zwischenspeichern ist für dynamische Daten weniger nützlich, obwohl es einige Ausnahmen gibt. Weitere Informationen finden Sie im Abschnitt "Hoch dynamische Daten zwischenspeichern " weiter unten in diesem Artikel. Wenn die ursprünglichen Daten regelmäßig geändert werden, werden die zwischengespeicherten Informationen schnell veraltet oder der Aufwand für die Synchronisierung des Caches mit dem ursprünglichen Datenspeicher reduziert die Effektivität der Zwischenspeicherung.

Ein Cache muss die vollständigen Daten für eine Entität nicht enthalten. Wenn z. B. ein Datenelement ein mehrwertiges Objekt darstellt, z. B. ein Bankkunde mit einem Namen, einer Adresse und einem Kontostand, bleiben einige dieser Elemente möglicherweise statisch, z. B. der Name und die Adresse. Andere Elemente, z. B. der Kontostand, sind möglicherweise dynamischer. In diesen Situationen kann es hilfreich sein, die statischen Teile der Daten zwischenzuspeichern und nur die verbleibenden Informationen abzurufen (oder zu berechnen), wenn erforderlich.

Führen Sie Leistungstests und Verwendungsanalysen durch, um festzustellen, ob das Vorabpopulieren oder das Laden des Caches bei Bedarf oder eine Kombination aus beiden geeignet ist. Stützen Sie die Entscheidung auf die Volatilität und das Nutzungsmuster der Daten. Die Cachenutzung und Die Leistungsanalyse sind in Anwendungen wichtig, die auf schwere Lasten stoßen und hoch skalierbar sein müssen. Beispielsweise können Sie in hoch skalierbaren Szenarien den Cache seeden, um die Auslastung des Datenspeichers zu Spitzenzeiten zu reduzieren.

Zwischenspeichern kann auch verwendet werden, um wiederholte Berechnungen zu vermeiden, während die Anwendung ausgeführt wird. Wenn ein Vorgang Daten transformiert oder eine komplizierte Berechnung durchführt, kann er die Ergebnisse des Vorgangs im Cache speichern. Wenn danach dieselbe Berechnung erforderlich ist, kann die Anwendung die Ergebnisse aus dem Cache abrufen.

Eine Anwendung kann Daten ändern, die sich in einem Cache befinden. Denken Sie jedoch an den Cache als vorübergehenden Datenspeicher, der jederzeit verschwinden könnte. Speichern Sie keine wertvollen Daten nur im Cache. Stellen Sie sicher, dass Sie auch die Informationen im ursprünglichen Datenspeicher beibehalten. Dieser Ansatz minimiert die Wahrscheinlichkeit, dass Daten verloren gehen, wenn der Cache nicht mehr verfügbar ist.

Hoch dynamische Daten zwischenspeichern

Das Speichern von schnell sich ändernden Informationen in einem beständigen Datenspeicher kann einen Aufwand für das System verursachen. Betrachten Sie z. B. ein Gerät, das kontinuierlich den Status oder eine andere Messung meldet. Wenn eine Anwendung diese Daten nicht zwischenspeichert, da die zwischengespeicherten Informationen in der Regel veraltet sind, kann die gleiche Überlegung beim Speichern und Abrufen dieser Informationen aus dem Datenspeicher zutreffen. In der Zeit, die zum Speichern und Abrufen dieser Daten benötigt wird, kann sich dies ändern.

Berücksichtigen Sie in einer solchen Situation die Vorteile, die dynamischen Informationen direkt im Cache statt im permanenten Datenspeicher zu speichern. Wenn die Daten nicht kritisch sind und keine Überwachung erfordern, spielt es keine Rolle, ob die gelegentliche Änderung verloren geht.

Verwalten des Datenablaufs in einem Cache

In den meisten Fällen enthält ein Cache Daten, die eine Kopie von Daten aus dem ursprünglichen Datenspeicher sind. Die Daten im ursprünglichen Datenspeicher können sich nach dem Zwischenspeicher ändern, wodurch die zwischengespeicherten Daten veraltet werden können. Mit vielen Zwischenspeicherungssystemen können Sie den Cache so konfigurieren, dass Daten ablaufen, wodurch der Zeitraum reduziert wird, in dem Daten möglicherweise nicht mehr aktuell sind.

Nachdem zwischengespeicherte Daten abgelaufen sind, wird der Cache entfernt. Die Anwendung ruft dann neue Daten aus dem ursprünglichen Datenspeicher ab und kann die abgelaufenen Daten im Cache ersetzen. Sie können eine Standardablaufrichtlinie festlegen, wenn Sie den Cache konfigurieren. In vielen Cachediensten können Sie auch den Ablaufzeitraum für einzelne Objekte angeben, wenn Sie sie programmgesteuert im Cache speichern. Bei einigen Caches können Sie den Ablaufzeitraum als absoluter Wert oder als Gleitwert angeben, wenn innerhalb eines bestimmten Zeitraums nicht auf das Element zugegriffen wird. Diese Einstellung setzt alle cacheweiten Ablaufrichtlinien außer Kraft, jedoch nur für die angegebenen Objekte.

Note

Berücksichtigen Sie den Ablaufzeitraum für den Cache und die Darin enthaltenen Objekte. Wenn Sie es zu kurz machen, laufen Objekte zu schnell ab und reduzieren die Vorteile der Verwendung des Caches. Wenn Sie den Zeitraum zu lang machen, riskieren Sie, dass die Daten veraltet werden.

Wenn Sie zulassen, dass Daten lange im Cache verbleiben, kann der Cache gefüllt werden. In diesem Fall können jegliche Anfragen zum Hinzufügen neuer Elemente zum Cache dazu führen, dass der Cache einige Elemente zwangsweise entfernt, und zwar in einem als Verdrängung bezeichneten Prozess. Cachedienste entfernen Daten in der Regel auf Grundlage des Least Recently Used (LRU)-Prinzips, jedoch können Sie diese Richtlinie gewöhnlich außer Kraft setzen und verhindern, dass Elemente entfernt werden. Wenn Sie diesen Ansatz jedoch übernehmen, riskieren Sie, den im Cache verfügbaren Arbeitsspeicher zu überschreiten. Eine Anwendung, die versucht, einem vollständigen Cache ein Element hinzuzufügen, schlägt mit Ausnahme fehl.

Einige Zwischenspeicherungsimplementierungen stellen möglicherweise andere Eviction-Richtlinien bereit. Zu den Arten von Verdrängungsrichtlinien gehören:

  • Eine zuletzt verwendete Richtlinie: Entfernt die zuletzt verwendeten Elemente aus dem Cache in der Erwartung, dass die Daten nicht mehr benötigt werden.
  • Eine First-in-First-Out-Richtlinie: Entfernt zuerst die ältesten Daten aus dem Cache.
  • Eine explizite Entfernungsrichtlinie: Entfernt Elemente basierend auf einem auslösenden Ereignis, z. B. den geänderten Daten, aus dem Cache.

Ungültige Daten in einem clientseitigen Cache

Daten, die in einem clientseitigen Cache gespeichert werden, gelten als außerhalb der Kontrolle des Diensts, der die Daten für den Client bereitstellt. Ein Dienst kann nicht direkt erzwingen, dass ein Client Informationen aus einem clientseitigen Cache hinzufügen oder entfernen kann.

Diese Einschränkung bedeutet, dass ein Client, der einen schlecht konfigurierten Cache verwendet, veraltete Informationen weiterhin verwenden kann. Wenn beispielsweise die Ablaufrichtlinien des Caches nicht ordnungsgemäß implementiert sind, verwendet ein Client möglicherweise veraltete Informationen, die lokal zwischengespeichert werden, wenn sich die Informationen in der ursprünglichen Datenquelle ändern.

Wenn Sie eine Webanwendung erstellen, die Daten über eine HTTP-Verbindung bedient, können Sie implizit einen Webclient wie einen Browser oder Einen Webproxy erzwingen, um die neuesten Informationen abzurufen. Sie können diese Aktualisierung auslösen, indem Sie den URI ändern, wenn Sie die Ressource aktualisieren. Webclients verwenden in der Regel den URI einer Ressource als Schlüssel im clientseitigen Cache. Wenn sich der URI ändert, ignoriert der Webclient also alle zuvor zwischengespeicherten Versionen einer Ressource und ruft stattdessen die neue Version ab.

Verwalten der Parallelität in einem Cache

Häufig entwerfen Sie Caches, die von mehreren Instanzen einer Anwendung gemeinsam genutzt werden sollen. Jede Anwendungsinstanz kann Daten im Cache lesen und ändern, sodass dieselben Parallelitätsprobleme wie bei jedem freigegebenen Datenspeicher auch für einen Cache gelten. In einer Situation, in der eine Anwendung Die im Cache gespeicherten Daten ändern muss, müssen Sie möglicherweise sicherstellen, dass aktualisierungen, die von einer Instanz der Anwendung vorgenommen wurden, die von einer anderen Instanz vorgenommenen Änderungen nicht überschreiben.

Je nach Art der Daten und der Wahrscheinlichkeit von Kollisionen führen Sie einen von zwei Ansätzen zur Parallelität ein:

  • Optimistisch: Bevor die Anwendung die Daten aktualisiert, überprüft sie, ob sich die Daten im Cache seit dem Abrufen geändert haben. Wenn die Daten immer noch identisch sind, nimmt die Anwendung die Änderung vor. Andernfalls entscheidet die Anwendung, ob sie aktualisiert werden soll. Die Geschäftslogik, die diese Entscheidung steuert, ist anwendungsspezifisch. Dieser Ansatz eignet sich für Situationen, in denen Updates selten sind oder konflikte unwahrscheinlich sind.

  • Pessimistisch: Wenn die Anwendung die Daten abruft, sperrt sie die Daten im Cache, um zu verhindern, dass eine andere Instanz sie ändert. Dieser Prozess stellt sicher, dass Kollisionen nicht auftreten können, aber es kann auch andere Instanzen blockieren, die dieselben Daten verarbeiten müssen. Pessimistische Parallelität kann sich auf die Skalierbarkeit einer Lösung auswirken und wird nur für kurzlebige Vorgänge empfohlen. Dieser Ansatz eignet sich möglicherweise für Situationen, in denen Kollisionen wahrscheinlicher sind, insbesondere, wenn eine Anwendung mehrere Elemente im Cache aktualisiert und sicherstellen muss, dass diese Änderungen konsistent angewendet werden.

Implementieren von hoher Verfügbarkeit und Skalierbarkeit und Verbesserung der Leistung

Vermeiden Sie die Verwendung eines Caches als primäres Repository von Daten. Der ursprüngliche Datenspeicher, aus dem der Cache aufgefüllt wird, dient dieser Rolle. Der ursprüngliche Datenspeicher stellt die Datenpersistenz sicher.

Achten Sie darauf, keine kritischen Abhängigkeiten von der Verfügbarkeit eines gemeinsamen Cachediensts in Ihre Lösungen einzuführen. Eine Anwendung sollte weiterhin funktionieren können, wenn der freigegebene Cache nicht verfügbar ist. Die Anwendung sollte nicht nicht mehr reagieren oder fehlschlagen, während sie darauf wartet, dass der Cachedienst den Betrieb fortsetzt.

Daher muss die Anwendung bereit sein, die Verfügbarkeit des Cachediensts zu erkennen und auf den ursprünglichen Datenspeicher zurückzugreifen, wenn auf den Cache nicht zugegriffen werden kann. Das Circuit-Breaker Muster eignet sich für die Behandlung dieses Szenarios. Der Dienst, der den Cache bereitstellt, kann wiederhergestellt werden, und nachdem er verfügbar ist, kann der Cache neu aufgefüllt werden, da Daten aus dem ursprünglichen Datenspeicher gelesen werden, und zwar nach einer Strategie wie dem Cache-Aside Muster.

Die Systemskalierbarkeit kann jedoch beeinträchtigt werden, wenn die Anwendung auf den ursprünglichen Datenspeicher zurückfällt, wenn der Cache vorübergehend nicht verfügbar ist. Während der Cache wiederhergestellt wird, kann der ursprüngliche Datenspeicher mit Anforderungen für Daten überlagert werden, was zu Timeouts und fehlgeschlagenen Verbindungen führt.

Erwägen Sie die Implementierung eines lokalen, privaten Caches in jeder Instanz einer Anwendung zusammen mit dem freigegebenen Cache, auf den alle Anwendungsinstanzen zugreifen. Wenn die Anwendung ein Element abruft, kann sie zuerst im lokalen Cache, dann im freigegebenen Cache und schließlich im ursprünglichen Datenspeicher überprüfen. Der lokale Cache kann mit den Daten im freigegebenen Cache oder in der Datenbank aufgefüllt werden, wenn der freigegebene Cache nicht verfügbar ist.

Dieser Ansatz erfordert eine sorgfältige Konfiguration, um zu verhindern, dass der lokale Cache im Hinblick auf den freigegebenen Cache zu veraltet wird. Der lokale Cache fungiert jedoch als Puffer, wenn der freigegebene Cache nicht erreichbar ist, wie im folgenden Diagramm dargestellt.

Diagramm mit der Struktur eines freigegebenen Caches, der einen lokalen privaten Cache als Puffer verwenden kann.

Diagramm, das eine Hybrid-Caching-Architektur zeigt. Ganz rechts ist eine SQL-Datenbank, die als Zylinder dargestellt wird. Im oberen rechten Bereich befindet sich ein großer Kreis mit der Beschriftung „Shared Cache Service“, der eine Gittertabelle enthält, die den gemeinsamen Cache darstellt. Ein Pfeil zeigt von der SQL-Datenbank nach links in das Raster des freigegebenen Cachediensts, der angibt, dass die Datenbank den freigegebenen Cache auffüllt. Oben links befindet sich ein großer Kreis mit der Bezeichnung Anwendungsinstanz A. Innerhalb des Kreises befindet sich oben ein Zahnradsymbol, darunter eine Tabelle in Rasterform, die einen lokalen privaten Cache darstellt. Ein doppelköpfiger schwarzer Pfeil verbindet das Zahnradsymbol mit dem lokalen Cache-Raster und kennzeichnet eine Lese-/Schreibbeziehung zwischen dem Anwendungsprozess und seinem lokalen Cache. Unten links befindet sich eine große kreisförmige Anwendungsinstanz B mit derselben internen Struktur. Vom freigegebenen Cache-Dienst gehen zwei blaue Pfeile nach außen: Einer verläuft schräg nach oben zum Zahnradsymbol in Anwendungsinstanz A, und einer verläuft schräg nach unten zum Zahnradsymbol in Anwendungsinstanz B. Eine schwarze rechteckige Linie bildet einen Fallbackpfad, der von der SQL-Datenbank aus entlang der oberen und rechten Kante verläuft, dann nach unten und am unteren Rand entlang und sich wieder mit den lokalen Cache-Rastern in beiden Anwendungsinstanzen verbindet. Eine Bezeichnung in der unteren Mitte des Diagramms liest, wenn der freigegebene Cachedienst nicht verfügbar ist, füllt die Anwendungslogik den lokalen Cache aus der Datenbank auf. Ganz links spannt sich eine geschwungene Klammer zwischen den Anwendungsinstanzen A und B; daneben steht die Beschriftung: Wenn der gemeinsam genutzte Cache-Dienst nicht verfügbar ist, können die Anwendungsinstanzen mithilfe lokaler, privater Caches weiterhin weiterarbeiten.

Um große Caches zu unterstützen, die relativ langlebige Daten enthalten, bieten einige Cachedienste eine Hochverfügbarkeitsoption, die automatisches Failover implementiert, wenn der Cache nicht mehr verfügbar ist. Dieser Ansatz umfasst in der Regel das Replizieren der zwischengespeicherten Daten, die auf einem primären Cacheserver auf einem sekundären Cacheserver gespeichert sind, und das Wechseln zum sekundären Server, wenn der primäre Server fehlschlägt oder die Konnektivität verloren geht.

Um die Latenz zu verringern, die durch das Schreiben an mehrere Ziele entsteht, kann die Replikation zum sekundären Server asynchron erfolgen, wenn Daten in den Cache auf dem primären Server geschrieben werden. Dieser Ansatz führt zu der Möglichkeit, dass einige zwischengespeicherte Informationen möglicherweise verloren gehen, wenn ein Fehler auftritt, aber der Anteil dieser Daten sollte klein sein, verglichen mit der Gesamtgröße des Caches.

Wenn ein freigegebener Cache groß ist, kann es von Vorteil sein, die zwischengespeicherten Daten auf Knoten zu verteilen, um die Wahrscheinlichkeit von Konflikten zu verringern und die Skalierbarkeit zu verbessern. Viele freigegebene Caches unterstützen die Möglichkeit, Knoten dynamisch hinzuzufügen und zu entfernen und die Daten über Partitionen hinweg neu auszubalancieren. Bei diesem Ansatz kann es sich um Clustering handeln, bei der die Sammlung von Knoten Clientanwendungen als einzelner Cache angezeigt wird. Intern werden die Daten jedoch nach einer vordefinierten Verteilungsstrategie, die die Last gleichmäßig ausgleicht, zwischen Knoten verteilt. Weitere Informationen finden Sie im Sharding-Muster.

Clustering kann auch die Verfügbarkeit des Caches erhöhen. Wenn ein Knoten fehlschlägt, kann weiterhin auf den Rest des Caches zugegriffen werden. Clustering wird häufig mit Replikation und Failover verwendet. Jeder Knoten kann repliziert werden, und das Replikat kann schnell online gebracht werden, wenn der Knoten fehlschlägt.

Viele Lese- und Schreibvorgänge umfassen wahrscheinlich einzelne Datenwerte oder Objekte. Manchmal ist es jedoch erforderlich, große Datenmengen schnell zu speichern oder abzurufen. Das Seeding eines Caches kann beispielsweise das Schreiben von Hunderten oder Tausenden von Elementen in den Cache umfassen. Eine Anwendung muss möglicherweise auch eine große Anzahl verwandter Elemente aus dem Cache als Teil derselben Anforderung abrufen.

Viele große Caches stellen Batchvorgänge für diese Zwecke bereit. Dieses Feature ermöglicht einer Clientanwendung das Packen einer großen Menge von Elementen in eine einzelne Anforderung, wodurch der Aufwand für die Ausführung einer großen Anzahl kleiner Anforderungen reduziert wird.

Zwischenspeichern und letztendliche Konsistenz

Damit das Cache-Aside Muster funktioniert, muss die Instanz der Anwendung, die den Cache auffüllt, Zugriff auf die neueste und konsistente Version der Daten haben. In einem System, das letztendliche Konsistenz implementiert (z. B. einen replizierten Datenspeicher), ist diese Bedingung möglicherweise nicht wahr.

Eine Instanz einer Anwendung kann ein Datenelement ändern und die zwischengespeicherte Version dieses Datenelements ungültig machen. Eine andere Instanz der Anwendung kann versuchen, dieses Element aus einem Cache zu lesen, was zu einem Cachefehler führt. Anschließend werden die Daten aus dem Datenspeicher gelesen und dem Cache hinzugefügt. Wenn der Datenspeicher jedoch nicht vollständig mit den anderen Replikaten synchronisiert wird, kann die Anwendungsinstanz den Cache mit dem alten Wert lesen und auffüllen.

Ein verteilter Cache fügt diesem Problem eine weitere Ebene hinzu. Der CAP-Theorem gibt an, dass ein verteiltes System höchstens zwei von drei Garantien bieten kann: Konsistenz, Verfügbarkeit und Partitionstoleranz. Da Netzwerkpartitionen in Cloudumgebungen unvermeidbar sind, müssen Sie zwischen Konsistenz und Verfügbarkeit wählen. Die meisten verteilten Caches, einschließlich Redis, priorisieren Verfügbarkeit und Partitionstoleranz gegenüber starker Konsistenz. Diese Priorität bedeutet, dass Lesevorgänge aus einem Cachereplikat veraltete Daten während einer Netzwerkpartition oder unmittelbar nach einem Schreibvorgang in einen anderen Knoten zurückgeben können. Wenn Sie Ihre Zwischenspeicherungsstrategie entwerfen, entscheiden Sie, wie viel Veraltetheit Ihre Anwendung tolerieren kann, und legen Sie die TTL-Werte (Time-to-Live) entsprechend fest. Verwenden Sie für Daten, die aktuell sein müssen, kürzere TTLs oder umgehen Sie den Cache vollständig, indem Sie aus dem Quelldatenspeicher lesen.

Weitere Informationen zur Behandlung der Datenkonsistenz in verteilten Systemen finden Sie unter "Datenüberlegungen für Microservices".

Schützen zwischengespeicherter Daten

Unabhängig vom verwendeten Cachedienst sollten Sie überlegen, wie Sie die Daten im Cache vor unbefugtem Zugriff schützen. Zwei Hauptanliegen sind:

  • Der Datenschutz der Daten im Cache.
  • Der Datenschutz der Daten, während sie zwischen dem Cache und der Anwendung fließt, die den Cache verwendet.

Um Daten im Cache zu schützen, implementiert der Cachedienst möglicherweise einen Authentifizierungsmechanismus, bei dem Anwendungen die folgenden Details angeben müssen:

  • Welche Identitäten auf Daten im Cache zugreifen können.
  • Welche Lese- und Schreibvorgänge diese Identitäten ausführen dürfen.

Um den mit dem Lesen und Schreiben von Daten verbundenen Aufwand zu verringern, kann eine Identität, nachdem ihr Schreib- oder Lesezugriff auf den Cache gewährt wurde, auf alle Daten im Cache zugreifen.

Wenn Sie den Zugriff auf Teilmengen der zwischengespeicherten Daten einschränken müssen, verwenden Sie einen der folgenden Ansätze:

  • Teilen Sie den Cache mithilfe verschiedener Cacheserver in Partitionen auf. Gewähren Sie nur Zugriff auf Identitäten für die Partitionen, die sie verwenden dürfen.

  • Verschlüsseln Sie die Daten in jeder Teilmenge mithilfe verschiedener Schlüssel. Stellen Sie die Verschlüsselungsschlüssel nur für Identitäten bereit, die Zugriff auf jede Teilmenge haben sollen. Eine Clientanwendung kann möglicherweise weiterhin alle Daten im Cache abrufen, aber sie kann nur die Daten entschlüsseln, für die sie über die Schlüssel verfügt.

Sie müssen außerdem die Daten schützen, wenn sie in den Cache fließen und aus diesem wieder heraus. Verlassen Sie sich auf die Sicherheitsfunktionen der Netzwerkinfrastruktur, die Clientanwendungen für die Verbindung mit dem Cache verwenden. Wenn der Cache mithilfe eines Lokalen Servers innerhalb derselben Organisation implementiert wird, die die Clientanwendungen hosten, erfordert die Isolierung des Netzwerks selbst möglicherweise nicht, dass Sie weitere Schritte ausführen. Wenn sich der Cache remote befindet und eine TCP- oder HTTP-Verbindung über ein öffentliches Netzwerk wie das Internet erfordert, erwägen Sie die Implementierung von TLS.

Implementieren von Caching mit Azure Managed Redis

In den verbleibenden Abschnitten dieses Artikels wird beschrieben, wie Sie die Zwischenspeicherungsmuster mithilfe von Azure Managed Redis implementieren. Azure Managed Redis ist ein verwalteter Redis-Dienst, den Sie als freigegebenen Cache für anwendungsübergreifende Instanzen verwenden können. Es unterstützt das Zwischenspeichern von Schlüsselwerten, Datenstrukturen wie Sätze, sortierte Mengen und Listen sowie optionale Persistenz für die Haltbarkeit bei Neustarts.

Informationen zu verfügbaren Ebenen, Kapazitätsplanung, Netzwerk- und Featuredetails finden Sie in der Dokumentation zu azure Managed Redis.

Verbinden und Konfigurieren von Clientanwendungen

Redis unterstützt Clientanwendungen in vielen Programmiersprachen. Für .NET-Anwendungen stehen mehrere Clientbibliotheken zur Verfügung, die jeweils für unterschiedliche Redis-Workloads geeignet sind. Die Auswahl der Bibliothek hängt davon ab, ob Sie Redis ausschließlich als Cache oder als Multimodelldatenplattform verwenden.

Verwenden Sie die statische Connect Methode der ConnectionMultiplexer Klasse, um eine Verbindung mit einem Redis-Server herzustellen. Die von dieser Methode erstellte Verbindung wird während der gesamten Lebensdauer der Clientanwendung verwendet. Mehrere gleichzeitige Threads können dieselbe Verbindung verwenden. Verbinden und trennen Sie die Verbindung nicht bei jedem Redis-Vorgang neu, da dies die Leistung beeinträchtigt.

Sprachspezifische Verbindungsbeispiele finden Sie unter Use Azure Managed Redis in .NET Core.

Auswählen einer .NET-Clientbibliothek

Wenn Sie Azure verwaltete Redis zum Zwischenspeichern verwenden, verwenden Sie die folgenden .NET Bibliotheken:

  • StackExchange.Redis: Ein Redis-Client mit niedriger Ebene, der eine hohe Leistung bietet. Verwenden Sie sie, wenn Sie direkten Zugriff auf Redis-Befehle, atome Operationen, Transaktionen, Pipelining oder Lua-Skripts benötigen.

  • Microsoft. Extensions.Caching.StackExchangeRedis: Stellt eine IDistributedCache-Integration für ASP.NET Core bereit. Verwenden Sie sie für die einfache Zwischenspeicherung von Schlüsseln, bei denen Werte als undurchsichtige Bytearrays gespeichert werden. Diese Abstraktion macht keine erweiterten Redis-Datenstrukturen verfügbar.

Diese Bibliotheken stellen die Grundtypen bereit, die zum Erstellen allgemeiner Zwischenspeicherungsmuster erforderlich sind, die Anwendung muss jedoch die Zwischenspeicherungslogik selbst implementieren.

Implementierung von Cache-Strategien

Die einfachste Möglichkeit zum Verwenden von Redis zum Zwischenspeichern ist das Speichern von Werten unter Schlüsseln mithilfe des Schlüsselwertmodells. Werte können Zeichenfolgen oder Binärdaten beliebiger Länge sein, wodurch Redis gut zum Zwischenspeichern serialisierter Objekte, Konfigurationsdaten, Sitzungszustand oder vorkompilierter Ergebnisse geeignet ist.

Gestalten Sie Ihren Schlüsselraum sorgfältig und verwenden Sie bedeutungsvolle (aber knappe) Schlüssel. Verwenden Sie beispielsweise strukturierte Schlüssel wie customer:100 (statt nur 100), um den Schlüssel für den Kunden mit der ID 100 darzustellen. Mit diesem Schema können Sie zwischen Werten unterscheiden, die unterschiedliche Datentypen speichern. Zum Beispiel können Sie auch den Schlüssel orders:100 verwenden, um den Schlüssel für die Bestellung mit der ID 100 darzustellen.

Während Zeichenfolgen der gängigste Zwischenspeicherungsansatz sind, unterstützt Redis einen umfangreichen Satz systemeigener Datentypen, z. B. Hashes, Listen, Sets, sortierte Mengen und Datenströme, die flexiblere Cachemuster ermöglichen. Weitere Informationen zu Redis-Datentypen finden Sie in der Redis-Dokumentation zu Datentypen.

Cache-Aside-Pattern implementieren

Wie in "Bestimmen, wie Daten effektiv zwischengespeichert werden" beschrieben, besteht ein allgemeiner Ansatz darin, Daten nach Bedarf in den Cache zu laden. Im folgenden Beispiel wird zuerst der Cache überprüft, die Datenquelle bei einem Fehler abgerufen und das Ergebnis für nachfolgende Anforderungen gespeichert:

var config = new ConfigurationOptions();
// ... configure endpoint, credentials, TLS, etc.
ConnectionMultiplexer redisHostConnection = ConnectionMultiplexer.Connect(config);
IDatabase cache = redisHostConnection.GetDatabase();

async Task<string> RetrieveItemAsync(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 is null)
    {
        itemValue = await GetItemFromDataSourceAsync(itemKey);
        await cache.StringSetAsync(itemKey, itemValue);
    }

    return itemValue;
}

Ausführen von Atom- und Batchvorgängen

Wenn mehrere Clients oder Anwendungsinstanzen einen Cache teilen, müssen Sie verhindern, dass gleichzeitige Aktualisierungen die Daten beschädigen. Die allgemeinen Parallelitätsstrategien werden in der Verwaltung der Parallelität in einem Cache weiter oben in diesem Artikel beschrieben. Redis bietet mehrere Mechanismen, mit denen diese Strategien implementiert werden.

  • Atomare Einzel-Schlüssel-Operationen: Verwenden Sie Befehle, um einen Wert in einem einzigen Schritt zu aktualisieren, und vermeiden Sie dadurch Race Conditions, die entstehen, wenn GET und SET separat ausgeführt werden.

    • INCR, INCRBY, DECR, DECRBY inkrementieren oder dekrementieren atomar einen numerischen Wert. In StackExchange.Redis verwenden IDatabase.StringIncrementAsync und IDatabase.StringDecrementAsync. Diese Befehle sind nützlich für Zähler, Ratenbegrenzer und die Kontingentverfolgung, wenn mehrere Clients denselben Schlüssel gleichzeitig aktualisieren.

    • GETSET setzt einen Schlüssel atomar auf einen neuen Wert und gibt den vorherigen Wert zurück. Verwenden Sie IDatabase.StringGetSetAsyncin StackExchange.Redis:

      string oldValue = await cache.StringGetSetAsync("data:counter", 0);
      
  • Operationen mit mehreren Schlüsseln:MGET und MSET ermöglichen das Lesen oder Schreiben mehrerer Zeichenfolgenwerte in einer einzigen Anfrage-Antwort-Runde, wodurch sich der Netzwerkaufwand verringert, wenn Sie mehrere Schlüssel auf einmal verarbeiten müssen. Die Methoden IDatabase.StringGetAsync und IDatabase.StringSetAsync werden überladen, um diese Funktionalität zu unterstützen:

    // Create a list of key-value pairs
    var keysAndValues =
        new KeyValuePair<RedisKey, RedisValue>[]
        {
            new("data:key1", "value1"),
            new("data:key99", "value2"),
            new("data:key322", "value3")
        };
    
    // Store the list of key-value pairs in the cache
    await cache.StringSetAsync(keysAndValues);
    ...
    // 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 = await cache.StringGetAsync(keys);
    
  • Transaktionen (optimistische Parallelität): Sie können den WATCH Befehl verwenden, um eine oder mehrere Tasten zu überwachen, bevor Sie mit einer Transaktion MULTI/EXECbeginnen. Wenn sich die überwachten Schlüssel vor dem Beginn der Transaktion ändern, verwirft Redis die Transaktion, und der Client kann die Transaktion wiederholen. Die StackExchange-Bibliothek bietet Unterstützung für Transaktionen über die ITransaction Schnittstelle.

    Sie erstellen ein ITransaction Objekt mithilfe der IDatabase.CreateTransaction Methode. Sie rufen Befehle für die Transaktion mithilfe der vom ITransaction Objekt bereitgestellten Methoden auf.

    Die ITransaction Schnittstelle bietet Zugriff auf eine Reihe von Methoden, die den Methoden ähneln, auf die über die IDatabase Schnittstelle zugegriffen wird, mit der Ausnahme, dass alle Methoden asynchron sind. Dies bedeutet, dass sie nur ausgeführt werden, wenn die ITransaction.Execute Methode aufgerufen wird. Der von der ITransaction.Execute Methode zurückgegebene Wert gibt an, ob die Transaktion erfolgreich erstellt wurde (true) oder ob sie fehlgeschlagen ist (false).

    Der folgende Codeausschnitt zeigt ein Beispiel, bei dem zwei Zähler als Teil derselben Transaktion inkrementiert und dekrementiert werden.

    ITransaction transaction = cache.CreateTransaction();
    
    var tx1 = transaction.StringIncrementAsync("data:counter1");
    var tx2 = transaction.StringDecrementAsync("data:counter2");
    
    bool result = await transaction.ExecuteAsync();
    
    Console.WriteLine($"Transaction {(result ? "succeeded" : "failed")}");
    
    if (result)
    {
        long increment = await tx1;
        long decrement = await tx2;
    
        Console.WriteLine($"Result of increment: {increment}");
        Console.WriteLine($"Result of decrement: {decrement}");
    }
    

    Transaktionen in Redis unterscheiden sich von Transaktionen in relationalen Datenbanken. Mit der Execute Methode werden alle Befehle, die die Transaktion umfassen, in die Warteschlange gestellt, und wenn irgendein Befehl ungültig ist, wird die Transaktion gestoppt. Wenn alle Befehle erfolgreich in die Warteschlange gestellt werden, wird jeder Befehl asynchron ausgeführt. Wenn ein Befehl fehlschlägt, fahren die anderen weiterhin mit der Verarbeitung fort. Wenn Sie überprüfen müssen, ob ein Befehl erfolgreich abgeschlossen wurde, rufen Sie die Ergebnisse mithilfe der Result Eigenschaft der entsprechenden Aufgabe ab, wie im vorherigen Beispiel gezeigt.

  • Lua Scripting. Für Multistep-Updates, die über mehrere Schlüssel hinweg atomar sein müssen, können Sie ein Lua-Skript auf dem Server ausführen. Redis führt das gesamte Skript als einzelnen Vorgang aus, ohne andere Befehle zu überlappen.

    Note

    In gruppierten Bereitstellungen müssen sich alle schlüssel, die an einer Transaktion oder einem Lua-Skript beteiligt sind, im selben Hashplatz befinden. Verwenden Sie Hash-Tags wie customer:{123}:name oder customer:{123}:email, um zusammengehörige Schlüssel zusammen anzuordnen.

Cachevorgänge ohne Rückmeldung ausführen

Wenn sich eine Cacheaktualisierung nicht auf die Anwendungskorrektur auswirkt, z. B. das Erhöhen eines Ansichtsindikators oder das Aktualisieren einer nicht kritischen Statistik, können Sie das Warten auf die Antwort des Servers überspringen. Bei einem Fire-and-Forget-Cachevorgang startet Ihre Anwendung eine Hintergrundaufgabe und fährt fort, ohne auf deren Abschluss zu warten. Redis unterstützt Fire-and-Forget-Operationen, die die Round-Trip-Latenz für den Client reduzieren, mithilfe von Befehls-Flags:

await cache.StringSetAsync("data:key1", 99);
...
cache.StringIncrement("data:key1", flags: CommandFlags.FireAndForget);

Automatisch ablaufende Schlüssel festlegen

Die unter Verwalten des Datenablaufs in einem Cache beschriebenen Ablaufstrategien werden in Redis über schlüsselbasierte TTLs implementiert. Wenn Sie ein Element in einem Redis-Cache speichern, können Sie ein Timeout angeben, nach dem das Element automatisch entfernt wird. Sie können auch abfragen, wie viel Zeit ein Schlüssel hat, bevor er abläuft, indem Sie den TTL Befehl verwenden. Dieser Befehl ist über die IDatabase.KeyTimeToLive Methode für StackExchange-Anwendungen verfügbar.

Der folgende Codeausschnitt zeigt, wie Sie eine Ablaufzeit von 20 Sekunden für einen Schlüssel festlegen und die verbleibende Lebensdauer des Schlüssels abfragen:

// 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 null
TimeSpan? expiry = cache.KeyTimeToLive("data:key1");

Sie können den Ablauf auch auf ein bestimmtes Datum und eine bestimmte Uhrzeit festlegen, indem Sie den EXPIREAT Befehl verwenden, der in der StackExchange-Bibliothek als KeyExpireAsync Methode verfügbar ist. Es akzeptiert einen DateTime Parameter:

await cache.StringSetAsync("data:key1", 99);
await cache.KeyExpireAsync("data:key1",
    new DateTime(2026, 9, 1, 0, 0, 0, DateTimeKind.Utc));

Tip

Sie können ein Element manuell aus dem Cache entfernen, indem Sie den DEL Befehl verwenden, der über die StackExchange-Bibliothek als IDatabase.KeyDeleteAsync Methode verfügbar ist.

Wenn Redis seinen Speichergrenzwert erreicht, werden Schlüssel gemäß einer konfigurierten Auslagerungsrichtlinie ausgelagert. Die Standardrichtlinie lautet volatile-lru, wodurch der am wenigsten kürzlich verwendete Schlüssel mit gesetztem TTL gelöscht wird. Andere Richtlinien umfassen allkeys-lru, volatile-randomund noeviction (was bewirkt, dass Schreibvorgänge fehlschlagen, wenn der Arbeitsspeicher voll ist). Wählen Sie eine Entfernungsrichtlinie basierend darauf aus, ob Ihre Anwendung TTLs konsistent verwendet und ob Sie Schlüssel schützen möchten, die keinen Ablauf haben. Weitere Informationen finden Sie unter Speicherverwaltung.

Querkorrelieren zwischengespeicherter Elemente

Wenn Sie verwandte Elemente zwischenspeichern, müssen Sie diese häufig nach Beziehung und nicht nach Primärschlüssel allein finden. Beispielsweise können Sie Blogbeiträge zwischenspeichern und Abfragen wie "Welche Beiträge teilen Tag Y?" oder "Welche Tags gehören zu Beitrag X?" beantworten.

In Azure Managed Redis ist der empfohlene Ansatz die Verwendung von RedisJSON und RediSearch. Speichern Sie jedes zwischengespeicherte Element als JSON-Dokument mit seinen Metadaten, und erstellen Sie dann einen RediSearch-Index über die Felder, die Sie abfragen müssen. RediSearch behandelt reverse Lookups, tagbasierte Filterung, Bereichsabfragen und Volltextsuche, ohne dass Ihre Anwendung separate Indexstrukturen verwalten muss.

Für einfachere Szenarien können Sie redis Sets auch verwenden, um Vorwärts- und Reverseindizes manuell zu erstellen. Speichern Sie einen Satz pro Beitrag (mit seinen Tags) und einen Satz pro Tag (mit den Post-IDs):

foreach (BlogPost post in posts)
{
    string postTagsKey = $"blog:posts:{post.Id}:tags";
    await cache.SetAddAsync(
        postTagsKey, post.Tags.Select(s => (RedisValue)s).ToArray());

    foreach (var tag in post.Tags)
    {
        await cache.SetAddAsync($"tag:{tag}:blog:posts", post.Id);
    }
}

Anschließend können Sie die Tags eines Beitrags mit SetMembersAsync abfragen, gemeinsame Tags mehrerer Beiträge mit SetCombineAsync(SetOperation.Intersect, ...) ermitteln oder alle Beiträge zu einem bestimmten Tag finden. Der Kompromiss besteht darin, dass Ihre Anwendung sowohl die Vorwärts- als auch die Reversesätze beibehalten muss, was die Komplexität erhöht, wenn die Anzahl der Beziehungen wächst.

Zuletzt geöffnete Elemente suchen

Viele Anwendungen müssen die zuletzt aufgerufenen oder angezeigten Elemente nachverfolgen. Eine Blogwebsite kann z. B. die zuletzt gelesenen Beiträge für einen zurückgegebenen Besucher anzeigen. Redis-Listen bieten eine effiziente Möglichkeit zum Implementieren von recency-basierten Cachemustern. Elemente können mithilfe von LPUSH oder RPUSH an beiden Enden der Liste hinzugefügt und mithilfe von LPOP oder RPOP entfernt werden. Verwenden Sie LTRIM, um die Länge der Liste zu begrenzen und ungebundenes Speicherwachstum zu verhindern.

Implementieren eines Leaderboards

Redis Sorted Sets (ZSETs) verwalten sortierte Rangfolgen, indem jedes Element einem numerischen Wert zugeordnet wird. Redis verwaltet die Sortierung automatisch. ZADD ist O(log N), und Bereichsanfragen wie ZRANGE und ZREVRANGE sind O(log N + M), wobei M die Anzahl der zurückgegebenen Elemente ist, sodass sortierte Mengen auch bei einer großen Anzahl an Elementen effizient bleiben.

Elemente zu einer Bestenliste hinzufügen

Das folgende Beispiel zeigt, wie Sie mithilfe des Befehls ZADD über SortedSetAddAsync einen Blogbeitrag und seine Bewertung zu einer Bestenliste hinzufügen:

var db = connection.GetDatabase();
string redisKey = "blog:post_rankings";

BlogPost blogPost = ...; // The blog post being ranked

await db.SortedSetAddAsync(redisKey, blogPost.Title, blogPost.Score);
Rangierte Elemente abrufen

Sie können Elemente in aufsteigender Bewertungsreihenfolge abrufen, indem Sie Folgendes verwenden SortedSetRangeByRankWithScoresAsync:

var entries = await db.SortedSetRangeByRankWithScoresAsync(redisKey);

foreach (var entry in entries)
{
    Console.WriteLine($"{entry.Element}: {entry.Score}");
}

Note

SortedSetRangeByRankAsync gibt nur Memberwerte und keine Bewertungen zurück.

Abruf von Top-N-Elementen

Um die höchstbewerteten Elemente zu erhalten, beispielsweise die Top-10-Beiträge, verwenden Sie eine absteigende Reihenfolge:

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

Sie können Elemente auch basierend auf Bewertungsgrenzen und nicht auf Rangfolge abfragen:

foreach (var post in await cache.SortedSetRangeByScoreWithScoresAsync(
                               redisKey, 5000, 100000))
{
    Console.WriteLine(post);
}

Um zu verhindern, dass eine Bestenliste unbegrenzt wächst, entfernen Sie alte Einträge mithilfe von SortedSetRemoveRangeByRankAsync oder verwenden Sie zeitlich begrenzte Schlüssel (z. B. für tägliche oder wöchentliche Bestenlisten). Sie können Punktzahlen atomar mithilfe von SortedSetIncrementAsync (ZINCRBY) aktualisieren.

Cachesitzungszustand und HTML-Ausgabe

Sie können Azure Verwaltete Redis verwenden, um Sitzungszustands- und Ausgabecachedaten für ASP.NET Core und ASP.NET Anwendungen zu speichern. Wenn Sie Sitzungsdaten und gerenderte Ausgabe in einem gemeinsamen Redis-basierten Cache speichern, können Anwendungen, die über mehrere Instanzen hinweg ausgeführt werden, z. B. in Azure App Service, Azure Kubernetes Service (AKS), Azure Container Apps oder VM-Skalierungsgruppen, konsistente Benutzererfahrungen aufrechterhalten, ohne dass Serveraffinität erforderlich ist.

Tip

Um eine optimale Leistung zu erzielen, stellen Sie Ihre Anwendung und azure Managed Redis-Instanz in derselben Azure-Region bereit.

ASP.NET Kern

ASP.NET Core-Anwendungen verwenden die IDistributedCache Abstraktion und Sitzungs-Middleware. Azure Managed Redis integriert sich mit IDistributedCache über das Microsoft.Extensions.Caching.StackExchangeRedis-Paket.

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "<your-cache-name>.<region>.redis.azure.net:10000";
    options.InstanceName = "app-cache:";
});

builder.Services.AddSession();

ASP.NET Core Ausgabezwischenspeicherungs-Middleware kann Redis auch als verteilter Sicherungsspeicher verwenden, sodass Anwendungen gerenderte Fragmente oder Seiten für alle Instanzen freigeben können. Weitere Informationen finden Sie unter ASP.NET Core Output Cache Provider for Redis.

.NET Aspire-Integration

.NET Aspire-Anwendungen können das Aspire.Hosting.Azure.Redis Paket verwenden, um eine Azure Managed Redis-Ressource im App-Host zu deklarieren. Nutzende Projekte erhalten die Verbindungskonfiguration automatisch durch Dependency Injection, wodurch die manuelle Verwaltung von Verbindungszeichenfolgen über Dienste hinweg entfällt.

// App host: declare the Azure Managed Redis resource
var cache = builder.AddAzureManagedRedis("cache");

builder.AddProject<Projects.ProductService>()
    .WithReference(cache);

Die Dienste registrieren den verteilten Cache auf die gleiche Weise wie jeder andere IDistributedCache Anbieter. Weitere Informationen finden Sie unter "Erste Schritte mit der Redis-Integration".

Hohe Verfügbarkeit, Skalierbarkeit und Partitionierung

Jede Azure Redis-Instanz verwendet Primär-/Replikatreplikation. Der Dienst überwacht die Knotengesundheit und befördert automatisch ein Replikat, wenn das primäre ausfällt. Da die Replikation asynchron ist, können während eines unerwarteten Failovers eine kleine Menge kürzlich geschriebener Daten verlorengehen. Für die allgemeinen Strategien hinter Replikation, Failover und mehrschichtigem Zwischenspeichern siehe Implementieren von hoher Verfügbarkeit und Skalierbarkeit sowie Leistungsverbesserung weiter oben in diesem Artikel.

Sie können einen lokalen Speichercache mit Azure Managed Redis kombinieren, um die Latenz zu reduzieren und einen Fallback bereitzustellen, wenn der freigegebene Cache vorübergehend nicht erreichbar ist. Das Circuit-Breaker-Pattern und Cache-Aside-Pattern helfen bei der Umsetzung dieses mehrschichtigen Ansatzes.

Bei Workloads, die die Kapazität eines einzelnen Knotens überschreiten, unterstützt Azure Managed Redis Partitionierungsdaten (sharding) über mehrere Redis-Knoten hinweg. Bei beiden Clustering-Richtlinien werden Daten automatisch über Knoten hinweg geshardet – mithilfe von Key-to-Shard-Hashing, automatischem Failover und Resynchronisierung sowie Online-Resharding (Scale-out und Scale-in). Azure Managed Redis unterstützt zwei Clusteringrichtlinien:

  • OSS-Clusteringrichtlinie (Standard): Clients kommunizieren direkt mit dem entsprechenden Shard und folgen der OSS Redis Cluster-Semantik, einschließlich MOVED- und ASK-Umleitungen. Clusterfähige Clients wie StackExchange.Redis behandeln diese Umleitungen automatisch. Diese Richtlinie bietet den niedrigsten Routingaufwand.

  • Redis Enterprise Clustering-Richtlinie: Ein Proxy stellt ein transparentes Routing über einen einzelnen Endpunkt bereit. Clients müssen keine clusterbewusste Logik implementieren oder MOVED-/ASK-Antworten behandeln. Diese Richtlinie bietet eine einfachere Clientintegration, führt jedoch einen geringen Routingaufwand ein.

Azure Managed Redis unterstützt auch den Nicht-Cluster-Modus, der ein einzelnes Primär-/Replikatpaar ohne Sharding nutzt. Dieser Modus eignet sich für kleinere Workloads, die keine horizontale Skalierung erfordern.

Note

Benutzerdefinierte Partitionierungsmodelle, z. B. clientseitiges Hashing oder Nicht-Microsoft Proxys, werden in der Regel nur in selbstverwalteten Redis-Bereitstellungen auf virtuellen Computern oder Kubernetes benötigt. Azure Managed Redis-Cluster verwaltet das Routing, die Fehlertoleranz und das Resharding automatisch.

Aktive Georeplikation

Für regionenübergreifende Verfügbarkeit unterstützt Azure Managed Redis die aktive Georeplikation, die Instanzen über Azure-Regionen hinweg zu einer einzigen Replikationsgruppe zusammenfasst. Jede Instanz kann Lese- und Schreibvorgänge verarbeiten und änderungen automatisch synchronisieren. Ihre Anwendung ist dafür verantwortlich, den Datenverkehr während eines regionalen Fehlers an eine fehlerfreie Instanz umzuleiten. Weitere Informationen finden Sie unter Aktive Georeplikation konfigurieren.

Datenpersistenz

Standardmäßig werden zwischengespeicherte Daten in Azure Managed Redis im Arbeitsspeicher gespeichert und können verloren gehen, wenn ein Knoten neu gestartet oder fehlschlägt. Bei Workloads, bei denen die Neuerstellung des Caches aus dem Quelldatenspeicher langsam oder teuer wäre, unterstützt Azure Managed Redis optionale Datenpersistenz:

  • RDB-Momentaufnahmen (Redis-Datenbank) erstellen periodische Point-in-Time-Momentaufnahmen, die auf einem verwalteten Datenträger gespeichert sind. RDB hat während des normalen Betriebs nur minimale Auswirkungen auf die Leistung, aber Daten, die seit dem letzten Snapshot geschrieben wurden, können verloren gehen.

  • AOF (Append-Only File) protokolliert jeden Schreibvorgang auf der Festplatte. AOF reduziert potenzielle Datenverluste auf etwa eine Sekunde von Schreibvorgängen, erzeugt jedoch größere Dateien und kann den Schreibdurchsatz reduzieren.

Sie können SOWOHL RDB als auch AOF zusammen verwenden. Redis lädt beim Start die RDB-Momentaufnahme und spielt dann das AOF-Protokoll für die nahezu vollständige Wiederherstellung ab.

Important

Persistenz verbessert die Haltbarkeit gegenüber Knotenfehlern, es ist jedoch kein Sicherungs- oder Notfallwiederherstellungsmechanismus. Behalten Sie für wichtige Daten immer die autoritative Kopie im Quelldatenspeicher bei, und verwenden Sie das Cache-Aside Muster , um den Cache erneut zu füllen.

Konfigurationsdetails finden Sie unter Konfigurieren der Datenpersistenz.

Schützen zwischengespeicherter Daten in Azure Managed Redis

Die Anleitung zum Schützen zwischengespeicherter Daten beschreibt Probleme bei der Zugriffssteuerung und der Datenübertragung. Azure Managed Redis hilft Ihnen, diesen Bedenken auf folgende Weise zu begegnen:

  • Verwenden Sie die Microsoft Entra ID-Authentifizierung als primären Zugriffssteuerungsmechanismus, und befolgen Sie das Prinzip der geringsten Berechtigungen, wenn Sie Zugriff gewähren.

  • Verwenden Sie private Endpunkte , um den Netzwerkzugriff einzuschränken, sodass Datenverkehr das öffentliche Internet nicht durchläuft.

  • Azure Managed Redis verschlüsselt Daten bei der Übertragung mithilfe von TLS und verschlüsselt gespeicherte Daten.

Überlegungen zur Serialisierung

Wenn Sie .NET-Objekte in Redis als Zeichenfolgenwerte speichern, müssen Sie sie serialisieren. Wenn Sie ein Serialisierungsformat auswählen, sollten Sie kompromisse zwischen Leistung, Interoperabilität, Versionsverwaltung und Nutzlastgröße berücksichtigen. Es gibt keinen einzigen schnellsten Serialisierer für alle Szenarien. Benchmarks sind stark vom Kontext abhängig und spiegeln möglicherweise nicht Ihre tatsächliche Arbeitsauslastung wider.

Wenn Die Azure Managed Redis-Ebene Redis RedisSON unterstützt, können Sie Objekte als systemeigene JSON-Dokumente speichern und einzelne Felder abfragen, ohne den gesamten Wert zu deserialisieren:

public static class RedisJsonExtensions
{
    public static async Task<T?> GetAsync<T>(
        this IDatabase cache,
        string key,
        string path = "$")
    {
        var result = await cache.ExecuteAsync("JSON.GET", key, path);

        if (result.IsNull)
            return default;

        return JsonSerializer.Deserialize<T>(result!);
    }

    public static async Task SetAsync<T>(
        this IDatabase cache,
        string key,
        T value,
        TimeSpan? expiry = null,
        string path = "$")
    {
        var json = JsonSerializer.Serialize(value);

        // Store JSON document
        await cache.ExecuteAsync("JSON.SET", key, path, json);

        // Apply TTL if provided
        if (expiry.HasValue)
        {
            await cache.KeyExpireAsync(key, expiry);
        }
    }

    public static async Task<bool> ExpireAsync(
        this IDatabase cache,
        string key,
        TimeSpan expiry)
    {
        return await cache.KeyExpireAsync(key, expiry);
    }
}

Wenn Sie stattdessen Werte als Redis-Zeichenfolgen serialisieren, umfassen allgemeine Formatoptionen Folgendes:

  • JSON – Lesbares Format, das über eine breite plattformübergreifende Unterstützung verfügt. Nicht das kompakteste Format, sondern eine gute Wahl, wenn zwischengespeicherte Elemente direkt an HTTP-Clients zurückgegeben werden, da dadurch ein zusätzlicher Deserialisierungs- und Reserialisierungsschritt vermieden wird.

  • MessagePack – Ein kompaktes Binärformat ohne Schemaanforderung. Erzeugt kleinere Nutzlasten als JSON mit geringerem Serialisierungsaufwand.

  • Protokollpuffer (Protobuf) – Ein schemabasiertes Binärformat, das kompakte Nutzlasten erzeugt. Erfordert .proto Definitionsdateien und einen Kompilierungsschritt, um sprachspezifischen Code zu generieren.

  • BSON – Ein Binärformat, das JSON mit weiteren Typen erweitert, z. B. Datumsangaben und rohe Binärdaten. Nutzlasten sind in der Größe mit JSON vergleichbar. Eine praktische Wahl, wenn Ihre Anwendung BSON bereits an anderer Stelle verwendet, z. B. mit MongoDB.

Die folgenden Muster sind möglicherweise relevant, wenn Sie die Zwischenspeicherung in Ihren Anwendungen implementieren:

  • Cache-Aside Muster: Dieses Muster beschreibt, wie Daten bei Bedarf in einen Cache aus einem Datenspeicher geladen werden. Außerdem wird die Konsistenz zwischen Daten im Cache und Daten im ursprünglichen Datenspeicher beibehalten.

  • Sharding-Muster: Dieses Muster enthält Informationen zur Implementierung horizontaler Partitionierung, um die Skalierbarkeit beim Speichern und Zugreifen auf große Datenmengen zu verbessern.