Redis Best Practices, Teil 1

In einer Reihe von Artikeln werde ich meine angepasste Übersetzung des Redis Best Practices- Abschnitts von der offiziellen Redis Labs-Website präsentieren.

Redis kann auf unzählige Arten verwendet werden. Es gibt jedoch mehrere Muster, mit denen häufig auftretende Probleme gelöst werden können. Wir haben eine Sammlung allgemeiner Muster zusammengestellt, die wir als Best Practices zur Lösung dieser Probleme betrachten. Diese Sammlung erhebt keinen Anspruch auf Vollständigkeit und scheint nicht die einzige Möglichkeit zu sein, Redis zu verwenden. Wir hoffen, dass sie als Ausgangspunkt für die Lösung von Problemen mit Redis dient.

Wir haben diesen Leitfaden für bewährte Verfahren nach Bedarf in Kapitel und Unterkapitel unterteilt (Anmerkung des Übersetzers: Einige Unterkapitel sind kurz, daher werde ich sie in einem kombinieren):

  • Im Kapitel „Indizierungsmuster“ werden wir nach Möglichkeiten suchen, um über den üblichen Schlüsselwertzugriff mit Redis hinauszugehen. Es enthält Möglichkeiten zur intelligenten Verwendung von Schlüsselmustern mithilfe verschiedener Redis-Datentypen, um nicht nur Daten zu finden, sondern auch die Komplexität des Zugriffs zu verringern.
  • Das Kapitel Interaktionsmuster konzentriert sich auf Redis-Muster, mit denen Daten über die Infrastruktur hinweg verschoben werden. In diesem Fall fungiert Redis nicht als Repository, sondern als Leitfaden für Daten.
  • Das Kapitel „Datenspeichermuster“ beschreibt Methoden zum Speichern komplexer Darstellungen von Daten in Redis. Wir werden komplexe Skripte von Dokumenten berechnen, die auf einfache und komplexe Weise verallgemeinert werden können.
  • Muster für temporär gespeicherte Daten werden im Kapitel „Zeitreihenmuster“ beschrieben.
  • Geschwindigkeitsbegrenzung wird häufig in Redis verwendet. Im Kapitel "Grundlegende Geschwindigkeitsbegrenzungsmuster" werden die Grundlagen der Anwendungsfälle behandelt.
  • Der Bloom-Filter ist seit langem in Redis zu sehen, und im Kapitel „Muster mit einem Bloom-Filter“ untersuchen wir probabilistische Datenstrukturen und wie sie sich von ihren unwahrscheinlichen Analoga unterscheiden.
  • die Theke ist eine überraschend tiefe Rezeption. In einem separaten Kapitel erfahren Sie, wie Sie Aktivitäten und eindeutige Elemente auf effiziente Weise berechnen können.
  • Schließlich werden wir darüber sprechen, wie wir Lua nutzen können, um Redis dazu zu bringen, mit weniger mehr zu erreichen.

Diese Anleitung ist inkonsistent, sodass Sie mit jedem Kapitel beginnen können. Sie können auch die Navigation am Anfang jedes Beitrags verwenden, um etwas Passendes zu finden.

Indizierungsmuster


Konzeptionell ist Redis eine Datenbank, die auf dem Schlüssel / Wert-Paradigma basiert, wenn jedem Datenelement ein bestimmter Schlüssel zugeordnet ist. Wenn Sie Daten für etwas anderes als einen Schlüssel abrufen möchten, müssen Sie einen Index implementieren, der einen der vielen in Redis verfügbaren Datentypen verwendet.

Die Indizierung in Redis unterscheidet sich erheblich von der in anderen Datenbanken, sodass Ihre eigenen Verwendungsszenarien und Daten die beste Strategie für die Indizierung bestimmen. In diesem Kapitel werden neben dem einfachen Abrufen von Schlüsseln und Werten einige allgemeine Strategien zum Abrufen von Daten behandelt:

  • sortierte Mengen als Indizes;
  • lexikografische Indizes;
  • Geodatenindizes;
  • IP-Geolocation
  • Volltextsuche;
  • partitionierte Indizes.

Sortierte Mengen als Indizes


Sortierte Mengen (ZSETs) sind ein Standarddatentyp in Redis, der viele eindeutige Objekte darstellt (Wiederholungen werden nicht gespeichert), wobei jedem Objekt eine Nummer (als "Anzahl" bezeichnet) zugewiesen wird, die als natürlicher Sortiermechanismus fungiert. Und obwohl Objekte nicht wiederholt werden können, können einige Objekte dieselbe Anzahl haben. Mit einem relativ geringen Zeitaufwand für das Hinzufügen, Löschen und Abrufen eines Wertebereichs (nach Rang oder Anzahl) eignen sich sortierte Mengen gut als Indizes. Nehmen Sie als Beispiel die Länder der Welt, geordnet nach Bevölkerung:

> ZADD countries-by-pop 1409517397 china > ZADD countries-by-pop 146573899 russia > ZADD countries-by-pop 81456724 germany > ZADD countries-by-pop 333016381 usa > ZADD countries-by-pop 1 mars > ZADD countries-by-pop 37290812 afghanistan > ZADD countries-by-pop 1388350202 india 

Es ist ganz einfach, die fünf besten Länder zu finden:

 > ZRANGE countries-by-pop 0 4 1) "mars" 2) "afghanistan" 3) "germany" 4) "russia" 5) "india" 

Und Länder mit einer Bevölkerung zwischen 10.000.000 und 1.000.000.000 erhalten:

 > ZRANGEBYSCORE countries-by-pop 10000000 1000000000 1) "afghanistan" 2) "germany" 3) "russia" 

Sie können mehrere Indizes erstellen, um verschiedene Arten der Datensortierung zu demonstrieren. In unserem Beispiel könnten wir dieselben Objekte verwenden, aber anstelle der Anzahl der Personen die Bevölkerungsdichte, die geografische Größe, die Anzahl der Internetnutzer usw. verwenden. Dadurch werden Hochleistungsindizes für verschiedene Aspekte erstellt. Wenn der Name eines Objekts durch Daten geteilt wird, die entweder in Redis (z. B. in Hash) oder in einem anderen Datenspeicher gespeichert sind, kann der sekundäre Prozess bei Bedarf zusätzliche Informationen zu jedem Element abrufen.

Lexikografische Indizes


Sortierte Mengen (ZSETs) mit einer Rangfolge nach Anzahl haben eine interessante Eigenschaft, mit der ein Mechanismus für die grobe alphabetische Sortierung erstellt werden kann. Die Eigenschaft ist, dass Objekte mit derselben Punktzahl in lexikografischer Reihenfolge und nach Grenzwerten zurückgegeben werden können. Nehmen Sie die folgenden Daten:

 > ZADD animal-list 0 bison 0 boa 0 dog 0 emu 0 falcon 0 alligator 0 chipmunk 

Dieser Befehl fügt dem Tierlistenschlüssel mehrere Tiere hinzu. Jedes Objekt hat eine Punktzahl von 0. Nach der Ausführung des ZRANGE-Befehls mit den Argumenten 0 und -1 sehen wir eine merkwürdige Reihenfolge:

 > ZRANGE animal-list 0 -1 1) "alligator" 2) "bison" 3) "boa" 4) "chipmunk" 5) "dog" 6) "emu" 7) "falcon" 

Obwohl die Elemente nicht alphabetisch hinzugefügt wurden, wurden sie alphabetisch sortiert zurückgegeben. Diese Reihenfolge ist das Ergebnis eines binären Zeichenfolgenvergleichs, byteweise. Dies bedeutet, dass ASCII-Zeichen in alphabetischer Reihenfolge zurückgegeben werden. Dies legt Folgendes nahe:

  • Zeichen in Groß- und Kleinschreibung werden nicht als gleich erkannt.
  • Multibyte-Zeichen werden nicht wie erwartet sortiert.

Redis bietet auch einige erweiterte Funktionen, um die lexikografische Suche weiter einzugrenzen. Zum Beispiel möchten wir Tiere zurückgeben, die mit b beginnen und mit e enden. Wir können den folgenden Befehl verwenden:

 > ZRANGEBYLEX animal-list [b (f 1) "bison" 2) "boa" 3) "chipmunk" 4) "dog" 5) "emu" 

Das Argument (f kann etwas verwirrend sein. Dies ist ein wichtiger Punkt, da Redis keine Ahnung vom wörtlichen Verständnis der Buchstaben des Alphabets hat. Dies bedeutet, dass wir berücksichtigen müssen, dass alles, was mit e beginnt, immer vor allem steht, was mit f beginnt, unabhängig von den nachfolgenden Buchstaben Ein weiterer Hinweis ist, dass die eckige Klammer die Suche mit Einschluss und die runde Klammer die Suche ohne Einschluss angibt. In unserem Fall wird bei einer Abfrage mit b diese in die Liste aufgenommen, während f nicht in der Auswahl angezeigt wird alle Elemente bis zum Ende verwenden das letzte Symbol (255 oder 0xFF) codiert:

 > ZRANGEBYLEX animal-list [c "[\xff" 1) "chipmunk" 2) "dog" 3) "emu" 4) "falcon" 

Dieser Befehl kann auch eingeschränkt werden, wodurch die Paginierung sichergestellt wird:

 > ZRANGEBYLEX animal-list [b (f LIMIT 0 2 1) "bison" 2) "boa" > ZRANGEBYLEX animal-list [b (f LIMIT 2 2 1) "chipmunk" 2) "dog" 

Der einzige Nachteil ist, dass die Zeitkomplexität mit zunehmendem Einzug zunimmt (erstes Argument nach LIMIT). Wenn Sie also 1 Million Objekte haben und versuchen, die letzten beiden zu erhalten, ist ein Crawl von nur einer Million erforderlich.

Geodatenverzeichnisse


Redis verfügt über mehrere Geodatenindizierungsteams (GEO-Teams). Im Gegensatz zu anderen Teams haben diese Teams jedoch keine eigenen Datentypen. Diese Befehle ergänzen die Art der sortierten Menge. Dies wird erreicht, indem der Breiten- und Längengrad unter Verwendung des Geohash-Algorithmus in die Punktzahl des sortierten Satzes codiert wird.
Das Hinzufügen von Elementen zu einem Geo-Index ist einfach. Angenommen, Sie verfolgen eine Gruppe von Autos, die auf einer Straße fahren. Wir nennen diesen Satz von Autos einfach "Autos". Angenommen, Ihre bestimmte Maschine kann als "Mein Auto" -Objekt identifiziert werden (wir verwenden den Begriff "Objekt", da der Geo-Index einfach eine Form der Menge ist). Um eine Maschine zum Set hinzuzufügen, können wir den Befehl ausführen:

 > GEOADD cars -115.17087 36.12306 my-car 

Das erste Argument ist die Menge, zu der wir hinzufügen, das zweite ist der Breitengrad, das dritte der Längengrad und das vierte der Name des Objekts.

Um den Standort der Maschine zu aktualisieren, müssen Sie den Befehl nur noch einmal mit den neuen Koordinaten ausführen. Dies funktioniert, weil ein Geo-Index einfach eine Menge ist, bei der doppelte Elemente nicht zulässig sind.

 > GEOADD cars -115.17172 36.12196 my-car 

Fügen Sie ein zweites Auto zu "Autos" hinzu. Diesmal führt Volodya sie:

 > GEOADD cars -115.171971 36.120609 volodias-car 

Wenn man sich die Koordinaten ansieht, kann man sagen, dass diese Autos ziemlich nahe beieinander liegen, aber wie viel? Sie können dies mit dem Befehl GEODIST definieren:

 > GEODIST cars my-car volodias-car "151.9653" 

Dies bedeutet, dass zwei Fahrzeuge ungefähr 151 Meter voneinander entfernt sind. Sie können auch in anderen Einheiten berechnen:

 > GEODIST cars my-car robins-car ft "498.5737" 

Dies ergab die gleiche Distanz in Schritten. Sie können auch Meilen (ml) oder Kilometer (km) verwenden.

Nun wollen wir sehen, wer sich im Radius eines bestimmten Punktes befindet:

 > GEORADIUS cars -115.17258 36.11996 100 m 1) "volodias-car" 

Dies brachte alle innerhalb eines Radius von 100 Metern um den angegebenen Punkt zurück. Sie können auch alle Personen im Radius eines Objekts aus der Gruppe anfordern:

 > GEORADIUSBYMEMBER cars volodias-car 152 m 1) "volodias-car" 2) "my-car" 

Wir können den Abstand auch durch Hinzufügen des optionalen WITHDIST-Arguments aktivieren (dies funktioniert für GEORADIUS oder GEORADIUSBYMEMBER):

 > GEORADIUSBYMEMBER cars volodias-car 152 m WITHDIST 1) 1) "volodias-car" 2) "0.0000" 2) 1) "my-car" 2) "151.9653" 

Ein weiteres optionales Argument für GEORADIUS und GEORADIUSBYMEMBER ist WITHCOORD, das die Koordinaten jedes Objekts zurückgibt. WITHDIST und WITHCOORD können zusammen oder getrennt verwendet werden:

 > GEORADIUSBYMEMBER cars volodias-car 152 m WITHDIST WITHCOORD 1) 1) "volodias-car" 2) "0.0000" 3) 1) "-115.17197102308273315" 2) "36.12060917648089031" 2) 1) "my-car" 2) "151.9653" 3) 1) "-115.17171889543533325" 2) "36.12196018285882104" 

Da Geodatenindizes nur eine Alternative zu sortierten Mengen sind, können einige Operatoren der letzteren verwendet werden. Wenn wir "my-car" aus der Menge "cars" entfernen möchten, können wir den Befehl der sortierten Menge von ZREM verwenden:

 > ZREM cars my-car 

Redis bietet eine Vielzahl von Werkzeugen für die Arbeit mit Geodaten. In diesem Abschnitt haben wir nur die grundlegenden untersucht.

IP-Geolocation


Das Ermitteln des tatsächlichen Standorts des verbundenen Dienstes kann sehr hilfreich sein. IP-Geolocation-Tabellen sind normalerweise recht groß und schwer effektiv zu verwalten. Wir können sortierte Sets verwenden, um schnelle und effiziente IP-Geolocation-Services zu implementieren.

IPv4 wird am häufigsten in Dezimalschreibweise angegeben (z. B. 74.125.43.99). Netzwerkdienste sehen diese Adresse jedoch als 32-Bit-Zahl, wobei jedes Byte eine von vier Zahlen in Dezimalform darstellt. Das obige Beispiel wäre 0x4A7D2B63 hexadezimal oder 1249717091 dezimal.

IP-Geolocation-Datasets sind weit verbreitet und haben normalerweise die Form einer einfachen Tabelle mit drei Spalten (Anfang, Ende, Position). Anfang und Ende sind die dezimale Darstellung von IPv4. In Redis können wir sortierte Mengen an dieses Format anpassen, da die IP-Bereiche keine „Lücken“ aufweisen. Daher können wir davon ausgehen, dass das Ende eines Bereichs der Anfang eines anderen Bereichs ist.

Fügen Sie für ein einfaches Beispiel den sortierten Mengen einige Bereiche hinzu:

 > ZADD ip-loc 1249716479 us:1 > ZADD ip-loc 1249716735 taiwan:1 > ZADD ip-loc 1249717759 us:2 > ZADD ip-loc 1249718015 finland:1 

Das erste Argument ist der Schlüssel unserer Menge, das zweite ist die dezimale Darstellung des Endes des IP-Bereichs und das letzte ist das Objekt selbst. Beachten Sie, dass das gesetzte Objekt eine Nummer nach dem Doppelpunkt hat. Dies dient nur zur Veranschaulichung. Echte IP-Tabellen haben eindeutige Kennungen für jeden Bereich (und mehr zusätzliche Informationen als nur den Namen des Landes).

Um die Tabelle nach einer bestimmten IP-Adresse abzufragen, können wir den Befehl ZRANGEBYSCORE mit einigen zusätzlichen Argumenten verwenden. Nehmen Sie die IP-Adresse und konvertieren Sie sie in eine Dezimalzahl. Dies kann mit Ihrer Programmiersprache erfolgen. Verwenden Sie zunächst die Adresse aus dem ursprünglichen Beispiel 74.125.43.99 (1249717091). Wenn wir diese Zahl als Bezugspunkt nehmen und kein Maximum angeben und dann das Ergebnis nur auf das erste Objekt beschränken, finden wir dessen Geolocation:

 > ZRANGEBYSCORE ip-loc 1249717091 +inf LIMIT 0 1 1) "us:2" 

Das erste Argument ist der Schlüssel unserer sortierten Menge, das zweite ist die dezimale Darstellung der IP-Adresse, das dritte (+ inf) weist Redis an, ohne Obergrenze anzufordern, und die letzten drei Argumente geben lediglich an, dass wir nur das allererste Ergebnis erhalten möchten.

Volltextsuche


Vor der Einführung von Modulen wurde die Volltextsuche mit nativen Redis-Befehlen implementiert. Das RedisSearch-Modul ist viel produktiver als dieses Muster. In einigen Umgebungen ist es jedoch nicht verfügbar. Darüber hinaus ist dieses Muster sehr interessant und kann auf andere Workloads verallgemeinert werden, in denen RedisSearch nicht ideal ist.
Angenommen, wir haben mehrere Textdokumente, die durchsucht werden müssen. Dies ist möglicherweise nicht der offensichtliche Anwendungsfall für Redis, da es den Zugriff auf Schlüssel ermöglicht. Andererseits kann Redis als völlig neue Volltextsuchmaschine verwendet werden.

Nehmen wir zunächst einige Beispieltexte in den Dokumenten:

"Redis ist sehr schnell"
"Geparden sind schnell"
"Geparden haben Flecken"

Wir teilen sie in Wortgruppen ein, die der Einfachheit halber durch ein Leerzeichen getrennt sind:

 > SADD ex1 redis is very fast > SADD ex2 cheetahs are very fast > SADD ex3 cheetahs have spots 

Beachten Sie, dass wir jede Zeile in einen eigenen Satz einfügen. Es scheint, als würden wir nur die gesamte Zeile hinzufügen - SADD ist variabel und nimmt mehrere Elemente gleichzeitig als Argumente. Wir haben auch alle Wörter in Kleinbuchstaben umgewandelt.
Dann müssen wir diesen Index umkehren und zeigen, welches Wort in welchem ​​Dokument enthalten ist. Dazu erstellen wir für jedes Wort eine Menge und geben den Namen des Dokuments als Objekt an:

 > SADD redis ex1 > SADD is ex1 > SADD very ex1 ex2 > SADD fast ex1 ex2 > SADD cheetahs ex2 ex3 > SADD have ex3 > SADD spots ex3 

Aus Gründen der Übersichtlichkeit haben wir dies in verschiedene Befehle unterteilt, aber alle Befehle werden normalerweise atomar im MULTI / EXEC-Block ausgeführt.

Um unseren winzigen Volltextindex abzufragen, verwenden wir den Befehl SINTER (Schnittmenge von Mengen). Finden Sie Dokumente mit "sehr" und "schnell":

 > SINTER very fast 1) "ex2" 2) "ex1" 

Falls es keine Dokumente gibt, die der Anfrage entsprechen, erhalten wir ein leeres Ergebnis:

 > SINTER cheetahs redis (empty list or set) 

Aus Gründen der Konsistenz ist es besser, SUNION anstelle von SINTER zu verwenden:

 > SUNION cheetahs redis 1) "ex2" 2) "ex1" 3) "ex3" 

Das Entfernen eines Objekts aus dem Index ist etwas komplizierter. Rufen Sie zuerst indizierte Wörter aus dem Dokument ab und löschen Sie dann die Dokument-ID aus jedem Wort:

 > SMEMBERS ex3 1) "spots" 2) "have" 3) "cheetahs" > SREM have ex3 > SREM cheetahs ex3 > SREM spots ex3 

Redis verfügt nicht über einen separaten Operator, der alle diese Schritte mit einem einzigen Befehl ausführt. Sie müssen also zuerst mit dem Befehl SMEMBERS abfragen und dann jedes Objekt nacheinander mit SREM löschen.

Dies ist natürlich eine sehr vereinfachte Volltextsuche. Sie können fortgeschrittenere Aktionen ausführen, indem Sie sortierte Sets anstelle der üblichen verwenden. In diesem Fall können Sie ein Wort, das in einem Dokument mehrmals vorkommt, höher einstufen als das Dokument, in dem es einmal vorkommt. Die oben beschriebenen Muster sind mit Ausnahme der verwendeten Mengenarten mehr oder weniger gleich.

Partitionierte Indizes


Eine einzelne Instanz (oder ein Shard) von Redis ist sehr brauchbar, aber es gibt Situationen, in denen Sie möglicherweise einen Index benötigen, der auf mehrere Instanzen verteilt ist. Zum Beispiel, um den Durchsatz zu erhöhen, indem Indizes parallelisiert werden, die größer sind als der freie Speicherplatz einer Instanz. Angenommen, Sie möchten eine Operation mit mehreren Tasten ausführen. Eine effektive Möglichkeit, diese Schlüssel zu trennen (zu partitionieren), besteht darin, eine gleichmäßige Verteilung der Schlüssel auf alle Partitionen sicherzustellen, alle Operationen in jeder Partition parallel auszuführen und die Ergebnisse am Ende zu kombinieren.

Um eine einheitliche Schlüsselverteilung zu erreichen, verwenden wir einen nicht kryptografischen Hashing-Algorithmus. Jede schnelle Hash-Funktion kann verwendet werden, wir verwenden jedoch den berühmten CRC-32 als Beispiel. In den meisten Fällen geben diese Algorithmen das Ergebnis hexadezimal zurück (für „mein-cooles-Dokument“ gibt CRC-32 F9FDB2C9 zurück). Die hexadezimale Darstellung ist für den Computer einfacher, es handelt sich jedoch nur um eine andere Darstellung von Dezimalzahlen, sodass Sie mit diesen Werten Berechnungen durchführen können.
Als nächstes müssen Sie die Anzahl der Partitionen bestimmen - dies sollte mindestens x2 der Anzahl der Kopien sein. Dies trägt weiter zur Skalierung bei.

Nehmen wir an, wir haben 3 Kopien und 6 Partitionen. Die Partition, an die das Dokument gesendet wird, kann wie folgt berechnet werden:

hash function(identifier document) modnumber partitions



 CRC32(“my-cool-document”) = F9FDB2C9 (16)  4194153161 (10) 4194153161 mod 6 = 5 

In Redis Enterprise können Sie steuern, zu welcher Partition der Schlüssel gehört, indem Sie entweder vordefinierte reguläre Ausdrücke verwenden oder einen Teil des Schlüssels in geschweifte Klammern setzen. In unserem Beispiel können wir den Schlüssel für das Dokument folgendermaßen festlegen:

idx: my-cool-document {5}

Dann haben wir ein anderes Dokument, das eine Partition mit der Nummer 3 ausgibt, daher sieht der Schlüssel so aus:

idx: mein-anderes-dokument {3}

Wenn Sie über zusätzliche Zusatzschlüssel verfügen, mit denen Sie arbeiten müssen und die mit diesem Dokument verknüpft sind, müssen Sie sich auf derselben Partition befinden, damit Sie Vorgänge mit beiden Schlüsseln gleichzeitig ausführen können, ohne dass eine Reihe von Fehlern auftreten. Dazu müssen Sie dem Schlüssel dieselbe Partitionsnummer wie dem Dokument hinzufügen.

Wenn Sie Ihre Daten aus der Ferne betrachten, werden Sie feststellen, dass Ihr Index ziemlich gleichmäßig über Partitionen verteilt ist. Sie können die Aufgabe, die für jede Partition ausgeführt werden muss, parallelisieren. Wenn Sie eine Aufgabe haben, die über den gesamten Index erledigt werden muss, muss Ihre Anwendung für jede Partition dieselbe Logik ausführen, das Ergebnis zurückgeben und nach Bedarf in der Anwendung kombinieren.

Hiermit endet der erste Artikel. Das nächste wird eine Übersetzung der Unterüberschriften "Interaktionsmuster" und "Datenspeichermuster" sein.

Source: https://habr.com/ru/post/de485672/


All Articles