Freigeben über


Caching guidance

Azure Cache for Redis

Zwischenspeichern ist eine gängige Methode zur Verbesserung der Leistung und Skalierbarkeit eines Systems. Daten werden zwischengespeichert, indem häufig verwendete Daten vorübergehend in den schnellen Speicher kopiert werden, der sich in der Nähe der Anwendung befindet. Wenn sich dieser schnelle Datenspeicher näher an der Anwendung befindet als die ursprüngliche Datenquelle, kann Zwischenspeichern die Antwortzeiten für Clientanwendungen erheblich verbessern und die Daten schneller bereitstellen.

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

  • Es bleibt relativ statisch.
  • Er ist langsam verglichen mit der Geschwindigkeit des Caches.
  • Er unterliegt in hohem Maße Konflikten.
  • Es ist weit entfernt, wenn die Netzwerklatenz dazu führen kann, dass der Zugriff langsam ist.

Zwischenspeichern in verteilten Anwendungen

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

  • Sie verwenden einen privaten Cache, in dem Daten lokal auf dem Computer gespeichert werden, auf dem eine Instanz einer Anwendung oder eines Diensts ausgeführt wird.
  • Sie verwenden einen freigegebenen Cache, der als gemeinsame Quelle dient, auf die von mehreren Prozessen und Computern zugegriffen werden kann.

In beiden Fällen kann zwischenspeichern clientseitig und serverseitig ausgeführt werden. Das clientseitige Zwischenspeichern erfolgt durch den Prozess, der die Benutzeroberfläche für ein System bereitstellt, z. B. einen Webbrowser oder eine Desktopanwendung. Die serverseitige Zwischenspeicherung erfolgt durch den Prozess, der die remote ausgeführten Geschäftsdienste bereitstellt.

Private caching

Der grundlegendste Cachetyp ist ein Speicher im Arbeitsspeicher. Er befindet sich im Adressbereich eines einzelnen Prozesses und wird direkt vom Code aufgerufen, der in diesem Prozess ausgeführt wird. Dieser Cachetyp kann schnell zugegriffen werden. Sie kann auch ein effektives Mittel zum Speichern bescheidener Mengen statischer Daten bieten. Die Größe eines Caches wird in der Regel durch die Menge an Arbeitsspeicher eingeschränkt, die auf dem Computer verfügbar ist, auf dem der Prozess gehostet wird.

Wenn Sie mehr Informationen zwischenspeichern müssen, als physisch im Arbeitsspeicher möglich sind, können Sie zwischengespeicherte Daten in das lokale Dateisystem schreiben. Dieser Prozess wird langsamer auf daten zugreifen, die 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 in Abbildung 1 dargestellt.

Die Ergebnisse der Verwendung eines Speichercaches in verschiedenen Instanzen einer Anwendung

Abbildung 1: Verwenden eines Speichercaches in verschiedenen Instanzen einer Anwendung.

Shared caching

Wenn Sie einen freigegebenen Cache verwenden, kann dies dazu beitragen, Bedenken zu verringern, dass sich Daten in jedem Cache unterscheiden können, was bei der Zwischenspeicherung im Arbeitsspeicher auftreten kann. Die gemeinsame Zwischenspeicherung stellt sicher, dass unterschiedliche Anwendungsinstanzen dieselbe Ansicht von zwischengespeicherten Daten sehen. Er sucht den Cache an einem separaten Speicherort, der in der Regel als Teil eines separaten Diensts gehostet wird, wie in Abbildung 2 dargestellt.

Die Ergebnisse der Verwendung eines freigegebenen Caches

Abbildung 2: Verwenden eines freigegebenen Caches.

Ein wichtiger Vorteil des gemeinsamen Caching-Ansatzes ist die Skalierbarkeit, die er bietet. 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 einfach 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 zugänglich, da er nicht mehr lokal für jede Anwendungsinstanz gespeichert wird.
  • Die Anforderung, einen separaten Cachedienst zu implementieren, kann der Lösung Komplexität hinzufügen.

Überlegungen zur Verwendung der Zwischenspeicherung

In den folgenden Abschnitten werden die Überlegungen zum Entwerfen und Verwenden eines Caches ausführlicher 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 werden die Vorteile der Zwischenspeicherung. Die Zwischenspeicherung reduziert die Latenz und die Inhaltsmenge, die mit der Verarbeitung großer Mengen gleichzeitiger Anforderungen im ursprünglichen Datenspeicher verbunden ist.

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 Im Cache gespeicherten Daten verwenden.

Erwägen Sie das Zwischenspeichern von Daten, die häufig gelesen, aber selten geändert werden (z. B. Daten mit einem höheren Anteil an Lesevorgängen als Schreibvorgängen). Es wird jedoch nicht empfohlen, den Cache als autoritativen Informationsspeicher zu verwenden. Stellen Sie stattdessen sicher, dass alle Änderungen, die Ihre Anwendung nicht verlieren kann, immer in einem permanenten Datenspeicher gespeichert werden. 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

Der Schlüssel zur effektiven Verwendung eines Caches liegt darin, die am besten geeigneten Daten zum Zwischenspeichern zu ermitteln und zur entsprechenden Zeit zwischenzuspeichern. Die Daten können dem Cache bei Bedarf hinzugefügt werden, wenn sie zum ersten Mal von einer Anwendung abgerufen werden. Die Anwendung muss die Daten nur einmal aus dem Datenspeicher abrufen, und dieser nachfolgende Zugriff kann mit dem Cache erfüllt werden.

Alternativ kann ein Cache teilweise oder vollständig im Voraus mit Daten gefüllt werden, in der Regel, wenn die Anwendung gestartet wird (ein Ansatz, der als Seeding bezeichnet wird). 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.

Häufig können Sie anhand einer Analyse von Verwendungsmustern entscheiden, ob sie einen Cache vollständig oder teilweise auffüllen und die zwischengespeicherten Daten auswählen möchten. Sie können beispielsweise den Cache mit den statischen Benutzerprofildaten für Kunden ausser setzen, die die Anwendung regelmäßig verwenden (vielleicht jeden Tag), 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 sind Referenzinformationen wie Produkt- und Preisinformationen in einer E-Commerce-Anwendung oder gemeinsam genutzte statische Ressourcen, die zu erstellen sind. Einige oder alle diese Daten können beim Starten der Anwendung in den Cache geladen werden, 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 zu dieser Berücksichtigung 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 sie erforderlich sind.

Es wird empfohlen, Leistungstests und Verwendungsanalysen durchzuführen, um festzustellen, ob das Vorabladen oder das Laden des Caches bei Bedarf oder eine Kombination aus beiden geeignet ist. Die Entscheidung sollte auf dem Volatilitäts- und Nutzungsmuster der Daten basieren. 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 einfach die Ergebnisse aus dem Cache abrufen.

Eine Anwendung kann Daten ändern, die in einem Cache gespeichert sind. Wir empfehlen jedoch, den Cache als vorübergehenden Datenspeicher zu betrachten, 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. Dies bedeutet: Wenn der Cache nicht verfügbar ist, minimieren Sie die Wahrscheinlichkeit, dass Daten verloren gehen.

Hoch dynamische Daten zwischenspeichern

Wenn Sie schnell ändernde Informationen in einem beständigen Datenspeicher speichern, kann dies 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 fast immer veraltet sind, könnte 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, hat es sich möglicherweise geändert.

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 handelt es sich bei Daten, die in einem Cache gespeichert sind, um eine Kopie der Daten, die im ursprünglichen Datenspeicher gespeichert sind. Die Daten im ursprünglichen Datenspeicher können sich ändern, nachdem sie zwischengespeichert wurden, wodurch die zwischengespeicherten Daten veraltet werden. Viele Zwischenspeicherungssysteme ermöglichen es Ihnen, den Cache so zu konfigurieren, dass Daten ablaufen, und den Zeitraum verringern, für den Daten möglicherweise nicht mehr aktuell sind.

Wenn zwischengespeicherte Daten ablaufen, werden sie aus dem Cache entfernt, und die Anwendung muss die Daten aus dem ursprünglichen Datenspeicher abrufen (sie kann die neu abgerufenen Informationen wieder im Cache speichern). Sie können eine Standardablaufrichtlinie festlegen, wenn Sie den Cache konfigurieren. In vielen Cachediensten können Sie auch den Ablaufzeitraum für einzelne Objekte festlegen, wenn Sie sie programmgesteuert im Cache speichern. Bei einigen Caches können Sie den Ablaufzeitraum als absoluter Wert oder als Gleitwert angeben, der bewirkt, dass das Element aus dem Cache entfernt wird, wenn innerhalb des angegebenen Zeitraums nicht darauf 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 Sie reduzieren die Vorteile der Verwendung des Caches. Wenn Sie den Zeitraum zu lang machen, riskieren Sie, dass die Daten veraltet werden.

Es ist auch möglich, dass der Cache möglicherweise auffüllt, wenn Daten über einen längeren Zeitraum ansässig bleiben dürfen. In diesem Fall können alle Anforderungen zum Hinzufügen neuer Elemente zum Cache dazu führen, dass einige Elemente in einem Prozess, der als Löschvorgang bezeichnet wird, forcibly entfernt werden. Cachedienste entfernen daten in der Regel auf basis der zuletzt verwendeten (LRU)-Basis, aber Sie können diese Richtlinie in der Regel 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, dem Cache ein Element hinzuzufügen, schlägt mit einer Ausnahme fehl.

Einige Zwischenspeicherungsimplementierungen stellen möglicherweise zusätzliche Eviction-Richtlinien bereit. Es gibt mehrere Arten von Eviction-Richtlinien. These include:

  • Eine zuletzt verwendete Richtlinie (in der Erwartung, dass die Daten nicht mehr benötigt werden).
  • Eine First-in-First-Out-Richtlinie (älteste Daten werden zuerst entfernt).
  • Eine explizite Entfernungsrichtlinie basierend auf einem ausgelösten Ereignis (z. B. den zu ändernden Daten).

Ungültige Daten in einem clientseitigen Cache

Daten, die in einem clientseitigen Cache gespeichert sind, werden in der Regel als außerhalb der Auspices des Diensts betrachtet, 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.

Dies bedeutet, dass es für einen Client möglich ist, der einen schlecht konfigurierten Cache verwendet, um veraltete Informationen weiterhin zu verwenden. 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 geändert haben.

Wenn Sie eine Webanwendung erstellen, die Daten über eine HTTP-Verbindung bedient, können Sie implizit einen Webclient (z. B. einen Browser oder Webproxy) erzwingen, um die neuesten Informationen abzurufen. Sie können dies tun, wenn eine Ressource durch eine Änderung des URI dieser Ressource aktualisiert wird. 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

Caches sind häufig so konzipiert, dass sie von mehreren Instanzen einer Anwendung freigegeben werden. Jede Anwendungsinstanz kann Daten im Cache lesen und ändern. Daher gelten dieselben Parallelitätsprobleme, die bei einem freigegebenen Datenspeicher auftreten, auch für einen Cache. In einer Situation, in der eine Anwendung Daten ändern muss, die im Cache gespeichert sind, 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 können Sie einen von zwei Ansätzen zur Parallelität anwenden:

  • Optimistic. Unmittelbar vor dem Aktualisieren der Daten überprüft die Anwendung, ob sich die Daten im Cache seit dem Abrufen geändert haben. Wenn die Daten immer noch identisch sind, kann die Änderung vorgenommen werden. Andernfalls muss die Anwendung entscheiden, 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.
  • Pessimistic. Wenn sie die Daten abruft, sperrt die Anwendung sie im Cache, um zu verhindern, dass eine andere Instanz sie ändert. Dieser Prozess stellt sicher, dass Kollisionen nicht auftreten können, sie können aber 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; Dies ist die Rolle des ursprünglichen Datenspeichers, aus dem der Cache aufgefüllt wird. Der ursprüngliche Datenspeicher ist dafür verantwortlich, die Persistenz der Daten sicherzustellen.

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 Dienst, der den freigegebenen Cache bereitstellt, nicht verfügbar ist. Die Anwendung sollte nicht mehr reagieren oder fehlschlagen, während der Cachedienst auf den Fortsetzen des Cachediensts wartet.

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. The Circuit-Breaker pattern is useful for handling this scenario. The service that provides the cache can be recovered, and once it becomes available, the cache can be repopulated as data is read from the original data store, following a strategy such as the Cache-aside pattern.

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 Datenspeicher wiederhergestellt wird, könnte der ursprüngliche Datenspeicher mit Anforderungen für Daten überschwemmt 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 einchecken. 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. Abbildung 3 zeigt diese Struktur.

Verwenden eines lokalen privaten Caches mit einem freigegebenen Cache

Abbildung 3: Verwenden eines lokalen privaten Caches mit einem freigegebenen Cache.

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 mit dem Schreiben an mehrere Ziele verbunden ist, kann die Replikation auf dem sekundären Server asynchron auftreten, 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 über Knoten zu partitionieren, um die Wahrscheinlichkeit von Inhalten 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 nahtloser, einzelner Cache präsentiert wird. Intern werden die Daten jedoch nach einer vordefinierten Verteilungsstrategie, die die Last gleichmäßig ausgleicht, zwischen Knoten verteilt. Weitere Informationen zu möglichen Partitionierungsstrategien finden Sie unter Datenpartitionierungsleitfaden.

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 in Verbindung 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. Dadurch kann eine Clientanwendung ein großes Mengen an Elementen in eine einzelne Anforderung packen und den Aufwand verringern, der mit der Durchführung einer großen Anzahl kleiner Anforderungen verbunden ist.

Zwischenspeichern und letztendliche Konsistenz

Damit das Zwischenspeichermuster 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 (z. B. einen replizierten Datenspeicher) implementiert, ist dies möglicherweise nicht der Fall.

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

Weitere Informationen zum Behandeln von Datenkonsistenz finden Sie in der Datenkonsistenzprimierung.

Schützen zwischengespeicherter Daten

Unabhängig vom verwendeten Cachedienst sollten Sie überlegen, wie Sie die Im Cache gespeicherten Daten vor unbefugtem Zugriff schützen. Es gibt zwei Hauptanliegen:

  • 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, der erfordert, dass Anwendungen Folgendes angeben:

  • Welche Identitäten auf Daten im Cache zugreifen können.
  • Welche Vorgänge (Lesen und Schreiben) diese Identitäten ausführen dürfen.

Um den Aufwand zu verringern, der mit dem Lesen und Schreiben von Daten verknüpft ist, kann diese Identität, nachdem eine Identität Schreib- oder Lesezugriff auf den Cache gewährt wurde, alle Daten im Cache verwenden.

Wenn Sie den Zugriff auf Teilmengen der zwischengespeicherten Daten einschränken müssen, können Sie eine der folgenden Aktionen ausführen:

  • Teilen Sie den Cache in Partitionen (mit unterschiedlichen Cacheservern), und 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, und 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 die Daten auch schützen, wenn sie in den Cache fließt und aus dem Cache. Dazu hängen Sie von den Sicherheitsfeatures ab, die von der Netzwerkinfrastruktur bereitgestellt werden, die Clientanwendungen zum Herstellen einer Verbindung mit dem Cache verwenden. Wenn der Cache mithilfe eines Standortservers innerhalb derselben Organisation implementiert wird, die die Clientanwendungen hosten, ist die Isolation des Netzwerks selbst möglicherweise nicht erforderlich, dass Sie zusätzliche Schritte ausführen. Wenn sich der Cache remote befindet und eine TCP- oder HTTP-Verbindung über ein öffentliches Netzwerk (z. B. das Internet) erfordert, sollten Sie SSL implementieren.

Überlegungen zur Implementierung der Zwischenspeicherung in Azure

Azure Cache for Redis ist eine Implementierung des Open Source Redis-Caches, der als Dienst in einem Azure-Rechenzentrum ausgeführt wird. Es stellt einen Cachedienst bereit, auf den von einer beliebigen Azure-Anwendung aus zugegriffen werden kann, unabhängig davon, ob die Anwendung als Clouddienst, eine Website oder innerhalb eines virtuellen Azure-Computers implementiert wird. Caches können von Clientanwendungen freigegeben werden, die über den entsprechenden Zugriffsschlüssel verfügen.

Azure Cache for Redis ist eine leistungsstarke Zwischenspeicherungslösung, die Verfügbarkeit, Skalierbarkeit und Sicherheit bietet. Sie wird in der Regel als Dienst ausgeführt, der sich auf einem oder mehreren dedizierten Computern erstreckt. Es versucht, so viele Informationen wie im Arbeitsspeicher zu speichern, um den schnellen Zugriff sicherzustellen. Diese Architektur soll eine geringe Latenz und einen hohen Durchsatz bieten, indem die Notwendigkeit verringert wird, langsame E/A-Vorgänge auszuführen.

Azure Cache for Redis ist mit vielen der verschiedenen APIs kompatibel, die von Clientanwendungen verwendet werden. Wenn Sie über vorhandene Anwendungen verfügen, die bereits Azure Cache für Redis verwenden, die lokal ausgeführt werden, bietet der Azure-Cache für Redis einen schnellen Migrationspfad zum Zwischenspeichern in der Cloud.

Features von Redis

Redis ist mehr als ein einfacher Cacheserver. Sie bietet eine verteilte In-Memory-Datenbank mit einem umfangreichen Befehlssatz, der viele gängige Szenarien unterstützt. Diese werden weiter unten in diesem Dokument im Abschnitt "Verwenden der Redis-Zwischenspeicherung" beschrieben. In diesem Abschnitt werden einige der wichtigsten Features zusammengefasst, die Redis bereitstellt.

Redis als In-Memory-Datenbank

Redis unterstützt Lese- und Schreibvorgänge. In Redis können Schreibvorgänge vor Systemfehlern geschützt werden, indem sie in regelmäßigen Abständen in einer lokalen Momentaufnahmedatei oder in einer Anfügeprotokolldatei gespeichert werden. Dies ist nicht der Fall in vielen Caches, die als transitory Datenspeicher betrachtet werden sollten.

Alle Schreibvorgänge sind asynchron und blockieren nicht, dass Clients Daten lesen und schreiben. Wenn Redis gestartet wird, liest er die Daten aus der Momentaufnahme oder Protokolldatei und verwendet sie zum Erstellen des Speichercaches. For more information, see Redis persistence on the Redis website.

Note

Redis garantiert nicht, dass alle Schreibvorgänge gespeichert werden, wenn ein katastrophaler Fehler auftritt, aber im schlimmsten Fall verlieren Sie möglicherweise nur ein paar Sekunden datenwert. Denken Sie daran, dass ein Cache nicht als autorisierende Datenquelle fungieren soll, und es liegt in der Verantwortung der Anwendungen, die den Cache verwenden, um sicherzustellen, dass wichtige Daten erfolgreich in einem entsprechenden Datenspeicher gespeichert werden. For more information, see the Cache-aside pattern.

Redis-Datentypen

Redis ist ein Schlüsselwertspeicher, in dem Werte einfache Typen oder komplexe Datenstrukturen wie Hashes, Listen und Mengen enthalten können. Es unterstützt eine Reihe von Atomvorgängen für diese Datentypen. Schlüssel können dauerhaft oder mit einer begrenzten Zeit-zu-Live-Zeit kategorisiert werden, an deren Zeitpunkt der Schlüssel und der entsprechende Wert automatisch aus dem Cache entfernt werden. Weitere Informationen zu Redis-Schlüsseln und -Werten finden Sie auf der Seite Eine Einführung in Redis-Datentypen und Abstraktionen auf der Redis-Website.

Redis-Replikation und Clustering

Redis unterstützt die primäre/untergeordnete Replikation, um die Verfügbarkeit und Wartung des Durchsatzes zu gewährleisten. Schreibvorgänge in einen primären Redis-Knoten werden auf mindestens einen untergeordneten Knoten repliziert. Lesevorgänge können von der primären oder einer der untergeordneten Elemente bedient werden.

Wenn Sie über eine Netzwerkpartition verfügen, können untergeordnete Elemente weiterhin Daten bereitstellen und dann beim erneuten Herstellen der Verbindung transparent neu synchronisiert werden. For further details, visit the Replication page on the Redis website.

Redis stellt außerdem Clustering bereit, mit dem Sie Daten transparent auf Servern in Shards aufteilen und die Last verteilen können. Dieses Feature verbessert die Skalierbarkeit, da neue Redis-Server hinzugefügt werden können und die Daten neu partitioniert werden können, wenn die Größe des Caches zunimmt.

Darüber hinaus kann jeder Server im Cluster mithilfe der primären/untergeordneten Replikation repliziert werden. Dadurch wird die Verfügbarkeit für jeden Knoten im Cluster sichergestellt. Weitere Informationen zum Clustering und Sharding finden Sie auf der Redis-Website auf der Redis-Website .

Redis-Speicherverwendung

Ein Redis-Cache hat eine endliche Größe, die von den ressourcen abhängt, die auf dem Hostcomputer verfügbar sind. Wenn Sie einen Redis-Server konfigurieren, können Sie den maximalen Arbeitsspeicher angeben, den er verwenden kann. Sie können einen Schlüssel auch in einem Redis-Cache so konfigurieren, dass er eine Ablaufzeit hat, nach der er automatisch aus dem Cache entfernt wird. Dieses Feature kann verhindern, dass der Speichercache mit alten oder veralteten Daten gefüllt wird.

Wenn der Speicher gefüllt wird, kann Redis Schlüssel und deren Werte automatisch entfernen, indem eine Reihe von Richtlinien folgt. Der Standardwert ist LRU (zuletzt verwendet), Aber Sie können auch andere Richtlinien auswählen, z. B. das Entfernen von Schlüsseln bei zufälliger Oder das Deaktivieren der Entfernung vollständig (in diesem Fall versucht, dem Cache Elemente hinzuzufügen, schlägt fehl, wenn er voll ist). Die Seite "Verwenden von Redis als LRU-Cache " enthält weitere Informationen.

Redis-Transaktionen und Batches

Redis ermöglicht es einer Clientanwendung, eine Reihe von Vorgängen zu übermitteln, die Daten im Cache als atome Transaktion lesen und schreiben. Alle Befehle in der Transaktion werden garantiert sequenziell ausgeführt, und zwischen anderen gleichzeitigen Clients werden keine Befehle miteinander verbunden.

Dies sind jedoch keine echten Transaktionen, da eine relationale Datenbank sie ausführen würde. Die Transaktionsverarbeitung besteht aus zwei Phasen– das erste ist, wenn die Befehle in die Warteschlange gestellt werden, und die zweite ist der Zeitpunkt, an dem die Befehle ausgeführt werden. Während der Befehlswarteschlange werden die Befehle, aus denen die Transaktion besteht, vom Client übermittelt. Wenn an diesem Punkt eine Art von Fehler auftritt (z. B. ein Syntaxfehler oder die falsche Anzahl von Parametern), lehnt Redis die Verarbeitung der gesamten Transaktion ab und verwirft sie.

Während der Ausführungsphase führt Redis jeden Befehl in der Warteschlange in Sequenz aus. Wenn während dieser Phase ein Befehl fehlschlägt, wird Redis mit dem nächsten Befehl in die Warteschlange fortgesetzt und führt keine Rollbacks der Auswirkungen von Befehlen durch, die bereits ausgeführt wurden. Diese vereinfachte Form der Transaktion trägt dazu bei, die Leistung aufrechtzuerhalten und leistungsbedingte Probleme zu vermeiden.

Redis implementiert eine Form der optimistischen Sperre, um die Konsistenz aufrechtzuerhalten. For detailed information about transactions and locking with Redis, visit the Transactions page on the Redis website.

Redis unterstützt auch nichttransactionale Batchverarbeitung von Anforderungen. Das Redis-Protokoll, das Clients zum Senden von Befehlen an einen Redis-Server verwenden, ermöglicht es einem Client, eine Reihe von Vorgängen als Teil derselben Anforderung zu senden. Dies kann dazu beitragen, die Paketfragmentierung im Netzwerk zu reduzieren. Wenn der Batch verarbeitet wird, wird jeder Befehl ausgeführt. Wenn eines dieser Befehle falsch formatiert ist, werden sie abgelehnt (was nicht mit einer Transaktion geschieht), aber die verbleibenden Befehle werden ausgeführt. Es gibt auch keine Garantie für die Reihenfolge, in der die Befehle im Batch verarbeitet werden.

Redis security

Redis konzentriert sich ausschließlich auf die Bereitstellung des schnellen Zugriffs auf Daten und ist darauf ausgelegt, innerhalb einer vertrauenswürdigen Umgebung auszuführen, auf die nur von vertrauenswürdigen Clients zugegriffen werden kann. Redis unterstützt ein eingeschränktes Sicherheitsmodell basierend auf der Kennwortauthentifizierung. (Es ist möglich, die Authentifizierung vollständig zu entfernen, obwohl dies nicht empfohlen wird.)

Alle authentifizierten Clients verwenden dasselbe globale Kennwort und haben Zugriff auf dieselben Ressourcen. Wenn Sie eine umfassendere Anmeldesicherheit benötigen, müssen Sie ihre eigene Sicherheitsebene vor dem Redis-Server implementieren, und alle Clientanforderungen sollten diese zusätzliche Ebene durchlaufen. Redis sollte nicht direkt für nicht vertrauenswürdige oder nicht authentifizierte Clients verfügbar gemacht werden.

Sie können den Zugriff auf Befehle einschränken, indem Sie sie deaktivieren oder umbenennen (und nur privilegierte Clients mit den neuen Namen bereitstellen).

Redis unterstützt keine Form der Datenverschlüsselung, sodass alle Codierungen von Clientanwendungen ausgeführt werden müssen. Darüber hinaus bietet Redis keine Art von Transportsicherheit. Wenn Sie Daten schützen müssen, während sie über das Netzwerk fließen, empfehlen wir die Implementierung eines SSL-Proxys.

For more information, visit the Redis security page on the Redis website.

Note

Azure Cache for Redis bietet eine eigene Sicherheitsebene, über die Clients eine Verbindung herstellen. Die zugrunde liegenden Redis-Server werden nicht für das öffentliche Netzwerk verfügbar gemacht.

Azure Redis-Cache

Azure Cache for Redis bietet Zugriff auf Redis-Server, die in einem Azure-Rechenzentrum gehostet werden. Sie fungiert als Fassade, die Zugangskontrolle und Sicherheit bietet. Sie können einen Cache mithilfe des Azure-Portals bereitstellen.

Das Portal stellt eine Reihe vordefinierter Konfigurationen bereit. Diese reichen von einem 53 GB Cache, der als dedizierter Dienst ausgeführt wird, der SSL-Kommunikation (für Datenschutz) und master/untergeordnete Replikation mit einer SLA (Service Level Agreement) von 99,9% Verfügbarkeit unterstützt, bis zu einem 250 MB-Cache ohne Replikation (keine Verfügbarkeitsgarantien), der auf gemeinsam genutzter Hardware ausgeführt wird.

Mithilfe des Azure-Portals können Sie auch die Entfernungsrichtlinie des Caches konfigurieren und den Zugriff auf den Cache steuern, indem Sie benutzer zu den bereitgestellten Rollen hinzufügen. Diese Rollen definieren die Vorgänge, die Mitglieder ausführen können, einschließlich Besitzer, Mitwirkender und Leser. Beispielsweise haben Mitglieder der Rolle "Besitzer" die vollständige Kontrolle über den Cache (einschließlich Sicherheit) und deren Inhalte, Mitglieder der Rolle "Mitwirkender" können Informationen im Cache lesen und schreiben, und Mitglieder der Rolle "Leser" können nur Daten aus dem Cache abrufen.

Die meisten Administrativen Aufgaben werden über das Azure-Portal ausgeführt. Aus diesem Grund sind viele der administrativen Befehle, die in der Standardversion von Redis verfügbar sind, nicht verfügbar, einschließlich der Möglichkeit, die Konfiguration programmgesteuert zu ändern, den Redis-Server herunterzufahren, zusätzliche untergeordnete Elemente zu konfigurieren oder Daten auf dem Datenträger zu speichern.

Das Azure-Portal enthält eine bequeme grafische Anzeige, mit der Sie die Leistung des Caches überwachen können. Sie können z. B. die Anzahl der hergestellten Verbindungen, die Anzahl der ausgeführten Anforderungen, das Lese- und Schreibvolumen sowie die Anzahl der Cachetreffer im Vergleich zu Cachefehlern anzeigen. Mithilfe dieser Informationen können Sie die Effektivität des Caches ermitteln und bei Bedarf zu einer anderen Konfiguration wechseln oder die Äumungsrichtlinie ändern.

Darüber hinaus können Sie Benachrichtigungen erstellen, die E-Mail-Nachrichten an einen Administrator senden, wenn eine oder mehrere kritische Metriken außerhalb eines erwarteten Bereichs liegen. Sie können beispielsweise einen Administrator benachrichtigen, wenn die Anzahl der Cachefehler in der letzten Stunde einen angegebenen Wert überschreitet, da der Cache möglicherweise zu klein ist oder Daten zu schnell entfernt werden.

Sie können auch die CPU-, Arbeitsspeicher- und Netzwerkauslastung für den Cache überwachen.

Weitere Informationen und Beispiele zum Erstellen und Konfigurieren eines Azure-Caches für Redis finden Sie auf der Seite "Lap around Azure Cache for Redis" im Azure-Blog.

Zwischenspeichern des Sitzungszustands und der HTML-Ausgabe

Wenn Sie ASP.NET Webanwendungen erstellen, die mit Azure-Webrollen ausgeführt werden, können Sie Sitzungsstatusinformationen und HTML-Ausgabe in einem Azure-Cache für Redis speichern. Der Sitzungsstatusanbieter für Azure Cache für Redis ermöglicht es Ihnen, Sitzungsinformationen zwischen verschiedenen Instanzen einer ASP.NET Webanwendung freizugeben und ist in Webfarmsituationen sehr nützlich, in denen client-server affinity nicht verfügbar ist und Sitzungsdaten im Arbeitsspeicher zwischengespeichert werden.

Die Verwendung des Sitzungszustandsanbieters mit Azure Cache for Redis bietet mehrere Vorteile, darunter:

  • Freigeben des Sitzungszustands mit einer großen Anzahl von Instanzen von ASP.NET Webanwendungen.
  • Verbesserte Skalierbarkeit.
  • Unterstützung des kontrollierten, gleichzeitigen Zugriffs auf die gleichen Sitzungszustandsdaten für mehrere Leser und einen einzelnen Writer.
  • Verwenden der Komprimierung zum Speichern von Arbeitsspeicher und Zur Verbesserung der Netzwerkleistung.

Weitere Informationen finden Sie unter ASP.NET Sitzungszustandsanbieter für Azure Cache für Redis.

Note

Verwenden Sie nicht den Sitzungszustandsanbieter für Azure Cache für Redis mit ASP.NET Anwendungen, die außerhalb der Azure-Umgebung ausgeführt werden. Die Latenz des Zugriffs auf den Cache von außerhalb von Azure kann die Leistungsvorteile des Zwischenspeicherns von Daten beseitigen.

Ebenso können Sie mit dem Ausgabecacheanbieter für Azure Cache for Redis die von einer ASP.NET Webanwendung generierten HTTP-Antworten speichern. Die Verwendung des Ausgabecacheanbieters mit Azure Cache for Redis kann die Reaktionszeiten von Anwendungen verbessern, die komplexe HTML-Ausgabe rendern. Anwendungsinstanzen, die ähnliche Antworten generieren, können die freigegebenen Ausgabefragmente im Cache verwenden, anstatt diese HTML-Ausgabe neu zu generieren. Weitere Informationen finden Sie unter ASP.NET Ausgabecacheanbieter für Azure Cache for Redis.

Erstellen eines benutzerdefinierten Redis-Caches

Azure Cache for Redis fungiert als Fassade für die zugrunde liegenden Redis-Server. Wenn Sie eine erweiterte Konfiguration benötigen, die nicht vom Azure Redis-Cache (z. B. einem Cache größer als 53 GB) abgedeckt ist, können Sie Ihre eigenen Redis-Server mithilfe von Azure Virtual Machines erstellen und hosten.

Dies ist ein potenziell komplexer Prozess, da Sie möglicherweise mehrere virtuelle Computer erstellen müssen, um als primäre und untergeordnete Knoten zu fungieren, wenn Sie die Replikation implementieren möchten. Darüber hinaus benötigen Sie, wenn Sie einen Cluster erstellen möchten, mehrere Primar- und untergeordnete Server. Eine minimale Clusterreplikationstopologie, die ein hohes Maß an Verfügbarkeit und Skalierbarkeit bietet, umfasst mindestens sechs VMs, die als drei Paare von primären/untergeordneten Servern organisiert sind (ein Cluster muss mindestens drei primäre Knoten enthalten).

Jedes primäre/untergeordnete Paar sollte nah zusammen angeordnet werden, um die Latenz zu minimieren. Jede Gruppe von Paaren kann jedoch in verschiedenen Azure-Rechenzentren in verschiedenen Regionen ausgeführt werden, wenn Sie zwischengespeicherte Daten in der Nähe der Anwendungen suchen möchten, die sie am wahrscheinlichsten verwenden. Ein Beispiel für das Erstellen und Konfigurieren eines Redis-Knotens, der als Azure-VM ausgeführt wird, finden Sie unter Running Redis auf einer CentOS Linux-VM in Azure.

Note

Wenn Sie Ihren eigenen Redis-Cache auf diese Weise implementieren, sind Sie für die Überwachung, Verwaltung und Sicherung des Diensts verantwortlich.

Partitionieren eines Redis-Caches

Die Partitionierung des Caches umfasst das Aufteilen des Caches auf mehrere Computer. Diese Struktur bietet Ihnen mehrere Vorteile gegenüber der Verwendung eines einzelnen Cacheservers, einschließlich:

  • Erstellen eines Caches, der viel größer ist als auf einem einzelnen Server gespeichert werden kann.
  • Verteilen von Daten über Server hinweg und verbessern die Verfügbarkeit. Wenn ein Server fehlschlägt oder nicht darauf zugreifen kann, sind die darin gespeicherten Daten nicht verfügbar, aber auf die Daten auf den verbleibenden Servern kann weiterhin zugegriffen werden. Für einen Cache ist dies nicht entscheidend, da die zwischengespeicherten Daten nur eine vorübergehende Kopie der Daten sind, die in einer Datenbank gespeichert sind. Zwischengespeicherte Daten auf einem Server, auf den nicht zugegriffen werden kann, können stattdessen auf einem anderen Server zwischengespeichert werden.
  • Die Verteilung der Last über Server hinweg, wodurch die Leistung und Skalierbarkeit verbessert werden.
  • Geolocating data close to the users that access it, so reducing latency.

Bei einem Cache wird die am häufigsten verwendete Form der Partitionierung sharding. In dieser Strategie ist jede Partition (oder Shard) ein Redis-Cache in eigener Eigenschaft. Daten werden mithilfe von Shardinglogik an eine bestimmte Partition weitergeleitet, die eine Vielzahl von Ansätzen zum Verteilen der Daten verwenden kann. The Sharding pattern provides more information about implementing sharding.

Zum Implementieren der Partitionierung in einem Redis-Cache können Sie eine der folgenden Ansätze verwenden:

  • Serverseitiges Abfragerouting. Bei dieser Technik sendet eine Clientanwendung eine Anforderung an einen der Redis-Server, die den Cache (wahrscheinlich den nächstgelegenen Server) umfassen. Jeder Redis-Server speichert Metadaten, die die darin enthaltene Partition beschreiben, und enthält auch Informationen dazu, welche Partitionen sich auf anderen Servern befinden. Der Redis-Server untersucht die Clientanforderung. Wenn sie lokal aufgelöst werden kann, führt sie den angeforderten Vorgang aus. Andernfalls wird die Anforderung an den entsprechenden Server weitergeleitet. Dieses Modell wird von Redis Clustering implementiert und wird auf der Redis-Cluster-Lernprogrammseite auf der Redis-Website ausführlicher beschrieben. Redis-Clustering ist für Clientanwendungen transparent, und zusätzliche Redis-Server können dem Cluster (und den neu partitionierten Daten) hinzugefügt werden, ohne dass Sie die Clients neu konfigurieren müssen.
  • Client-side partitioning. In diesem Modell enthält die Clientanwendung Logik (möglicherweise in Form einer Bibliothek), die Anforderungen an den entsprechenden Redis-Server weiter leitet. Dieser Ansatz kann mit Azure Cache für Redis verwendet werden. Erstellen Sie mehrere Azure-Cache für Redis (eine für jede Datenpartition), und implementieren Sie die clientseitige Logik, mit der die Anforderungen an den richtigen Cache weitergeleitet werden. Wenn sich das Partitionierungsschema ändert (wenn z. B. zusätzlicher Azure-Cache für Redis erstellt wird), müssen Clientanwendungen möglicherweise neu konfiguriert werden.
  • Proxy-assisted partitioning. In diesem Schema senden Clientanwendungen Anforderungen an einen zwischengeschalteten Proxydienst, der versteht, wie die Daten partitioniert werden, und leitet dann die Anforderung an den entsprechenden Redis-Server weiter. Dieser Ansatz kann auch mit Azure Cache für Redis verwendet werden. Der Proxydienst kann als Azure-Clouddienst implementiert werden. Für diesen Ansatz ist eine zusätzliche Komplexitätsstufe erforderlich, um den Dienst zu implementieren, und Anforderungen erfordern möglicherweise länger als die Verwendung clientseitiger Partitionierung.

Die Seitenpartitionierung: Wie Daten zwischen mehreren Redis-Instanzen auf der Redis-Website aufgeteilt werden, finden Sie weitere Informationen zur Implementierung der Partitionierung mit Redis.

Implementieren von Redis-Cacheclientanwendungen

Redis unterstützt Clientanwendungen, die in zahlreichen Programmiersprachen geschrieben wurden. Wenn Sie neue Anwendungen mithilfe von .NET Framework erstellen, wird empfohlen, die StackExchange.Redis-Clientbibliothek zu verwenden. Diese Bibliothek stellt ein .NET Framework-Objektmodell bereit, das die Details zum Herstellen einer Verbindung mit einem Redis-Server abstrahiert, Befehle sendet und Antworten empfängt. Es ist in Visual Studio als NuGet-Paket verfügbar. Sie können dieselbe Bibliothek verwenden, um eine Verbindung mit einem Azure-Cache für Redis oder einem benutzerdefinierten Redis-Cache herzustellen, der auf einer VM gehostet wird.

Zum Herstellen einer Verbindung mit einem Redis-Server verwenden Sie die statische Connect Methode der ConnectionMultiplexer Klasse. Die von dieser Methode erstellte Verbindung ist so konzipiert, dass sie während der gesamten Lebensdauer der Clientanwendung verwendet wird, und dieselbe Verbindung kann von mehreren gleichzeitigen Threads verwendet werden. Stellen Sie bei jedem Ausführen eines Redis-Vorgangs keine Verbindung wieder her und trennen Sie sie nicht, da dies die Leistung beeinträchtigen kann.

Sie können die Verbindungsparameter angeben, z. B. die Adresse des Redis-Hosts und das Kennwort. Wenn Sie Azure Cache für Redis verwenden, ist das Kennwort entweder der primäre oder sekundäre Schlüssel, der mit dem Azure-Portal für Azure Cache für Redis generiert wird.

Nachdem Sie eine Verbindung mit dem Redis-Server hergestellt haben, können Sie ein Handle für die Redis-Datenbank abrufen, die als Cache fungiert. Die Redis-Verbindung stellt die GetDatabase Methode bereit, um dies zu tun. Anschließend können Sie Elemente aus dem Cache abrufen und Daten im Cache speichern, indem Sie die StringGet Methoden verwenden StringSet . Diese Methoden erwarten einen Schlüssel als Parameter und geben das Element entweder im Cache mit einem übereinstimmenden Wert (StringGet) zurück oder fügen das Element mit diesem Schlüssel (StringSet) dem Cache hinzu.

Je nach Standort des Redis-Servers können viele Vorgänge eine Latenz verursachen, während eine Anforderung an den Server übertragen wird und eine Antwort an den Client zurückgegeben wird. Die StackExchange-Bibliothek stellt asynchrone Versionen vieler Methoden bereit, die verfügbar gemacht werden, damit Clientanwendungen reaktionsfähig bleiben. Diese Methoden unterstützen das aufgabenbasierte asynchrone Muster in .NET Framework.

Der folgende Codeausschnitt zeigt eine Methode mit dem Namen RetrieveItem. Es veranschaulicht eine Implementierung des Cache-Aside-Musters basierend auf Redis und der StackExchange-Bibliothek. Die Methode verwendet einen Zeichenfolgenschlüsselwert und versucht, das entsprechende Element aus dem Redis-Cache abzurufen, indem die StringGetAsync Methode aufgerufen wird (die asynchrone Version von StringGet).

Wenn das Element nicht gefunden wird, wird es mithilfe der GetItemFromDataSourceAsync Methode (eine lokale Methode und nicht Teil der StackExchange-Bibliothek) aus der zugrunde liegenden Datenquelle abgerufen. Anschließend wird er mithilfe der StringSetAsync Methode dem Cache hinzugefügt, damit er beim nächsten Mal schneller abgerufen werden kann.

// 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;
}

Die StringGet Methoden und StringSet Methoden sind nicht auf das Abrufen oder Speichern von Zeichenfolgenwerten beschränkt. Sie können jedes Element verwenden, das als Bytearray serialisiert wird. Wenn Sie ein .NET-Objekt speichern müssen, können Sie es als Bytestream serialisieren und die StringSet Methode verwenden, um es in den Cache zu schreiben.

Ebenso können Sie ein Objekt aus dem Cache lesen, indem Sie die StringGet Methode verwenden und es als .NET-Objekt deserialisieren. Der folgende Code zeigt eine Reihe von Erweiterungsmethoden für die IDatabase-Schnittstelle (die GetDatabase Methode einer Redis-Verbindung gibt ein IDatabase Objekt zurück), und einige Beispielcode, der diese Methoden zum Lesen und Schreiben eines BlogPost Objekts in den Cache verwendet:

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;
    }
}

Der folgende Code veranschaulicht eine Methode mit dem Namen RetrieveBlogPost , die diese Erweiterungsmethoden verwendet, um ein serialisierbares BlogPost Objekt nach dem Cachemuster zu lesen und in den Cache zu schreiben:

// 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 unterstützt Befehlspipelineing, wenn eine Clientanwendung mehrere asynchrone Anforderungen sendet. Redis kann die Anforderungen mit derselben Verbindung multiplex statt Befehle in einer strengen Reihenfolge empfangen und beantworten.

Dieser Ansatz trägt dazu bei, die Latenz zu reduzieren, indem das Netzwerk effizienter genutzt wird. Der folgende Codeausschnitt zeigt ein Beispiel, das die Details von zwei Kunden gleichzeitig abruft. Der Code sendet zwei Anforderungen und führt dann eine andere Verarbeitung (nicht angezeigt) aus, bevor sie auf den Empfang der Ergebnisse wartet. Die Wait Methode des Cacheobjekts ähnelt der .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);

Weitere Informationen zum Schreiben von Clientanwendungen, die den Azure-Cache für Redis verwenden können, finden Sie in der Dokumentation zu Azure Cache for Redis. More information is also available at StackExchange.Redis.

Die Seitenpipelinen und Multiplexer auf derselben Website bieten weitere Informationen zu asynchronen Vorgängen und Pipelining mit Redis und der StackExchange-Bibliothek.

Verwenden der Redis-Zwischenspeicherung

Die einfachste Verwendung von Redis zum Zwischenspeichern ist Schlüsselwertpaare, bei denen der Wert eine nicht interpretierte Zeichenfolge mit beliebiger Länge ist, die beliebige Binärdaten enthalten kann. (Es handelt sich im Wesentlichen um ein Bytearray, das als Zeichenfolge behandelt werden kann). Dieses Szenario wurde im Abschnitt Implementieren von Redis Cache-Clientanwendungen weiter oben in diesem Artikel veranschaulicht.

Beachten Sie, dass Schlüssel auch nicht interpretierte Daten enthalten, sodass Sie alle binären Informationen als Schlüssel verwenden können. Je länger der Schlüssel ist, desto mehr Speicherplatz dauert es zum Speichern, und je länger es dauert, um Nachschlagevorgänge auszuführen. Entwerfen Sie Für Benutzerfreundlichkeit und einfache Wartung Ihre Keyspace sorgfältig und verwenden Sie aussagekräftige (aber nicht ausführliche) Schlüssel.

Verwenden Sie beispielsweise strukturierte Schlüssel wie "customer:100", um den Schlüssel für den Kunden mit der ID 100 und nicht einfach "100" darzustellen. Mit diesem Schema können Sie ganz einfach zwischen Werten unterscheiden, die unterschiedliche Datentypen speichern. Sie können z. B. auch den Schlüssel "orders:100" verwenden, um den Schlüssel für die Bestellung mit der ID 100 darzustellen.

Abgesehen von eindimensionalen binären Zeichenfolgen kann ein Wert in einem Redis-Schlüsselwertpaar auch strukturiertere Informationen enthalten, einschließlich Listen, Sets (sortiert und unsortiert) und Hashes. Redis bietet einen umfassenden Befehlssatz, der diese Typen bearbeiten kann, und viele dieser Befehle sind für .NET Framework-Anwendungen über eine Clientbibliothek wie StackExchange verfügbar. Die Seite Eine Einführung in Redis-Datentypen und Abstraktionen auf der Redis-Website bietet eine detailliertere Übersicht über diese Typen und die Befehle, mit denen Sie sie bearbeiten können.

In diesem Abschnitt werden einige häufige Anwendungsfälle für diese Datentypen und Befehle zusammengefasst.

Ausführen von Atom- und Batchvorgängen

Redis unterstützt eine Reihe von Atom-Get-and-Set-Operationen für Zeichenfolgenwerte. Diese Vorgänge entfernen die möglichen Renngefahren, die bei der Verwendung von separaten GET und SET Befehlen auftreten können. Die verfügbaren Vorgänge umfassen:

  • INCR, INCRBY, DECRund DECRBY, die atomische Inkrementierungs- und Dekrementierungsvorgänge für ganzzahlige numerische Datenwerte ausführen. Die StackExchange-Bibliothek stellt überladene Versionen der IDatabase.StringIncrementAsync Und IDatabase.StringDecrementAsync Methoden bereit, um diese Vorgänge auszuführen und den resultierenden Wert zurückzugeben, der im Cache gespeichert ist. Der folgende Codeausschnitt veranschaulicht die Verwendung dieser Methoden:

    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, der den Wert abruft, der einem Schlüssel zugeordnet ist, und ändert ihn in einen neuen Wert. Die StackExchange-Bibliothek stellt diesen Vorgang über die IDatabase.StringGetSetAsync Methode zur Verfügung. Der folgende Codeausschnitt zeigt ein Beispiel für diese Methode. Dieser Code gibt den aktuellen Wert zurück, der dem Schlüssel "data:counter" aus dem vorherigen Beispiel zugeordnet ist. Anschließend wird der Wert für diesen Schlüssel wieder auf Null zurückgesetzt, alles im Rahmen desselben Vorgangs:

    ConnectionMultiplexer redisHostConnection = ...;
    IDatabase cache = redisHostConnection.GetDatabase();
    ...
    string oldValue = await cache.StringGetSetAsync("data:counter", 0);
    
  • MGET und MSET, die einen Satz von Zeichenfolgenwerten als einzelnen Vorgang zurückgeben oder ändern können. Die IDatabase.StringGetAsync Methoden IDatabase.StringSetAsync und Methoden werden überladen, um diese Funktionalität zu unterstützen, wie im folgenden Beispiel gezeigt:

    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);
    
    

Sie können auch mehrere Vorgänge in einer einzelnen Redis-Transaktion kombinieren, wie im Abschnitt "Redis-Transaktionen und Batches" weiter oben in diesem Artikel beschrieben. 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 denen ä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 wert, der von der ITransaction.Execute Methode zurückgegeben wird, gibt an, ob die Transaktion erfolgreich erstellt wurde (true) oder ob sie fehlgeschlagen ist (false).

Der folgende Codeausschnitt zeigt ein Beispiel, in dem zwei Indikatoren als Teil derselben Transaktion erhöht und verringert werden:

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);

Denken Sie daran, dass Redis-Transaktionen im Gegensatz zu Transaktionen in relationalen Datenbanken sind. Mit der Execute Methode werden einfach alle Befehle in die Warteschlange gestellt, aus denen die auszuführende Transaktion besteht, und wenn eine der Befehle falsch formatiert ist, wird die Transaktion beendet. Wenn alle Befehle erfolgreich in die Warteschlange gestellt wurden, wird jeder Befehl asynchron ausgeführt.

Wenn ein Befehl fehlschlägt, fahren die anderen weiterhin mit der Verarbeitung fort. If you need to verify that a command has completed successfully, you must fetch the results of the command by using the Result property of the corresponding task, as shown in the example above. Reading the Result property will block the calling thread until the task has completed.

Weitere Informationen finden Sie unter Transaktionen in Redis.

Beim Ausführen von Batchvorgängen können Sie die IBatch Schnittstelle der StackExchange-Bibliothek verwenden. Diese Schnittstelle bietet Zugriff auf eine Reihe von Methoden, die denen ähneln, auf die über die IDatabase Schnittstelle zugegriffen wird, mit der Ausnahme, dass alle Methoden asynchron sind.

Sie erstellen ein IBatch Objekt mithilfe der IDatabase.CreateBatch Methode und führen dann den Batch mithilfe der IBatch.Execute Methode aus, wie im folgenden Beispiel gezeigt. Dieser Code legt einfach einen Zeichenfolgenwert fest, erhöht und erhöht dieselben Leistungsindikatoren, die im vorherigen Beispiel verwendet werden, und zeigt die Ergebnisse an:

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);

Es ist wichtig zu verstehen, dass im Gegensatz zu einer Transaktion, wenn ein Befehl in einem Batch fehlschlägt, da er falsch formatiert ist, die anderen Befehle möglicherweise weiterhin ausgeführt werden. Die IBatch.Execute Methode gibt keinen Hinweis auf Erfolg oder Fehler zurück.

Ausführen von Feuer- und Vergessen-Cachevorgängen

Redis unterstützt Feuer- und Vergessen-Vorgänge mithilfe von Befehlskennzeichen. In diesem Fall initiiert der Client einfach einen Vorgang, hat aber kein Interesse an dem Ergebnis und wartet nicht, bis der Befehl abgeschlossen ist. Das folgende Beispiel zeigt, wie Sie den BEFEHL INCR als Auslösen- und Vergessen-Vorgang ausführen:

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

Automatisches Ablaufen von Schlüsseln angeben

Wenn Sie ein Element in einem Redis-Cache speichern, können Sie ein Timeout angeben, nach dem das Element automatisch aus dem Cache entfernt wird. Sie können auch abfragen, wie viel mehr Zeit ein Schlüssel hat, bevor er abläuft, indem Sie den TTL Befehl verwenden. Dieser Befehl ist für StackExchange-Anwendungen mithilfe der IDatabase.KeyTimeToLive Methode 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:

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");

Sie können die Ablaufzeit auch auf ein bestimmtes Datum und eine bestimmte Uhrzeit festlegen, indem Sie den Befehl "EXPIRE" verwenden, der in der StackExchange-Bibliothek als KeyExpireAsync Methode verfügbar ist:

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

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.

Verwenden von Tags zum Querkorrelieren zwischengespeicherter Elemente

Ein Redis-Satz ist eine Sammlung mehrerer Elemente, die einen einzelnen Schlüssel gemeinsam verwenden. Mit dem SADD-Befehl können Sie einen Satz erstellen. Sie können die Elemente in einem Satz mithilfe des BEFEHLS SMEMBERS abrufen. Die StackExchange-Bibliothek implementiert den SADD-Befehl mit der IDatabase.SetAddAsync Methode und den SMEMBERS-Befehl mit der IDatabase.SetMembersAsync Methode.

Sie können vorhandene Sets auch kombinieren, um neue Sätze zu erstellen, indem Sie die Befehle SDIFF (Set Difference), SINTER (set intersection) und SUNION (set union) verwenden. Die StackExchange-Bibliothek vereint diese Vorgänge in der IDatabase.SetCombineAsync Methode. Der erste Parameter für diese Methode gibt den auszuführenden Setvorgang an.

Die folgenden Codeausschnitte zeigen, wie Sets hilfreich sein können, um Sammlungen verwandter Elemente schnell zu speichern und abzurufen. Dieser Code verwendet den BlogPost Typ, der im Abschnitt "Implementieren von Redis Cache Client Applications" weiter oben in diesem Artikel beschrieben wurde.

Ein BlogPost Objekt enthält vier Felder– eine ID, einen Titel, eine Bewertungsbewertung und eine Sammlung von Tags. Der erste Codeausschnitt unten zeigt die Beispieldaten, die zum Auffüllen einer C#-Liste von BlogPost Objekten verwendet werden:

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
}

Sie können die Tags für jedes BlogPost Objekt als Satz in einem Redis-Cache speichern und jeden Satz der ID der .BlogPost Auf diese Weise kann eine Anwendung schnell alle Tags finden, die zu einem bestimmten Blogbeitrag gehören. Um die Suche in entgegengesetzter Richtung zu ermöglichen und alle Blogbeiträge zu finden, die ein bestimmtes Tag freigeben, können Sie einen weiteren Satz erstellen, der die Blogbeiträge enthält, die auf die Tag-ID im Schlüssel verweisen:

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);
    }
}

Mit diesen Strukturen können Sie viele häufig verwendete Abfragen sehr effizient ausführen. Sie können beispielsweise alle Tags für Blogbeitrag 1 wie folgt finden und anzeigen:

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

Sie finden alle Tags, die für Blogbeitrag 1 und Blogbeitrag 2 gemeinsam sind, indem Sie einen satz Schnittmengenvorgang wie folgt ausführen:

// 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);
}

Außerdem finden Sie alle Blogbeiträge, die ein bestimmtes Tag enthalten:

// 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);
}

Zuletzt geöffnete Elemente suchen

Eine häufige Aufgabe, die für viele Anwendungen erforderlich ist, besteht darin, die zuletzt zugegriffenen Elemente zu finden. Eine Blogwebsite kann beispielsweise Informationen zu den zuletzt gelesenen Blogbeiträgen anzeigen.

Sie können diese Funktionalität mithilfe einer Redis-Liste implementieren. Eine Redis-Liste enthält mehrere Elemente, die denselben Schlüssel gemeinsam verwenden. Die Liste fungiert als doppelt beendete Warteschlange. Sie können Elemente an ein Ende der Liste übertragen, indem Sie die Befehle LPUSH (linker Push) und RPUSH (rechtes Push) verwenden. Sie können Elemente aus beiden Enden der Liste abrufen, indem Sie die Befehle LPOP und RPOP verwenden. Sie können auch eine Reihe von Elementen zurückgeben, indem Sie die Befehle LRANGE und RRANGE verwenden.

Die folgenden Codeausschnitte zeigen, wie Sie diese Vorgänge mithilfe der StackExchange-Bibliothek ausführen können. Dieser Code verwendet den BlogPost Typ aus den vorherigen Beispielen. Wenn ein Blogbeitrag von einem Benutzer gelesen wird, verschiebt die IDatabase.ListLeftPushAsync Methode den Titel des Blogbeitrags auf eine Liste, die dem Schlüssel "blog:recent_posts" im Redis-Cache zugeordnet ist.

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

Da mehr Blogbeiträge gelesen werden, werden ihre Titel in die gleiche Liste verschoben. Die Liste wird nach der Reihenfolge sortiert, in der die Titel hinzugefügt wurden. Die zuletzt gelesenen Blogbeiträge befinden sich am linken Ende der Liste. (Wenn derselbe Blogbeitrag mehrmals gelesen wird, enthält er mehrere Einträge in der Liste.)

Sie können die Titel der zuletzt gelesenen Beiträge mithilfe der IDatabase.ListRange Methode anzeigen. Diese Methode verwendet den Schlüssel, der die Liste, einen Ausgangspunkt und einen Endpunkt enthält. Der folgende Code ruft die Titel der 10 Blogbeiträge (Elemente von 0 bis 9) am linken Ende der Liste ab:

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

Beachten Sie, dass die ListRangeAsync Methode keine Elemente aus der Liste entfernt. Dazu können Sie die IDatabase.ListLeftPopAsync Und-Methoden IDatabase.ListRightPopAsync verwenden.

Um zu verhindern, dass die Liste unbegrenzt wächst, können Sie Elemente in regelmäßigen Abständen durch Kürzen der Liste ausschneiden. Der folgende Codeausschnitt zeigt Ihnen, wie Sie alle fünf Linkselemente aus der Liste entfernen:

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

Implementieren einer Geschäftsleitung

Standardmäßig werden die Elemente in einer Gruppe nicht in einer bestimmten Reihenfolge aufbewahrt. Sie können einen sortierten Satz mithilfe des ZADD-Befehls (der IDatabase.SortedSetAdd Methode in der StackExchange-Bibliothek) erstellen. Die Elemente werden mithilfe eines numerischen Werts sortiert, der als Score bezeichnet wird, der als Parameter für den Befehl bereitgestellt wird.

Der folgende Codeausschnitt fügt den Titel eines Blogbeitrags zu einer sortierten Liste hinzu. In diesem Beispiel verfügt jeder Blogbeitrag auch über ein Bewertungsfeld, das die Rangfolge des Blogbeitrags enthält.

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);

Mit der IDatabase.SortedSetRangeByRankWithScores Methode können Sie die Titel und Bewertungen des Blogbeitrags in aufsteigender Bewertungsreihenfolge abrufen:

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

Note

Die StackExchange-Bibliothek stellt auch die IDatabase.SortedSetRangeByRankAsync Methode bereit, die die Daten in der Bewertungsreihenfolge zurückgibt, aber nicht die Ergebnisse zurückgibt.

Sie können elemente auch in absteigender Reihenfolge von Bewertungen abrufen und die Anzahl der elemente einschränken, die zurückgegeben werden, indem Sie der Methode zusätzliche Parameter IDatabase.SortedSetRangeByRankWithScoresAsync bereitstellen. Im nächsten Beispiel werden die Titel und Bewertungen der top 10 bewerteten Blogbeiträge angezeigt:

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

Im nächsten Beispiel wird die Methode verwendet, mit der IDatabase.SortedSetRangeByScoreWithScoresAsync Sie die Elemente einschränken können, die auf diejenigen zurückgegeben werden, die innerhalb eines bestimmten Bewertungsbereichs liegen:

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

Nachricht mithilfe von Kanälen

Neben der Funktion als Datencache bietet ein Redis-Server Messaging über einen leistungsfähigen Herausgeber-/Abonnentenmechanismus. Clientanwendungen können einen Kanal abonnieren, und andere Anwendungen oder Dienste können Nachrichten im Kanal veröffentlichen. Wenn Sie Anwendungen abonnieren, erhalten Sie diese Nachrichten und können sie verarbeiten.

Redis stellt den Befehl SUBSCRIBE für Clientanwendungen bereit, die zum Abonnieren von Kanälen verwendet werden. Dieser Befehl erwartet den Namen eines oder mehrerer Kanäle, in denen die Anwendung Nachrichten akzeptiert. Die StackExchange-Bibliothek enthält die ISubscription Schnittstelle, über die eine .NET Framework-Anwendung Kanäle abonnieren und veröffentlichen kann.

Sie erstellen ein ISubscription Objekt mithilfe der GetSubscriber Methode der Verbindung mit dem Redis-Server. Anschließend lauschen Sie mithilfe der SubscribeAsync Methode dieses Objekts auf Nachrichten in einem Kanal. Das folgende Codebeispiel zeigt, wie Sie einen Kanal mit dem Namen "messages:blogPosts" abonnieren:

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

Der erste Parameter für die Subscribe Methode ist der Name des Kanals. Dieser Name folgt den gleichen Konventionen, die von Schlüsseln im Cache verwendet werden. Der Name kann beliebige Binärdaten enthalten, es wird jedoch empfohlen, relativ kurze, aussagekräftige Zeichenfolgen zu verwenden, um eine gute Leistung und Verhaltbarkeit zu gewährleisten.

Beachten Sie auch, dass der von Kanälen verwendete Namespace von dem, der von Schlüsseln verwendet wird, getrennt ist. Dies bedeutet, dass Sie Kanäle und Schlüssel haben können, die denselben Namen haben, obwohl dies dazu führen kann, dass Der Anwendungscode schwieriger verwaltet werden kann.

Der zweite Parameter ist ein Aktionsdelegat. Dieser Delegat wird asynchron ausgeführt, wenn eine neue Nachricht im Kanal angezeigt wird. In diesem Beispiel wird einfach die Meldung auf der Konsole angezeigt (die Nachricht enthält den Titel eines Blogbeitrags).

Zum Veröffentlichen in einem Kanal kann eine Anwendung den Befehl Redis PUBLISH verwenden. Die StackExchange-Bibliothek stellt die IServer.PublishAsync Methode zum Ausführen dieses Vorgangs bereit. Der nächste Codeausschnitt zeigt, wie eine Nachricht im Kanal "messages:blogPosts" veröffentlicht wird:

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

Es gibt mehrere Punkte, die Sie über den Mechanismus zum Veröffentlichen/Abonnieren verstehen sollten:

  • Mehrere Abonnenten können denselben Kanal abonnieren, und alle empfangen die Nachrichten, die in diesem Kanal veröffentlicht werden.
  • Abonnenten erhalten nur Nachrichten, die veröffentlicht wurden, nachdem sie abonniert wurden. Kanäle werden nicht gepuffert, und sobald eine Nachricht veröffentlicht wurde, überträgt die Redis-Infrastruktur die Nachricht an jeden Abonnenten und entfernt sie dann.
  • Standardmäßig werden Nachrichten von Abonnenten in der Reihenfolge empfangen, in der sie gesendet werden. In einem hochaktiven System mit einer großen Anzahl von Nachrichten und vielen Abonnenten und Herausgebern kann die garantierte sequenzielle Zustellung von Nachrichten die Leistung des Systems verlangsamen. Wenn jede Nachricht unabhängig ist und die Reihenfolge unwichtig ist, können Sie die gleichzeitige Verarbeitung durch das Redis-System aktivieren, wodurch die Reaktionsfähigkeit verbessert werden kann. Sie können dies in einem StackExchange-Client erreichen, indem Sie den PreserveAsyncOrder der Vom Abonnenten verwendeten Verbindung auf "false" festlegen:
ConnectionMultiplexer redisHostConnection = ...;
redisHostConnection.PreserveAsyncOrder = false;
ISubscriber subscriber = redisHostConnection.GetSubscriber();

Serialization considerations

Wenn Sie ein Serialisierungsformat auswählen, sollten Sie kompromisse zwischen Leistung, Interoperabilität, Versionsverwaltung, Kompatibilität mit vorhandenen Systemen, Datenkomprimierung und Arbeitsspeicheraufwand berücksichtigen. Wenn Sie die Leistung bewerten, denken Sie daran, dass Benchmarks stark vom Kontext abhängig sind. Möglicherweise spiegeln sie Ihre tatsächliche Arbeitsauslastung nicht wider und erwägen möglicherweise keine neueren Bibliotheken oder Versionen. Für alle Szenarien gibt es keinen einzigen "schnellsten" Serialisierer.

Zu den zu berücksichtigenden Optionen gehören:

  • Protocol Buffers (also called protobuf) is a serialization format developed by Google for serializing structured data efficiently. Es verwendet stark typierte Definitionsdateien, um Nachrichtenstrukturen zu definieren. Diese Definitionsdateien werden dann zum sprachspezifischen Code zum Serialisieren und Deserialisieren von Nachrichten kompiliert. Protobuf kann über vorhandene RPC-Mechanismen verwendet werden oder einen RPC-Dienst generieren.

  • Apache Thrift uses a similar approach, with strongly typed definition files and a compilation step to generate the serialization code and RPC services.

  • Apache Avro provides similar functionality to Protocol Buffers and Thrift, but there's no compilation step. Serialisierte Daten enthalten stattdessen immer ein Schema, das die Struktur beschreibt.

  • JSON is an open standard that uses human-readable text fields. Sie verfügt über eine breite plattformübergreifende Unterstützung. JSON verwendet keine Nachrichtenschemas. Als textbasiertes Format ist es nicht sehr effizient über das Kabel. In einigen Fällen können Sie jedoch zwischengespeicherte Elemente direkt über HTTP an einen Client zurückgeben. In diesem Fall kann das Speichern von JSON die Kosten für die Deserialisierung aus einem anderen Format und anschließendes Serialisieren in JSON sparen.

  • Binary JSON (BSON) ist ein binäres Serialisierungsformat, das eine Struktur wie JSON verwendet. BSON wurde entwickelt, um leicht zu scannen und schnell zu serialisieren und deserialisieren, relativ zu JSON. Nutzlasten sind mit JSON vergleichbar. Je nach Daten kann eine BSON-Nutzlast kleiner oder größer als eine JSON-Nutzlast sein. BSON verfügt über einige zusätzliche Datentypen, die in JSON nicht verfügbar sind, insbesondere BinData (für Bytearrays) und Date.

  • MessagePack is a binary serialization format that is designed to be compact for transmission over the wire. Es gibt keine Nachrichtenschemas oder Nachrichtentypüberprüfungen.

  • Bond is a cross-platform framework for working with schematized data. Sie unterstützt die sprachübergreifende Serialisierung und Deserialisierung. Wichtige Unterschiede zu anderen hier aufgeführten Systemen sind unterstützung für Vererbung, Typaliasen und Generika.

  • gRPC is an open-source RPC system developed by Google. Standardmäßig verwendet sie Protokollpuffer als Definitionssprache und zugrunde liegendes Nachrichtenaustauschformat.

Next steps

Die folgenden Muster können auch für Ihr Szenario relevant sein, wenn Sie die Zwischenspeicherung in Ihren Anwendungen implementieren:

  • Cache-aside pattern: This pattern describes how to load data on demand into a cache from a data store. Dieses Muster trägt auch dazu bei, die Konsistenz zwischen Daten, die im Cache gespeichert sind, und den Daten im ursprünglichen Datenspeicher aufrechtzuerhalten.

  • The Sharding pattern provides information about implementing horizontal partitioning to help improve scalability when storing and accessing large volumes of data.