Verwendung von Redis mit Python

Verwendung von Redis mit Python

In diesem Tutorial erfahren Sie, wie Sie Python mit Redis verwenden (ausgesprochenRED-iss oder vielleichtREE-diss oderRed-DEES, je nachdem, wen Sie fragen), was blitzschnell ist Speicherschlüsselwertspeicher, der für alles von A bis Z verwendet werden kann. Folgendes sagtSeven Databases in Seven Weeks, ein beliebtes Buch über Datenbanken, über Redis:

Es ist nicht einfach zu bedienen. Es ist eine Freude. Wenn eine API UX für Programmierer ist, sollte sich Redis neben dem Mac Cube im Museum of Modern Art befinden.

Und wenn es um Geschwindigkeit geht, ist Redis schwer zu schlagen. Lesevorgänge sind schnell und Schreibvorgänge sind sogar noch schneller und verarbeiten nach einigen Benchmarks mehr als 100.000SETOperationen pro Sekunde. (Source)

Fasziniert? Dieses Tutorial wurde für Python-Programmierer entwickelt, die möglicherweise keine oder nur geringe Redis-Erfahrung haben. Wir werden zwei Tools gleichzeitig angehen und sowohl Redis selbst als auch eine seiner Python-Client-Bibliotheken,redis-py, vorstellen.

redis-py (das Sie nur alsredis importieren) ist einer von vielen Python-Clients für Redis, wird jedoch von den Redis-Entwicklern selbst als“currently the way to go for Python” in Rechnung gestellt. Sie können Redis-Befehle von Python aus aufrufen und vertraute Python-Objekte zurückerhalten.

In this tutorial, you’ll cover:

  • Installieren von Redis aus dem Quellcode und Verstehen des Zwecks der resultierenden Binärdateien

  • Erlernen eines mundgerechten Teils von Redis selbst, einschließlich seiner Syntax, seines Protokolls und seines Designs

  • Beherrschen Sieredis-py und sehen Sie gleichzeitig, wie das Redis-Protokoll implementiert wird

  • Einrichten und Kommunizieren mit einer Amazon ElastiCache Redis-Serverinstanz

Free Bonus:Click here to get access to a chapter from Python Tricks: The Book zeigt Ihnen die Best Practices von Python anhand einfacher Beispiele, die Sie sofort anwenden können, um schöneren + Pythonic-Code zu schreiben.

Redis von der Quelle installieren

Wie mein Ur-Ur-Großvater sagte, baut nichts so viel auf wie die Installation von der Quelle. Dieser Abschnitt führt Sie durch das Herunterladen, Erstellen und Installieren von Redis. Ich verspreche, dass dies kein bisschen schaden wird!

Note: Dieser Abschnitt ist auf die Installation unter Mac OS X oder Linux ausgerichtet. Wenn Sie Windows verwenden, gibt es Microsoftforkvon Redis, die als Windows-Dienst installiert werden können. Es genügt zu sagen, dass Redis als Programm am bequemsten auf einer Linux-Box lebt und dass das Einrichten und Verwenden unter Windows schwierig sein kann.

Laden Sie zunächst den Redis-Quellcode als Tarball herunter:

$ redisurl="http://download.redis.io/redis-stable.tar.gz"
$ curl -s -o redis-stable.tar.gz $redisurl

Wechseln Sie anschließend zuroot und extrahieren Sie den Quellcode des Archivs zu/usr/local/lib/:

$ sudo su root
$ mkdir -p /usr/local/lib/
$ chmod a+w /usr/local/lib/
$ tar -C /usr/local/lib/ -xzf redis-stable.tar.gz

Optional können Sie jetzt das Archiv selbst entfernen:

$ rm redis-stable.tar.gz

Dadurch erhalten Sie ein Quellcode-Repository bei/usr/local/lib/redis-stable/. Redis ist in C geschrieben, daher müssen Sie mit dem Dienstprogrammmakekompilieren, verknüpfen und installieren:

$ cd /usr/local/lib/redis-stable/
$ make && make install

Die Verwendung vonmake install führt zwei Aktionen aus:

  1. Der erste Befehlmake kompiliert und verknüpft den Quellcode.

  2. Der Teilmake install nimmt die Binärdateien und kopiert sie nach/usr/local/bin/, sodass Sie sie von überall ausführen können (vorausgesetzt,/usr/local/bin/ ist inPATH).

Hier sind alle bisherigen Schritte:

$ redisurl="http://download.redis.io/redis-stable.tar.gz"
$ curl -s -o redis-stable.tar.gz $redisurl
$ sudo su root
$ mkdir -p /usr/local/lib/
$ chmod a+w /usr/local/lib/
$ tar -C /usr/local/lib/ -xzf redis-stable.tar.gz
$ rm redis-stable.tar.gz
$ cd /usr/local/lib/redis-stable/
$ make && make install

Nehmen Sie sich an dieser Stelle einen Moment Zeit, um zu bestätigen, dass Redis inPATHenthalten ist, und überprüfen Sie die Version:

$ redis-cli --version
redis-cli 5.0.3

Wenn Ihre Shellredis-cli nicht finden kann, überprüfen Sie, ob/usr/local/bin/ in der UmgebungsvariablenPATH enthalten ist, und fügen Sie sie hinzu, wenn nicht.

Zusätzlich zuredis-cli führtmake install tatsächlich dazu, dass eine Handvoll verschiedener ausführbarer Dateien (und ein Symlink) bei/usr/local/bin/ platziert werden:

$ # A snapshot of executables that come bundled with Redis
$ ls -hFG /usr/local/bin/redis-* | sort
/usr/local/bin/redis-benchmark*
/usr/local/bin/redis-check-aof*
/usr/local/bin/redis-check-rdb*
/usr/local/bin/redis-cli*
/usr/local/bin/redis-sentinel@
/usr/local/bin/redis-server*

Obwohl alle diese Funktionen für den beabsichtigten Gebrauch bestimmt sind, sind die beiden, die Sie wahrscheinlich am meisten interessieren,redis-cli undredis-server, die wir in Kürze skizzieren werden. Bevor wir jedoch dazu kommen, ist es angebracht, eine Basiskonfiguration einzurichten.

Redis konfigurieren

Redis ist hochgradig konfigurierbar. Nehmen Sie sich eine Minute Zeit, um einige reine Konfigurationsoptionen festzulegen, die sich auf die Datenbankpersistenz und die grundlegende Sicherheit beziehen:

$ sudo su root
$ mkdir -p /etc/redis/
$ touch /etc/redis/6379.conf

Schreiben Sie nun Folgendes in/etc/redis/6379.conf. Wir werden im Verlauf des Tutorials nach und nach erläutern, was die meisten davon bedeuten:

# /etc/redis/6379.conf

port              6379
daemonize         yes
save              60 1
bind              127.0.0.1
tcp-keepalive     300
dbfilename        dump.rdb
dir               ./
rdbcompression    yes

Die Redis-Konfiguration ist selbstdokumentierend. Diesample redis.conf filebefinden sich in der Redis-Quelle, damit Sie viel Spaß beim Lesen haben. Wenn Sie Redis in einem Produktionssystem verwenden, lohnt es sich, alle Ablenkungen auszublenden und sich die Zeit zu nehmen, diese Beispieldatei vollständig zu lesen, um sich mit den Vor- und Nachteilen von Redis vertraut zu machen und Ihr Setup zu optimieren.

In einigen Tutorials, einschließlich Teilen der Redis-Dokumentation, wird möglicherweise auch vorgeschlagen, das Shell-Skriptinstall_server.sh inredis/utils/install_server.sh auszuführen. Sie können dies auf jeden Fall als umfassendere Alternative zu den oben genannten Optionen ausführen. Beachten Sie jedoch einige Feinheiten zuinstall_server.sh:

  • Es funktioniert nicht unter Mac OS X - nur unter Debian und Ubuntu Linux.

  • In/etc/redis/6379.conf wird ein vollständigerer Satz von Konfigurationsoptionen eingefügt.

  • Es werden System Vinit script bis/etc/init.d/redis_6379 geschrieben, mit denen Siesudo service redis_6379 start ausführen können.

Die Redis-Kurzanleitung enthält auch einen Abschnitt zumore proper Redis setup. Die oben genannten Konfigurationsoptionen sollten jedoch für dieses Lernprogramm und die ersten Schritte völlig ausreichend sein.

Security Note: Vor einigen Jahren wies der Autor von Redis auf Sicherheitslücken in früheren Versionen von Redis hin, wenn keine Konfiguration festgelegt wurde. Redis 3.2 (die aktuelle Version 5.0.3 vom März 2019) hat Schritte unternommen, um dieses Eindringen zu verhindern, und die Optionprotected-mode standardmäßig aufyes gesetzt.

Wir setzenbind 127.0.0.1 explizit so, dass Redis nur über die localhost-Schnittstelle auf Verbindungen wartet, obwohl Sie diese Whitelist auf einem echten Produktionsserver erweitern müssten. Der Punktprotected-mode dient als Schutzmaßnahme, die dieses Verhalten beim Binden an den lokalen Host nachahmt, wenn Sie unter der Optionbindnichts anderes angeben.

Nachdem dies erledigt ist, können wir uns jetzt mit Redis selbst befassen.

Zehn oder so Minuten bis Redis

In diesem Abschnitt erhalten Sie gerade genug Kenntnisse über Redis, um gefährlich zu sein, und werden dessen Design und grundlegende Verwendung beschrieben.

Anfangen

Redis hat einclient-server architecture und verwendet einrequest-response model. Dies bedeutet, dass Sie (der Client) über eine TCP-Verbindung standardmäßig über Port 6379 eine Verbindung zu einem Redis-Server herstellen. Sie fordern eine Aktion an (z. B. Lesen, Schreiben, Abrufen, Einstellen oder Aktualisieren), und der Serverservesgibt eine Antwort zurück.

Es können viele Clients mit demselben Server kommunizieren, worum es bei Redis oder einer Client-Server-Anwendung wirklich geht. Jeder Client führt einen (normalerweise blockierenden) Lesevorgang an einem Socket durch, der auf die Serverantwort wartet.

cli inredis-cli steht fürcommand line interface, undserver inredis-server steht für den Betrieb eines Servers. Auf die gleiche Weise, wie Siepython in der Befehlszeile ausführen würden, können Sieredis-cli ausführen, um in eine interaktive REPL (Read Eval Print Loop) zu springen, in der Sie Clientbefehle direkt über die Shell ausführen können.

Zunächst müssen Sie jedochredis-server starten, damit Sie einen laufenden Redis-Server haben, mit dem Sie kommunizieren können. Ein üblicher Weg, dies in der Entwicklung zu tun, besteht darin, einen Server beilocalhost (IPv4-Adresse127.0.0.1) zu starten. Dies ist die Standardeinstellung, sofern Sie Redis nichts anderes mitteilen. Sie können auchredis-server den Namen Ihrer Konfigurationsdatei übergeben, was der Angabe aller Schlüssel-Wert-Paare als Befehlszeilenargumente entspricht:

$ redis-server /etc/redis/6379.conf
31829:C 07 Mar 2019 08:45:04.030 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
31829:C 07 Mar 2019 08:45:04.030 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=31829, just started
31829:C 07 Mar 2019 08:45:04.030 # Configuration loaded

Wir setzen die Konfigurationsoptiondaemonize aufyes, damit der Server im Hintergrund läuft. (Verwenden Sie andernfalls--daemonize yes als Option fürredis-server.)

Jetzt können Sie Redis REPL starten. Geben Sieredis-cli in Ihre Befehlszeile ein. Sie sehen dashost:port-Paar des Servers, gefolgt von einer>-Aufforderung:

127.0.0.1:6379>

Hier ist einer der einfachsten Redis-Befehle,PING, der nur die Konnektivität zum Server testet und"PONG" zurückgibt, wenn alles in Ordnung ist:

127.0.0.1:6379> PING
PONG

Bei Redis-Befehlen wird die Groß- und Kleinschreibung nicht berücksichtigt, bei Python-Befehlen jedoch definitiv nicht.

Note: Als weitere Überprüfung der Integrität können Sie mitpgrep nach der Prozess-ID des Redis-Servers suchen:

$ pgrep redis-server
26983

Verwenden Siepkill redis-server in der Befehlszeile, um den Server zu beenden. Unter Mac OS X können Sie auchredis-cli shutdown verwenden.

Als Nächstes verwenden wir einige der gängigen Redis-Befehle und vergleichen sie mit dem, wie sie in reinem Python aussehen würden.

Redis als Python-Wörterbuch

Redis steht fürRemote Dictionary Service.

"Du meinst, wie ein Pythondictionary?" Sie können fragen.

Yes. Im Allgemeinen gibt es viele Parallelen, die Sie zwischen einem Python-Wörterbuch (oder einer generischen Hash-Tabelle) und dem, was Redis ist und tut, ziehen können:

  • Eine Redis-Datenbank enthältkey:value Paare und unterstützt Befehle wieGET,SET undDEL sowieseveral hundred zusätzliche Befehle.

  • Rediskeys sind immer Zeichenfolgen.

  • Redisvalues können verschiedene Datentypen sein. In diesem Tutorial werden einige der wichtigsten Wertdatentypen behandelt:string,list,hashes undsets. Einige erweiterte Typen umfassengeospatial items und den neuenstream-Typ.

  • Viele Redis-Befehle arbeiten in konstanter O (1) -Zeit, genau wie das Abrufen eines Werts aus einem Pythondict oder einer beliebigen Hash-Tabelle.

Der Redis-Erfinder Salvatore Sanfilippo würde den Vergleich einer Redis-Datenbank mit einer einfachen Vanille-Pythondictwahrscheinlich nicht lieben. Er nennt das Projekt einen „Datenstruktur-Server“ (anstelle eines Schlüsselwertspeichers wiememcached), da Redis das Speichern von zusätzlichen Datentypen vonkey:valuenebenstring:stringunterstützt ) s. Für unsere Zwecke ist dies jedoch ein nützlicher Vergleich, wenn Sie mit dem Python-Wörterbuchobjekt vertraut sind.

Lassen Sie uns einsteigen und anhand eines Beispiels lernen. Unsere erste Spielzeugdatenbank (mit der ID 0) wird eine Zuordnung voncountry:capital city sein, wobei wirSET verwenden, um Schlüssel-Wert-Paare festzulegen:

127.0.0.1:6379> SET Bahamas Nassau
OK
127.0.0.1:6379> SET Croatia Zagreb
OK
127.0.0.1:6379> GET Croatia
"Zagreb"
127.0.0.1:6379> GET Japan
(nil)

Die entsprechende Folge von Anweisungen in reinem Python würde folgendermaßen aussehen:

>>>

>>> capitals = {}
>>> capitals["Bahamas"] = "Nassau"
>>> capitals["Croatia"] = "Zagreb"
>>> capitals.get("Croatia")
'Zagreb'
>>> capitals.get("Japan")  # None

Wir verwendencapitals.get("Japan") anstelle voncapitals["Japan"], da Redisnil anstelle eines Fehlers zurückgibt, wenn kein Schlüssel gefunden wird, was analog zu PythonsNone ist.

Mit Redis können Sie auch mehrere Schlüssel-Wert-Paare in einem Befehl festlegen und abrufen,MSET bzw.MGET:

127.0.0.1:6379> MSET Lebanon Beirut Norway Oslo France Paris
OK
127.0.0.1:6379> MGET Lebanon Norway Bahamas
1) "Beirut"
2) "Oslo"
3) "Nassau"

Das nächste in Python ist mitdict.update():

>>>

>>> capitals.update({
...     "Lebanon": "Beirut",
...     "Norway": "Oslo",
...     "France": "Paris",
... })
>>> [capitals.get(k) for k in ("Lebanon", "Norway", "Bahamas")]
['Beirut', 'Oslo', 'Nassau']

Wir verwenden.get() anstelle von.__getitem__(), um das Verhalten von Redis nachzuahmen, einen null-ähnlichen Wert zurückzugeben, wenn kein Schlüssel gefunden wird.

Als drittes Beispiel bewirkt der BefehlEXISTS, wie es sich anhört, nämlich zu überprüfen, ob ein Schlüssel vorhanden ist:

127.0.0.1:6379> EXISTS Norway
(integer) 1
127.0.0.1:6379> EXISTS Sweden
(integer) 0

Python hat das Schlüsselwortin, um dasselbe zu testen, das andict.__contains__(key) weitergeleitet wird:

>>>

>>> "Norway" in capitals
True
>>> "Sweden" in capitals
False

Diese wenigen Beispiele sollen mit nativem Python zeigen, was mit einigen allgemeinen Redis-Befehlen auf hoher Ebene geschieht. Zu den Python-Beispielen gibt es hier keine Client-Server-Komponente, undredis-py hat das Bild noch nicht eingegeben. Dies soll nur die Redis-Funktionalität anhand eines Beispiels zeigen.

Hier ist eine Zusammenfassung der wenigen Redis-Befehle, die Sie gesehen haben, und ihrer funktionalen Python-Entsprechungen:

Die Python Redis-Clientbibliothekredis-py, auf die Sie in diesem Artikel in Kürze eingehen werden, funktioniert anders. Es kapselt eine tatsächliche TCP-Verbindung zu einem Redis-Server und sendet Rohbefehle als Bytes, die mitREdis Serialization Protocol (RESP) serialisiert wurden, an den Server. Anschließend wird die unformatierte Antwort in ein Python-Objekt wiebytes,int oder sogardatetime.datetime zurückgeführt.

Note: Bisher haben Sie über die interaktive REPL vonredis-climit dem Redis-Server gesprochen. Sie können auchissue commands directly verwenden, genauso wie Sie den Namen eines Skripts an die ausführbare Datei vonpythonübergeben würden, z. B.python myscript.py.

Bisher haben Sie einige der grundlegenden Datentypen von Redis gesehen, bei denen es sich um eine Zuordnung vonstring:stringhandelt. Während dieses Schlüssel-Wert-Paar in den meisten Schlüssel-Wert-Speichern üblich ist, bietet Redis eine Reihe anderer möglicher Werttypen an, die Sie als Nächstes sehen werden.

Weitere Datentypen in Python vs Redis

Bevor Sie den Python-Client vonredis-pytarten, ist es auch hilfreich, sich mit einigen weiteren Redis-Datentypen vertraut zu machen. Alle Redis-Tasten sind Zeichenfolgen. Dies ist der Wert, der zusätzlich zu den in den Beispielen bisher verwendeten Zeichenfolgenwerten Datentypen (oder Strukturen) annehmen kann.

Ahash ist eine Zuordnung vonstring:string,field-value-Paaren genannt, die sich unter einem Schlüssel der obersten Ebene befindet:

127.0.0.1:6379> HSET realpython url "https://realpython.com/"
(integer) 1
127.0.0.1:6379> HSET realpython github realpython
(integer) 1
127.0.0.1:6379> HSET realpython fullname "Real Python"
(integer) 1

Dies setzt drei Feld-Wert-Paare für einkey,"realpython". Wenn Sie an die Terminologie und Objekte von Python gewöhnt sind, kann dies verwirrend sein. Ein Redis-Hash ist ungefähr analog zu einem Pythondict, der eine Ebene tief verschachtelt ist:

data = {
    "realpython": {
        "url": "https://realpython.com/",
        "github": "realpython",
        "fullname": "Real Python",
    }
}

Die Felder von Redis ähneln den Python-Schlüsseln jedes verschachtelten Schlüssel-Wert-Paares im inneren Wörterbuch oben. Redis reserviert den Begriffkey für den Datenbankschlüssel der obersten Ebene, der die Hash-Struktur selbst enthält.

Genau wie esMSET für die Schlüssel-Wert-Paare vonstring:stringgibt, gibt es auchHMSET für Hashes, um mehrere Paarewithin als Hash-Wert-Objekt festzulegen:

127.0.0.1:6379> HMSET pypa url "https://www.pypa.io/" github pypa fullname "Python Packaging Authority"
OK
127.0.0.1:6379> HGETALL pypa
1) "url"
2) "https://www.pypa.io/"
3) "github"
4) "pypa"
5) "fullname"
6) "Python Packaging Authority"

Die Verwendung vonHMSET ist wahrscheinlich eine engere Parallele für die Art und Weise, wie wirdata einem verschachtelten Wörterbuch oben zugewiesen haben, anstatt jedes verschachtelte Paar wie beiHSET festzulegen.

Zwei zusätzliche Werttypen sindlists undsets, die einen Hash oder eine Zeichenfolge als Redis-Wert ersetzen können. Sie sind weitgehend so, wie sie klingen, daher werde ich Ihre Zeit nicht mit zusätzlichen Beispielen in Anspruch nehmen. Hashes, Listen und Sets haben jeweils einige Befehle, die für den jeweiligen Datentyp spezifisch sind und in einigen Fällen durch ihren Anfangsbuchstaben gekennzeichnet sind:

  • Hashes: Befehle zum Bearbeiten von Hashes beginnen mitH, z. B.HSET,HGET oderHMSET.

  • Sets: Befehle zum Bearbeiten von Sätzen beginnen mit einemS, z. B.SCARD, der die Anzahl der Elemente auf den Satzwert erhält, der einem bestimmten Schlüssel entspricht.

  • Lists: Befehle zum Bearbeiten von Listen beginnen mitL oderR. Beispiele sindLPOP undRPUSH. DieL oderR beziehen sich auf die Seite der Liste, auf der gearbeitet wird. Ein paar Listenbefehlen ist auch einB vorangestellt, wasblocking bedeutet. Eine Blockierungsoperation lässt sich nicht von anderen Operationen unterbrechen, während sie ausgeführt wird. Zum Beispiel führtBLPOP einen blockierenden Left-Pop in einer Listenstruktur aus.

Note: Ein bemerkenswertes Merkmal des Redis-Listentyps ist, dass es sich eher um eine verknüpfte Liste als um ein Array handelt. Dies bedeutet, dass das Anhängen O (1) ist, während das Indizieren bei einer beliebigen Indexnummer O (N) ist.

Hier ist eine kurze Liste von Befehlen, die sich speziell auf die Datentypen string, hash, list und set in Redis beziehen:

Type Befehle

Sets

SADD,SCARD,SDIFF,SDIFFSTORE,SINTER,SINTERSTORE,SISMEMBER,SMEMBERS,SMOVE,SPOP,SRANDMEMBER,SREM,SSCAN,SUNION,SUNIONSTORE

Hashes

HDEL,HEXISTS,HGET,HGETALL,HINCRBY,HINCRBYFLOAT,HKEYS,HLEN,HMGET,HMSET,HSCAN,HSET,HSETNX,HSTRLEN,HVALS

Listen

BLPOP,BRPOP,BRPOPLPUSH,LINDEX,LINSERT,LLEN,LPOP,LPUSH,LPUSHX,LRANGE,LREM,LSET,LTRIM,RPOP,RPOPLPUSH,RPUSH,RPUSHX

Streicher

APPEND,BITCOUNT,BITFIELD,BITOP,BITPOS,DECR,DECRBY,GET,GETBIT,GETRANGE,GETSET,INCR,INCRBY,INCRBYFLOAT,MGET,MSET,MSETNX,PSETEX,SET,SETBIT,SETEX,SETNX,SETRANGE,STRLEN

Diese Tabelle ist kein vollständiges Bild der Redis-Befehle und -Typen. Es gibt eine Vielzahl fortgeschrittener Datentypen wiegeospatial items,sorted sets undHyperLogLog. Auf der Seite Rediscommandskönnen Sie nach Datenstrukturgruppen filtern. Es gibt auch diedata types summary undintroduction to Redis data types.

Da wir auf Python umsteigen werden, können Sie jetzt Ihre Spielzeugdatenbank mitFLUSHDB löschen undredis-cli REPL beenden:

127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> QUIT

Dadurch kehren Sie zu Ihrer Shell-Eingabeaufforderung zurück. Sie könnenredis-server im Hintergrund laufen lassen, da Sie es auch für den Rest des Tutorials benötigen.

Verwenden vonredis-py: Redis in Python

Nachdem Sie einige Grundlagen von Redis beherrschen, ist es an der Zeit, inredis-py zu springen, den Python-Client, mit dem Sie über eine benutzerfreundliche Python-API mit Redis kommunizieren können.

Erste Schritte

redis-py ist eine gut etablierte Python-Client-Bibliothek, mit der Sie über Python-Aufrufe direkt mit einem Redis-Server kommunizieren können:

$ python -m pip install redis

Stellen Sie als Nächstes sicher, dass Ihr Redis-Server im Hintergrund noch aktiv ist. Sie können mitpgrep redis-server prüfen. Wenn Sie mit leeren Händen auftauchen, starten Sie einen lokalen Server mitredis-server /etc/redis/6379.conf neu.

Kommen wir nun zum Python-zentrierten Teil der Dinge. Hier ist die "Hallo Welt" vonredis-py:

>>>

 1 >>> import redis
 2 >>> r = redis.Redis()
 3 >>> r.mset({"Croatia": "Zagreb", "Bahamas": "Nassau"})
 4 True
 5 >>> r.get("Bahamas")
 6 b'Nassau'

Redis, das in Zeile 2 verwendet wird, ist die zentrale Klasse des Pakets und das Arbeitspferd, mit dem Sie (fast) jeden Redis-Befehl ausführen. Die Verbindung und Wiederverwendung des TCP-Sockets erfolgt hinter den Kulissen für Sie, und Sie rufen Redis-Befehle mithilfe von Methoden für die Klasseninstanzr auf.

Beachten Sie auch, dass der Typ des zurückgegebenen Objektsb'Nassau' in Zeile 6 derbytes-Typ von Python und nichtstr ist. Es istbytes anstelle vonstr, der der häufigste Rückgabetyp inredis-py ist. Daher müssen Sie möglicherweiser.get("Bahamas").decode("utf-8") aufrufen, je nachdem, was Sie tatsächlich mit dem zurückgegebenen tun möchten bytestring.

Kommt Ihnen der obige Code bekannt vor? Die Methoden stimmen in fast allen Fällen mit dem Namen des Redis-Befehls überein, der dasselbe tut. Hier haben Sier.mset() undr.get() aufgerufen, dieMSET undGET in der nativen Redis-API entsprechen.

Dies bedeutet auch, dassHGETALL zur.hgetall(),PING zur.ping() wird und so weiter. Es gibtfew Ausnahmen, aber die Regel gilt für die große Mehrheit der Befehle.

Während die Redis-Befehlsargumente normalerweise in eine ähnlich aussehende Methodensignatur übersetzt werden, nehmen sie Python-Objekte an. Beispielsweise verwendet der Aufruf vonr.mset() im obigen Beispiel ein Pythondict als erstes Argument anstelle einer Folge von Bytestrings.

Wir haben dieRedis-Instanzr ohne Argumente erstellt, aber sie wird mit einer Reihe vonparameters gebündelt, wenn Sie sie benötigen:

# From redis/client.py
class Redis(object):
    def __init__(self, host='localhost', port=6379,
                 db=0, password=None, socket_timeout=None,
                 # ...

Sie können sehen, dass das Standardpaarhostname:portlocalhost:6379 ist, was genau das ist, was wir für unsere lokal gespeicherteredis-server-Instanz benötigen.

Der Parameterdb ist die Datenbanknummer. Sie können mehrere Datenbanken in Redis gleichzeitig verwalten, und jede wird durch eine Ganzzahl identifiziert. Die maximale Anzahl von Datenbanken beträgt standardmäßig 16.

Wenn Sie nurredis-cli über die Befehlszeile ausführen, beginnt dies bei Datenbank 0. Verwenden Sie das Flag-n, um eine neue Datenbank zu starten, wie inredis-cli -n 5.

Zulässige Schlüsseltypen

Eine wissenswerte Sache ist, dassredis-py erfordert, dass Sie ihm Schlüssel übergeben, diebytes,str,int oderfloat sind. (Die letzten 3 dieser Typen werden inbytes konvertiert, bevor sie an den Server gesendet werden.)

Stellen Sie sich einen Fall vor, in dem Sie Kalenderdaten als Schlüssel verwenden möchten:

>>>

>>> import datetime
>>> today = datetime.date.today()
>>> visitors = {"dan", "jon", "alex"}
>>> r.sadd(today, *visitors)
Traceback (most recent call last):
# ...
redis.exceptions.DataError: Invalid input of type: 'date'.
Convert to a byte, string or number first.

Sie müssen das Objekt Pythondateexplizit instr konvertieren, was Sie mit.isoformat() tun können:

>>>

>>> stoday = today.isoformat()  # Python 3.7+, or use str(today)
>>> stoday
'2019-03-10'
>>> r.sadd(stoday, *visitors)  # sadd: set-add
3
>>> r.smembers(stoday)
{b'dan', b'alex', b'jon'}
>>> r.scard(today.isoformat())
3

Zusammenfassend lässt sich sagen, dass Redis selbst nur Zeichenfolgen als Schlüssel zulässt. redis-py ist etwas liberaler in Bezug auf die akzeptierten Python-Typen, obwohl es letztendlich alles in Bytes konvertiert, bevor es an einen Redis-Server gesendet wird.

Beispiel: PyHats.com

Es ist Zeit, ein umfassenderes Beispiel zu nennen. Stellen wir uns vor, wir hätten beschlossen, eine lukrative Website, PyHats.com, zu starten, die unverschämt überteuerte Hüte an alle verkauft, die sie kaufen, und Sie mit der Erstellung der Website beauftragt.

Sie werden Redis verwenden, um einen Teil des Produktkatalogs, der Inventarisierung und der Erkennung des Bot-Verkehrs für PyHats.com zu verwalten.

Es ist der erste Tag für die Website und wir werden drei Hüte in limitierter Auflage verkaufen. Jeder Hut wird in einem Redis-Hash von Feld-Wert-Paaren gehalten, und der Hash hat einen Schlüssel, der eine zufällige Ganzzahl mit Präfix ist, wie z. B.hat:56854717. Die Verwendung des Präfixhat: ist die Redis-Konvention zum Erstellen einer Art Namensraum innerhalb einer Redis-Datenbank:

import random

random.seed(444)
hats = {f"hat:{random.getrandbits(32)}": i for i in (
    {
        "color": "black",
        "price": 49.99,
        "style": "fitted",
        "quantity": 1000,
        "npurchased": 0,
    },
    {
        "color": "maroon",
        "price": 59.99,
        "style": "hipster",
        "quantity": 500,
        "npurchased": 0,
    },
    {
        "color": "green",
        "price": 99.99,
        "style": "baseball",
        "quantity": 200,
        "npurchased": 0,
    })
}

Beginnen wir mit der Datenbank1, da wir in einem vorherigen Beispiel die Datenbank0 verwendet haben:

>>>

>>> r = redis.Redis(db=1)

Um diese Daten zum ersten Mal in Redis zu schreiben, können wir.hmset() (Hash-Multi-Set) verwenden und sie für jedes Wörterbuch aufrufen. Das "Multi" ist eine Referenz zum Festlegen mehrerer Feld-Wert-Paare, wobei "Feld" in diesem Fall einem Schlüssel eines der verschachtelten Wörterbücher inhats entspricht:

 1 >>> with r.pipeline() as pipe:
 2 ...    for h_id, hat in hats.items():
 3 ...        pipe.hmset(h_id, hat)
 4 ...    pipe.execute()
 5 Pipeline>>
 6 Pipeline>>
 7 Pipeline>>
 8 [True, True, True]
 9
10 >>> r.bgsave()
11 True

Der obige Codeblock führt auch das Konzept von Redispipelining ein. Auf diese Weise können Sie die Anzahl der Roundtrip-Transaktionen reduzieren, die Sie zum Schreiben oder Lesen von Daten von Ihrem Redis-Server benötigen. Wenn Sie nur dreimalr.hmset() aufgerufen hätten, wäre für jede geschriebene Zeile ein Hin- und Her-Roundtrip-Vorgang erforderlich.

Bei einer Pipeline werden alle Befehle auf der Clientseite gepuffert und dann auf einen Schlag mitpipe.hmset() in Zeile 3 auf einmal gesendet. Aus diesem Grund werden alle dreiTrueAntworten auf einmal zurückgegeben, wenn Sie in Zeile 4pipe.execute() aufrufen. In Kürze wird ein erweiterter Anwendungsfall für eine Pipeline angezeigt.

Note: Die Redis-Dokumente bietenexample, um dasselbe mitredis-cli zu tun, wobei Sie den Inhalt einer lokalen Datei zum Masseneinfügen weiterleiten können.

Lassen Sie uns kurz überprüfen, ob sich alles in unserer Redis-Datenbank befindet:

>>>

>>> pprint(r.hgetall("hat:56854717"))
{b'color': b'green',
 b'npurchased': b'0',
 b'price': b'99.99',
 b'quantity': b'200',
 b'style': b'baseball'}

>>> r.keys()  # Careful on a big DB. keys() is O(N)
[b'56854717', b'1236154736', b'1326692461']

Das erste, was wir simulieren möchten, ist, was passiert, wenn ein Benutzer aufPurchase klickt. Wenn der Artikel auf Lager ist, erhöhen Sie seinenpurchased um 1 und verringern Sie seinequantity (Inventar) um 1. Sie können.hincrby() verwenden, um dies zu tun:

>>>

>>> r.hincrby("hat:56854717", "quantity", -1)
199
>>> r.hget("hat:56854717", "quantity")
b'199'
>>> r.hincrby("hat:56854717", "npurchased", 1)
1

Note:HINCRBY arbeitet immer noch mit einem Hashwert, der eine Zeichenfolge ist, versucht jedoch, die Zeichenfolge als 64-Bit-Ganzzahl mit Vorzeichen 10 zu interpretieren, um die Operation auszuführen.

Dies gilt für andere Befehle, die sich auf das Inkrementieren und Dekrementieren für andere Datenstrukturen beziehen, nämlichINCR,INCRBY,INCRBYFLOAT,ZINCRBY undHINCRBYFLOAT. Sie erhalten eine Fehlermeldung, wenn die Zeichenfolge am Wert nicht als Ganzzahl dargestellt werden kann.

Es ist jedoch nicht wirklich so einfach. Das Ändern vonquantity undnpurchased in zwei Codezeilen verbirgt die Tatsache, dass ein Klick, ein Kauf und eine Zahlung mehr bedeuten. Wir müssen noch ein paar Überprüfungen durchführen, um sicherzustellen, dass wir niemanden mit einer leichteren Brieftasche und ohne Hut zurücklassen:

  • Step 1: Überprüfen Sie, ob der Artikel auf Lager ist, oder lösen Sie auf andere Weise eine Ausnahme im Backend aus.

  • Step 2: Wenn es auf Lager ist, führen Sie die Transaktion aus, verringern Sie das Feldquantity und erhöhen Sie das Feldnpurchased.

  • Step 3: Achten Sie auf Änderungen, die das Inventar zwischen den ersten beiden Schritten verändern (arace condition).

Schritt 1 ist relativ einfach: Er besteht aus einem.hget(), um die verfügbare Menge zu überprüfen.

Schritt 2 ist etwas komplizierter. Das Paar von Erhöhungs- und Verringerungsoperationen mussatomically ausgeführt werden: Entweder sollten beide erfolgreich abgeschlossen werden, oder beide sollten nicht abgeschlossen sein (falls mindestens eine fehlschlägt).

Bei Client-Server-Frameworks ist es immer wichtig, auf die Atomizität zu achten und darauf zu achten, was in Fällen, in denen mehrere Clients gleichzeitig versuchen, mit dem Server zu kommunizieren, schief gehen kann. Die Antwort darauf in Redis besteht darin, einentransaction-Block zu verwenden, was bedeutet, dass entweder beide oder keiner der Befehle durchkommt.

Inredis-py istPipeline standardmäßig einetransactional pipeline-Klasse. Dies bedeutet, dass die Klasse, obwohl sie tatsächlich nach etwas anderem benannt ist (Pipelining), auch zum Erstellen eines Transaktionsblocks verwendet werden kann.

In Redis beginnt eine Transaktion mitMULTI und endet mitEXEC:

 1 127.0.0.1:6379> MULTI
 2 127.0.0.1:6379> HINCRBY 56854717 quantity -1
 3 127.0.0.1:6379> HINCRBY 56854717 npurchased 1
 4 127.0.0.1:6379> EXEC

MULTI (Zeile 1) markiert den Beginn der Transaktion undEXEC (Zeile 4) markiert das Ende. Alles dazwischen wird als eine Alles-oder-Nichts-gepufferte Folge von Befehlen ausgeführt. Dies bedeutet, dass es unmöglich ist,quantity (Zeile 2) zu dekrementieren, aber dann die Inkrementierungsoperation des Ausgleichsnpurchasedfehlschlägt (Zeile 3).

Kehren wir zu Schritt 3 zurück: Wir müssen uns aller Änderungen bewusst sein, die das Inventar zwischen den ersten beiden Schritten verändern.

Schritt 3 ist der schwierigste. Nehmen wir an, es befindet sich noch ein einziger Hut in unserem Inventar. Zwischen der Zeit, in der Benutzer A die Anzahl der verbleibenden Hüte überprüft und ihre Transaktion tatsächlich verarbeitet, überprüft Benutzer B auch das Inventar und stellt ebenfalls fest, dass ein Hut auf Lager ist. Beide Benutzer dürfen den Hut kaufen, aber wir haben 1 Hut zu verkaufen, nicht 2, also sind wir am Haken und ein Benutzer hat kein Geld mehr. Nicht gut.

Redis hat eine clevere Antwort auf das Dilemma in Schritt 3: Es heißtoptimistic locking und unterscheidet sich von der typischen Sperrung in einem RDBMS wie PostgreSQL. Optimistisches Sperren bedeutet auf den Punkt gebracht, dass die aufrufende Funktion (Client) keine Sperre erhält, sondern auf Änderungen der Daten überwacht, die sie induring the time it would have held a lock schreibt. Wenn während dieser Zeit ein Konflikt auftritt, versucht die aufrufende Funktion einfach den gesamten Vorgang erneut.

Sie können eine optimistische Sperre erzielen, indem Sie den BefehlWATCH (.watch() inredis-py) verwenden, der das Verhalten voncheck-and-setliefert.

Lassen Sie uns einen großen Teil des Codes einführen und anschließend Schritt für Schritt durchgehen. Sie können sich vorstellen, dassbuyitem() jedes Mal aufgerufen wird, wenn ein Benutzer auf die SchaltflächeBuy Now oderPurchase klickt. Der Zweck besteht darin, zu bestätigen, dass der Artikel auf Lager ist, und auf der Grundlage dieses Ergebnisses eine Maßnahme zu ergreifen, die auf sichere Weise auf Rennbedingungen achtet und erneut versucht, falls einer erkannt wird:

 1 import logging
 2 import redis
 3
 4 logging.basicConfig()
 5
 6 class OutOfStockError(Exception):
 7     """Raised when PyHats.com is all out of today's hottest hat"""
 8
 9 def buyitem(r: redis.Redis, itemid: int) -> None:
10     with r.pipeline() as pipe:
11         error_count = 0
12         while True:
13             try:
14                 # Get available inventory, watching for changes
15                 # related to this itemid before the transaction
16                 pipe.watch(itemid)
17                 nleft: bytes = r.hget(itemid, "quantity")
18                 if nleft > b"0":
19                     pipe.multi()
20                     pipe.hincrby(itemid, "quantity", -1)
21                     pipe.hincrby(itemid, "npurchased", 1)
22                     pipe.execute()
23                     break
24                 else:
25                     # Stop watching the itemid and raise to break out
26                     pipe.unwatch()
27                     raise OutOfStockError(
28                         f"Sorry, {itemid} is out of stock!"
29                     )
30             except redis.WatchError:
31                 # Log total num. of errors by this user to buy this item,
32                 # then try the same process again of WATCH/HGET/MULTI/EXEC
33                 error_count += 1
34                 logging.warning(
35                     "WatchError #%d: %s; retrying",
36                     error_count, itemid
37                 )
38     return None

Die kritische Zeile tritt in Zeile 16 mitpipe.watch(itemid) auf, wodurch Redis angewiesen wird, die angegebenenitemid auf Änderungen ihres Werts zu überwachen. Das Programm überprüft das Inventar durch den Aufruf vonr.hget(itemid, "quantity") in Zeile 17:

16 pipe.watch(itemid)
17 nleft: bytes = r.hget(itemid, "quantity")
18 if nleft > b"0":
19     # Item in stock. Proceed with transaction.

Wenn das Inventar während dieses kurzen Fensters zwischen dem Überprüfen des Artikelbestands und dem Versuch, ihn zu kaufen, berührt wird, gibt Redis einen Fehler zurück undredis-py löst einenWatchError aus (Zeile 30). Das heißt, wenn sich einer der Hashs, auf dieitemid zeigt, nach dem Aufruf von.hget(), aber vor den nachfolgenden Aufrufen von.hincrby() in den Zeilen 20 und 21 ändert, führen wir das Ganze erneut aus als Ergebnis in einer anderen Iteration derwhile True-Schleife verarbeiten.

Dies ist der „optimistische“ Teil der Sperrung: Anstatt dem Client durch die Abruf- und Einstellungsvorgänge eine zeitaufwändige Gesamtsperre für die Datenbank zu ermöglichen, überlassen wir es Redis, den Client und den Benutzer nur in diesem Fall zu benachrichtigen fordert einen erneuten Versuch der Bestandsaufnahme.

Ein Schlüssel hier ist das Verständnis des Unterschieds zwischenclient-side undserver-side Operationen:

nleft = r.hget(itemid, "quantity")

Diese Python-Zuweisung liefert das Ergebnis der clientseitigenr.hget(). Umgekehrt puffern Methoden, die Sie aufpipe aufrufen, effektiv alle Befehle in einem und senden sie dann in einer einzigen Anforderung an den Server:

16 pipe.multi()
17 pipe.hincrby(itemid, "quantity", -1)
18 pipe.hincrby(itemid, "npurchased", 1)
19 pipe.execute()

In der Mitte der Transaktionspipeline werden keine Daten auf die Clientseite zurückgesendet. Sie müssen.execute() (Zeile 19) aufrufen, um die Ergebnissequenz auf einmal zurückzugewinnen.

Obwohl dieser Block zwei Befehle enthält, besteht er aus genau einem Roundtrip-Vorgang vom Client zum Server und zurück.

Das bedeutet, dass der Clientuse nicht sofort das Ergebnis vonpipe.hincrby(itemid, "quantity", -1) aus Zeile 20 sein kann, da Methoden aufPipeline nur die Instanz vonpipeelbst zurückgeben. Wir haben zu diesem Zeitpunkt noch nichts vom Server verlangt. Während.hincrby() normalerweise den resultierenden Wert zurückgibt, können Sie ihn auf der Clientseite nicht sofort referenzieren, bis die gesamte Transaktion abgeschlossen ist.

Es gibt einen Haken: Aus diesem Grund können Sie den Aufruf von.hget() nicht in den Transaktionsblock einfügen. Wenn Sie dies getan haben, können Sie noch nicht wissen, ob Sie das Feldnpurchasedinkrementieren möchten, da Sie keine Echtzeitergebnisse von Befehlen erhalten können, die in eine Transaktionspipeline eingefügt werden.

Wenn sich das Inventar bei Null befindet, geben wirUNWATCHdie Artikel-ID ein und erhöhenOutOfStockError (Zeile 27), um letztendlich die begehrteSold Out-Seite anzuzeigen, die unsere Hutkäufer dringend begehren wird um noch mehr unserer Hüte zu immer ausgefalleneren Preisen zu kaufen:

24 else:
25     # Stop watching the itemid and raise to break out
26     pipe.unwatch()
27     raise OutOfStockError(
28         f"Sorry, {itemid} is out of stock!"
29     )

Hier ist eine Illustration. Beachten Sie, dass unsere Startmenge199 für Hut 56854717 ist, da wir oben.hincrby() genannt haben. Imitieren wir drei Käufe, bei denen die Felderquantity undnpurchasedgeändert werden sollten:

>>>

>>> buyitem(r, "hat:56854717")
>>> buyitem(r, "hat:56854717")
>>> buyitem(r, "hat:56854717")
>>> r.hmget("hat:56854717", "quantity", "npurchased")  # Hash multi-get
[b'196', b'4']

Jetzt können wir mehr Käufe vorspulen und einen Strom von Käufen nachahmen, bis der Bestand auf Null sinkt. Stellen Sie sich diese wieder vor, die von einer ganzen Reihe verschiedener Clients stammen und nicht nur von einerRedis-Instanz:

>>>

>>> # Buy remaining 196 hats for item 56854717 and deplete stock to 0
>>> for _ in range(196):
...     buyitem(r, "hat:56854717")
>>> r.hmget("hat:56854717", "quantity", "npurchased")
[b'0', b'200']

Wenn ein armer Benutzer zu spät zum Spiel kommt, sollte ihm einOutOfStockErrorangezeigt werden, das unsere Anwendung anweist, eine Fehlermeldungsseite im Frontend zu rendern:

>>>

>>> buyitem(r, "hat:56854717")
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 20, in buyitem
__main__.OutOfStockError: Sorry, hat:56854717 is out of stock!

Es scheint an der Zeit zu sein, Nachschub zu leisten.

Verwenden des Schlüsselablaufs

Lassen Sie unskey expiry einführen, ein weiteres Unterscheidungsmerkmal in Redis. Wenn Sieexpireals Schlüssel verwenden, werden dieser Schlüssel und sein entsprechender Wert nach einer bestimmten Anzahl von Sekunden oder zu einem bestimmten Zeitstempel automatisch aus der Datenbank gelöscht.

Inredis-py können Sie dies unter anderem durch.setex() erreichen, mit dem Sie ein grundlegendes Schlüssel-Wert-Paar vonstring:stringmit einem Ablauf festlegen können:

>>>

 1 >>> from datetime import timedelta
 2
 3 >>> # setex: "SET" with expiration
 4 >>> r.setex(
 5 ...     "runner",
 6 ...     timedelta(minutes=1),
 7 ...     value="now you see me, now you don't"
 8 ... )
 9 True

Sie können das zweite Argument als Zahl in Sekunden oder alstimedelta-Objekt angeben, wie in Zeile 6 oben. Ich mag letzteres, weil es weniger zweideutig und bewusster erscheint.

Es gibt auch Methoden (und natürlich entsprechende Redis-Befehle), um die verbleibende Lebensdauer (time-to-live) eines Schlüssels zu ermitteln, dessen Ablauf festgelegt wurde:

>>>

>>> r.ttl("runner")  # "Time To Live", in seconds
58
>>> r.pttl("runner")  # Like ttl, but milliseconds
54368

Unten können Sie das Fenster bis zum Ablauf beschleunigen und dann beobachten, wie der Schlüssel abläuft. Danach gebenr.get()None und.exists()0 zurück:

>>>

>>> r.get("runner")  # Not expired yet
b"now you see me, now you don't"

>>> r.expire("runner", timedelta(seconds=3))  # Set new expire window
True
>>> # Pause for a few seconds
>>> r.get("runner")
>>> r.exists("runner")  # Key & value are both gone (expired)
0

In der folgenden Tabelle sind die Befehle zum Ablauf von Schlüsselwerten zusammengefasst, einschließlich der oben beschriebenen. Die Erklärungen stammen direkt ausredis-py's Method Docstrings:

Unterschrift Zweck

r.setex(name, time, value)

Setzt den Wert von Schlüsselname aufvalue, der intime Sekunden abläuft, wobeitime durchint oder Pythontimedelta dargestellt werden kann Objekt

r.psetex(name, time_ms, value)

Setzt den Wert von Schlüsselname aufvalue, der intime_ms Millisekunden abläuft, wobeitime_ms durchint oder Pythontimedelta dargestellt werden kann Objekt

r.expire(name, time)

Setzt ein Ablaufflag für Schlüsselname fürtime Sekunden, wobeitime durch einint- oder ein Pythontimedelta-Objekt dargestellt werden kann

r.expireat(name, when)

Setzt ein Ablaufflag für Schlüsselname, wobeiwhen alsint dargestellt werden kann, das die Unix-Zeit oder ein Pythondatetime-Objekt angibt

r.persist(name)

Entfernt einen Ablauf vonname

r.pexpire(name, time)

Setzt ein Ablaufflag für Schlüsselname fürtime Millisekunden, undtime kann durch einint- oder ein Pythontimedelta-Objekt dargestellt werden

r.pexpireat(name, when)

Setzt ein Ablaufflag für Schlüsselname, wobeiwhen alsint dargestellt werden kann, die die Unix-Zeit in Millisekunden (Unix-Zeit * 1000) oder ein Pythondatetime-Objekt darstellen

r.pttl(name)

Gibt die Anzahl der Millisekunden zurück, bis der Schlüsselname abläuft

r.ttl(name)

Gibt die Anzahl der Sekunden zurück, bis der Schlüsselname abläuft

PyHats.com, Part 2

Einige Tage nach seinem Debüt hat PyHats.com so viel Hype ausgelöst, dass einige unternehmungslustige Benutzer Bots erstellen, um innerhalb von Sekunden Hunderte von Artikeln zu kaufen, was Ihrer Meinung nach nicht gut für die langfristige Gesundheit Ihres Hutgeschäfts ist.

Nachdem Sie nun erfahren haben, wie Schlüssel ablaufen, können Sie sie im Backend von PyHats.com verwenden.

Wir werden einen neuen Redis-Client erstellen, der als Verbraucher (oder Beobachter) fungiert und einen Strom eingehender IP-Adressen verarbeitet, die wiederum von mehreren HTTPS-Verbindungen zum Server der Website stammen können.

Ziel des Beobachters ist es, einen Strom von IP-Adressen aus mehreren Quellen zu überwachen und innerhalb verdächtig kurzer Zeit nach einer Flut von Anfragen von einer einzelnen Adresse Ausschau zu halten.

Einige Middleware auf dem Website-Server überträgt alle eingehenden IP-Adressen in eine Redis-Liste mit.lpush(). Hier ist eine grobe Methode, um einige eingehende IPs mithilfe einer neuen Redis-Datenbank nachzuahmen:

>>>

>>> r = redis.Redis(db=5)
>>> r.lpush("ips", "51.218.112.236")
1
>>> r.lpush("ips", "90.213.45.98")
2
>>> r.lpush("ips", "115.215.230.176")
3
>>> r.lpush("ips", "51.218.112.236")
4

Wie Sie sehen können, gibt.lpush() die Länge der Liste zurück, nachdem der Push-Vorgang erfolgreich war. Bei jedem Aufruf von.lpush() wird die IP an den Anfang der Redis-Liste gesetzt, die durch die Zeichenfolge"ips" verschlüsselt wird.

In dieser vereinfachten Simulation stammen die Anforderungen technisch alle vom selben Client. Sie können sich jedoch vorstellen, dass sie möglicherweise von vielen verschiedenen Clients stammen und alle auf demselben Redis-Server in dieselbe Datenbank übertragen werden.

Öffnen Sie jetzt eine neue Shell-Registerkarte oder ein neues Fenster und starten Sie eine neue Python-REPL. In dieser Shell erstellen Sie einen neuen Client, der einem ganz anderen Zweck dient als der Rest. Er befindet sich in einer endlosenwhile True-Schleife und blockiertBLPOPmitips) s Liste, die jede Adresse verarbeitet:

 1 # New shell window or tab
 2
 3 import datetime
 4 import ipaddress
 5
 6 import redis
 7
 8 # Where we put all the bad egg IP addresses
 9 blacklist = set()
10 MAXVISITS = 15
11
12 ipwatcher = redis.Redis(db=5)
13
14 while True:
15     _, addr = ipwatcher.blpop("ips")
16     addr = ipaddress.ip_address(addr.decode("utf-8"))
17     now = datetime.datetime.utcnow()
18     addrts = f"{addr}:{now.minute}"
19     n = ipwatcher.incrby(addrts, 1)
20     if n >= MAXVISITS:
21         print(f"Hat bot detected!:  {addr}")
22         blacklist.add(addr)
23     else:
24         print(f"{now}:  saw {addr}")
25     _ = ipwatcher.expire(addrts, 60)

Lassen Sie uns einige wichtige Konzepte durchgehen.

Dieipwatcher verhalten sich wieconsumer, sitzen herum und warten darauf, dass neue IPs auf die Redis-Liste von"ips"gesetzt werden. Es empfängt sie alsbytes, wie z. B. b ”51.218.112.236”, und macht sie mit dem Modulipaddress zu einem angemessenerenaddress object:

15 _, addr = ipwatcher.blpop("ips")
16 addr = ipaddress.ip_address(addr.decode("utf-8"))

Dann bilden Sie einen Redis-String-Schlüssel unter Verwendung der Adresse und der Minute der Stunde, zu der dieipwatcher die Adresse gesehen haben, erhöhen die entsprechende Anzahl um1 und erhalten dabei die neue Anzahl:

17 now = datetime.datetime.utcnow()
18 addrts = f"{addr}:{now.minute}"
19 n = ipwatcher.incrby(addrts, 1)

Wenn die Adresse mehr alsMAXVISITS gesehen hat, sieht es so aus, als hätten wir einen PyHats.com-Web-Scraper in der Hand, der versucht, die nächstentulip bubble zu erstellen. Leider haben wir keine andere Wahl, als diesem Benutzer so etwas wie einen gefürchteten 403-Statuscode zurückzugeben.

Wir verwendenipwatcher.expire(addrts, 60), um die(address minute)-Kombination 60 Sekunden nach dem letzten Mal abzulaufen. Dies soll verhindern, dass unsere Datenbank mit veralteten einmaligen Seitenbetrachtern verstopft wird.

Wenn Sie diesen Codeblock in einer neuen Shell ausführen, sollte diese Ausgabe sofort angezeigt werden:

2019-03-11 15:10:41.489214:  saw 51.218.112.236
2019-03-11 15:10:41.490298:  saw 115.215.230.176
2019-03-11 15:10:41.490839:  saw 90.213.45.98
2019-03-11 15:10:41.491387:  saw 51.218.112.236

Die Ausgabe wird sofort angezeigt, da diese vier IPs in der Warteschlangenliste mit"ips" gespeichert waren und darauf warteten, von unserenipwatcher herausgezogen zu werden. Wenn Sie.blpop() (oder den BefehlBLPOP) verwenden, wird blockiert, bis ein Element in der Liste verfügbar ist, und es wird dann entfernt. Es verhält sich wie PythonsQueue.get(), das auch blockiert, bis ein Element verfügbar ist.

Neben dem Ausspucken von IP-Adressen hat unseripwatchereinen zweiten Job. Für eine bestimmte Minute einer Stunde (Minute 1 bis Minute 60) klassifiziertipwatcher eine IP-Adresse als Hat-Bot, wenn in dieser Minute 15 oder mehrGET-Anfragen gesendet werden.

Wechseln Sie zurück zu Ihrer ersten Shell und ahmen Sie einen Seitenschaber nach, der die Site innerhalb weniger Millisekunden mit 20 Anfragen überflutet:

for _ in range(20):
    r.lpush("ips", "104.174.118.18")

Wechseln Sie schließlich zurück zur zweiten Shell, dieipwatcher enthält, und Sie sollten eine Ausgabe wie diese sehen:

2019-03-11 15:15:43.041363:  saw 104.174.118.18
2019-03-11 15:15:43.042027:  saw 104.174.118.18
2019-03-11 15:15:43.042598:  saw 104.174.118.18
2019-03-11 15:15:43.043143:  saw 104.174.118.18
2019-03-11 15:15:43.043725:  saw 104.174.118.18
2019-03-11 15:15:43.044244:  saw 104.174.118.18
2019-03-11 15:15:43.044760:  saw 104.174.118.18
2019-03-11 15:15:43.045288:  saw 104.174.118.18
2019-03-11 15:15:43.045806:  saw 104.174.118.18
2019-03-11 15:15:43.046318:  saw 104.174.118.18
2019-03-11 15:15:43.046829:  saw 104.174.118.18
2019-03-11 15:15:43.047392:  saw 104.174.118.18
2019-03-11 15:15:43.047966:  saw 104.174.118.18
2019-03-11 15:15:43.048479:  saw 104.174.118.18
Hat bot detected!:  104.174.118.18
Hat bot detected!:  104.174.118.18
Hat bot detected!:  104.174.118.18
Hat bot detected!:  104.174.118.18
Hat bot detected!:  104.174.118.18
Hat bot detected!:  104.174.118.18

Nun, [.keys] #Ctrl [.kbd .key-c] # C ## aus der "+ while True" -Schleife und Sie werden sehen, dass die beleidigende IP zu Ihrer Blacklist hinzugefügt wurde:

>>>

>>> blacklist
{IPv4Address('104.174.118.18')}

Können Sie den Defekt in diesem Erkennungssystem finden? Der Filter überprüft die Minute als.minute und nicht alslast 60 seconds (eine rollierende Minute). Es wäre schwieriger, eine fortlaufende Prüfung durchzuführen, um zu überwachen, wie oft ein Benutzer in den letzten 60 Sekunden gesehen wurde. Es gibt eine clevere Lösung, bei der die sortierten Sätze von Redis beiClassDojo verwendet werden. Josiah CarlsonsRedis in Action bietet auch ein ausführlicheres und allgemeineres Beispiel für diesen Abschnitt unter Verwendung einer IP-to-Location-Cache-Tabelle.

Ausdauer und Schnappschuss

Einer der Gründe, warum Redis sowohl beim Lesen als auch beim Schreiben so schnell ist, besteht darin, dass die Datenbank auf dem Server im Arbeitsspeicher (RAM) gespeichert ist. Eine Redis-Datenbank kann jedoch auch in einem Prozess namenssnapshotting auf der Festplatte gespeichert (beibehalten) werden. Der Grund dafür ist, eine physische Sicherung im Binärformat zu führen, damit Daten rekonstruiert und bei Bedarf wieder in den Speicher gestellt werden können, z. B. beim Serverstart.

Sie haben das Snapshotting bereits aktiviert, ohne es zu wissen, als Sie zu Beginn dieses Lernprogramms die Grundkonfiguration mit der Optionsave eingerichtet haben:

# /etc/redis/6379.conf

port              6379
daemonize         yes
save              60 1
bind              127.0.0.1
tcp-keepalive     300
dbfilename        dump.rdb
dir               ./
rdbcompression    yes

Das Format istsave <seconds> <changes>. Dadurch wird Redis angewiesen, die Datenbank auf der Festplatte zu speichern, wenn sowohl die angegebene Anzahl von Sekunden als auch die Anzahl der Schreibvorgänge für die Datenbank aufgetreten sind. In diesem Fall weisen wir Redis an, die Datenbank alle 60 Sekunden auf der Festplatte zu speichern, wenn in dieser Zeitspanne von 60 Sekunden mindestens ein Änderungsschreibvorgang durchgeführt wurde. Dies ist eine ziemlich aggressive Einstellung gegenüber densample Redis config file, die diese dreisave-Direktiven verwendet:

# Default redis/redis.conf
save 900 1
save 300 10
save 60 10000

EinRDB snapshot ist eine vollständige (und keine inkrementelle) Erfassung der Datenbank zu einem bestimmten Zeitpunkt. (RDB bezieht sich auf eine Redis-Datenbankdatei.) Wir haben auch das Verzeichnis und den Dateinamen der resultierenden Datendatei angegeben, die geschrieben wird:

# /etc/redis/6379.conf

port              6379
daemonize         yes
save              60 1
bind              127.0.0.1
tcp-keepalive     300
dbfilename        dump.rdb
dir               ./
rdbcompression    yes

Dies weist Redis an, in einer Binärdatendatei mit dem Namendump.rdb im aktuellen Arbeitsverzeichnis zu speichern, von wo ausredis-server ausgeführt wurde:

$ file -b dump.rdb
data

Sie können einen Speicher auch manuell mit dem Redis-BefehlBGSAVE aufrufen:

127.0.0.1:6379> BGSAVE
Background saving started

Der „BG“ inBGSAVE zeigt an, dass das Speichern im Hintergrund erfolgt. Diese Option ist auch in derredis-py-Methode verfügbar:

>>>

>>> r.lastsave()  # Redis command: LASTSAVE
datetime.datetime(2019, 3, 10, 21, 56, 50)
>>> r.bgsave()
True
>>> r.lastsave()
datetime.datetime(2019, 3, 10, 22, 4, 2)

In diesem Beispiel wird ein weiterer neuer Befehl und eine neue Methode vorgestellt:.lastsave(). In Redis wird der Unix-Zeitstempel des letzten DB-Speichers zurückgegeben, den Python Ihnen alsdatetime-Objekt zurückgibt. Oben sehen Sie, dass sich das Ergebnis vonr.lastsave()aufgrund vonr.bgsave() ändert.

r.lastsave() ändert sich auch, wenn Sie die automatische Momentaufnahme mit der Konfigurationsoptionsave aktivieren.

Um all dies neu zu formulieren, gibt es zwei Möglichkeiten, um Snapshots zu aktivieren:

  1. Explizit über den Redis-BefehlBGSAVE oderredis-py Methode.bgsave()

  2. Implizit über die Konfigurationsoptionsave (die Sie auch mit.config_set() inredis-py festlegen können).

RDB-Snapshots sind schnell, da der übergeordnete Prozess den Systemaufruffork()verwendet, um das zeitintensive Schreiben auf die Festplatte an einen untergeordneten Prozess weiterzuleiten, damit der übergeordnete Prozess seinen Weg fortsetzen kann. Darauf bezieht sichbackground inBGSAVE.

Es gibt auchSAVE (.save() inredis-py), aber dies führt eine synchrone (blockierende) Speicherung durch, anstattfork() zu verwenden, sodass Sie es nicht ohne einen bestimmten Grund verwenden sollten .

Obwohl.bgsave() im Hintergrund auftritt, ist dies nicht ohne Kosten. Die Zeit für das Auftreten vonfork() selbst kann tatsächlich erheblich sein, wenn die Redis-Datenbank überhaupt groß genug ist.

Wenn dies ein Problem darstellt oder Sie es sich nicht leisten können, auch nur einen winzigen Teil der Daten zu verpassen, der aufgrund der periodischen Natur des RDB-Snapshots verloren gegangen ist, sollten Sie sich die alternative Strategieappend-only file (AOF) ansehen zum Schnappschuss. AOF kopiert Redis-Befehle in Echtzeit auf die Festplatte, sodass Sie eine wörtliche befehlsbasierte Rekonstruktion durchführen können, indem Sie diese Befehle wiedergeben.

Problemumgehungen für die Serialisierung

Kommen wir zurück zu den Redis-Datenstrukturen. Mit seiner Hash-Datenstruktur unterstützt Redis das Verschachteln auf einer Ebene:

127.0.0.1:6379> hset mykey field1 value1

Das Python-Client-Äquivalent würde folgendermaßen aussehen:

r.hset("mykey", "field1", "value1")

Hier können Sie sich"field1": "value1" als das Schlüssel-Wert-Paar eines Python-Dikts{"field1": "value1"} vorstellen, währendmykey der Schlüssel der obersten Ebene ist:

Redis-Befehl Pure-Python-Äquivalent

r.set("key", "value")

r = {"key": "value"}

r.hset("key", "field", "value")

r = {"key": {"field": "value"}}

Was aber, wenn der Wert dieses Wörterbuchs (der Redis-Hash) etwas anderes als eine Zeichenfolge enthalten soll, z. B. einlist oder ein verschachteltes Wörterbuch mit Zeichenfolgen als Werten?

Hier ist ein Beispiel mit einigenJSON-ähnlichen Daten, um die Unterscheidung klarer zu machen:

restaurant_484272 = {
    "name": "Ravagh",
    "type": "Persian",
    "address": {
        "street": {
            "line1": "11 E 30th St",
            "line2": "APT 1",
        },
        "city": "New York",
        "state": "NY",
        "zip": 10016,
    }
}

Angenommen, wir möchten einen Redis-Hash mit den Schlüssel484272 und Feldwertpaaren setzen, die den Schlüsselwertpaaren vonrestaurant_484272 entsprechen. Redis unterstützt dies nicht direkt, darestaurant_484272 verschachtelt ist:

>>>

>>> r.hmset(484272, restaurant_484272)
Traceback (most recent call last):
# ...
redis.exceptions.DataError: Invalid input of type: 'dict'.
Convert to a byte, string or number first.

Sie können diese Funktion tatsächlich mit Redis ausführen. Es gibt zwei verschiedene Möglichkeiten, verschachtelte Daten inredis-py und Redis nachzuahmen:

  1. Serialisieren Sie die Werte zu einer Zeichenfolge mitjson.dumps()

  2. Verwenden Sie ein Trennzeichen in den Schlüsselzeichenfolgen, um die Verschachtelung in den Werten nachzuahmen

Schauen wir uns jeweils ein Beispiel an.

Option 1: Serialisieren Sie die Werte in einen String

Sie könnenjson.dumps() verwenden, umdict in eine JSON-formatierte Zeichenfolge zu serialisieren:

>>>

>>> import json
>>> r.set(484272, json.dumps(restaurant_484272))
True

Wenn Sie.get() aufrufen, ist der Wert, den Sie zurückerhalten, einbytes-Objekt. Vergessen Sie also nicht, es zu deserialisieren, um das ursprüngliche Objekt zurückzugewinnen. json.dumps() undjson.loads() sind Umkehrungen voneinander, um Daten zu serialisieren bzw. zu deserialisieren:

>>>

>>> from pprint import pprint
>>> pprint(json.loads(r.get(484272)))
{'address': {'city': 'New York',
             'state': 'NY',
             'street': '11 E 30th St',
             'zip': 10016},
 'name': 'Ravagh',
 'type': 'Persian'}

Dies gilt für jedes Serialisierungsprotokoll, wobeiyaml eine andere häufig verwendete Option ist:

>>>

>>> import yaml  # python -m pip install PyYAML
>>> yaml.dump(restaurant_484272)
'address: {city: New York, state: NY, street: 11 E 30th St, zip: 10016}\nname: Ravagh\ntype: Persian\n'

Unabhängig davon, für welches Serialisierungsprotokoll Sie sich entscheiden, ist das Konzept dasselbe: Sie nehmen ein für Python einzigartiges Objekt und konvertieren es in einen Bytestring, der in mehreren Sprachen erkannt und ausgetauscht werden kann.

Option 2: Verwenden Sie ein Trennzeichen in Schlüsselzeichenfolgen

Es gibt eine zweite Option, bei der die "Verschachtelung" durch Verketten mehrerer Schlüsselebenen in einem Pythondict nachgeahmt wird. Dies besteht darin, das verschachtelte Wörterbuch umrecursion zu reduzieren, sodass jeder Schlüssel eine verkettete Zeichenfolge ist und die Werte die am tiefsten verschachtelten Werte aus dem ursprünglichen Wörterbuch sind. Betrachten Sie unser Wörterbuchobjektrestaurant_484272:

restaurant_484272 = {
    "name": "Ravagh",
    "type": "Persian",
    "address": {
        "street": {
            "line1": "11 E 30th St",
            "line2": "APT 1",
        },
        "city": "New York",
        "state": "NY",
        "zip": 10016,
    }
}

Wir wollen es in diese Form bringen:

{
    "484272:name":                     "Ravagh",
    "484272:type":                     "Persian",
    "484272:address:street:line1":     "11 E 30th St",
    "484272:address:street:line2":     "APT 1",
    "484272:address:city":             "New York",
    "484272:address:state":            "NY",
    "484272:address:zip":              "10016",
}

Dies ist, wassetflat_skeys() unten tut, mit der zusätzlichen Funktion, dass es.set() Operationen auf derRedisInstanz selbst ausführt, anstatt eine Kopie des Eingabewörterbuchs zurückzugeben:

 1 from collections.abc import MutableMapping
 2
 3 def setflat_skeys(
 4     r: redis.Redis,
 5     obj: dict,
 6     prefix: str,
 7     delim: str = ":",
 8     *,
 9     _autopfix=""
10 ) -> None:
11     """Flatten `obj` and set resulting field-value pairs into `r`.
12
13     Calls `.set()` to write to Redis instance inplace and returns None.
14
15     `prefix` is an optional str that prefixes all keys.
16     `delim` is the delimiter that separates the joined, flattened keys.
17     `_autopfix` is used in recursive calls to created de-nested keys.
18
19     The deepest-nested keys must be str, bytes, float, or int.
20     Otherwise a TypeError is raised.
21     """
22     allowed_vtypes = (str, bytes, float, int)
23     for key, value in obj.items():
24         key = _autopfix + key
25         if isinstance(value, allowed_vtypes):
26             r.set(f"{prefix}{delim}{key}", value)
27         elif isinstance(value, MutableMapping):
28             setflat_skeys(
29                 r, value, prefix, delim, _autopfix=f"{key}{delim}"
30             )
31         else:
32             raise TypeError(f"Unsupported value type: {type(value)}")

Die Funktion durchläuft die Schlüssel-Wert-Paare vonobj und überprüft zunächst den Werttyp (Zeile 25), um festzustellen, ob die weitere Rekursion aufhören sollte, und legt das Schlüssel-Wert-Paar fest. Wenn der Wert andernfalls wiedict aussieht (Zeile 27), wird er in diese Zuordnung zurückgeführt und die zuvor angezeigten Schlüssel als Schlüsselpräfix hinzugefügt (Zeile 28).

Lassen Sie es uns bei der Arbeit sehen:

>>>

>>> r.flushdb()  # Flush database: clear old entries
>>> setflat_skeys(r, restaurant_484272, 484272)

>>> for key in sorted(r.keys("484272*")):  # Filter to this pattern
...     print(f"{repr(key):35}{repr(r.get(key)):15}")
...
b'484272:address:city'             b'New York'
b'484272:address:state'            b'NY'
b'484272:address:street:line1'     b'11 E 30th St'
b'484272:address:street:line2'     b'APT 1'
b'484272:address:zip'              b'10016'
b'484272:name'                     b'Ravagh'
b'484272:type'                     b'Persian'

>>> r.get("484272:address:street:line1")
b'11 E 30th St'

Die letzte Schleife oben verwendetr.keys("484272*"), wobei"484272*" als Muster interpretiert wird und mit allen Schlüsseln in der Datenbank übereinstimmt, die mit"484272" beginnen.

Beachten Sie auch, wiesetflat_skeys() nur.set() anstelle von.hset() aufruft, da wir mit einfachen Feldwertwertpaaren vonstring:stringarbeiten und jedem Feld der ID-Schlüssel 484272 vorangestellt wird Zeichenfolge.

Verschlüsselung

Ein weiterer Trick, der Ihnen hilft, nachts gut zu schlafen, besteht darin, eine symmetrische Verschlüsselung hinzuzufügen, bevor Sie etwas an einen Redis-Server senden. Betrachten Sie dies als Add-On zu der Sicherheit, die Sie sicherstellen sollten, indem Sie die richtigen Werte inRedis configurationfestlegen. Im folgenden Beispiel wird das Paketcryptographyverwendet:

$ python -m pip install cryptography

Stellen Sie sich zur Veranschaulichung vor, Sie hätten vertrauliche Karteninhaberdaten (CD), die Sie niemals auf einem Server im Klartext haben möchten, egal was passiert. Bevor Sie es in Redis zwischenspeichern, können Sie die Daten serialisieren und dann die serialisierte Zeichenfolge mitFernet verschlüsseln:

>>>

>>> import json
>>> from cryptography.fernet import Fernet

>>> cipher = Fernet(Fernet.generate_key())
>>> info = {
...     "cardnum": 2211849528391929,
...     "exp": [2020, 9],
...     "cv2": 842,
... }

>>> r.set(
...     "user:1000",
...     cipher.encrypt(json.dumps(info).encode("utf-8"))
... )

>>> r.get("user:1000")
b'gAAAAABcg8-LfQw9TeFZ1eXbi'  # ... [truncated]

>>> cipher.decrypt(r.get("user:1000"))
b'{"cardnum": 2211849528391929, "exp": [2020, 9], "cv2": 842}'

>>> json.loads(cipher.decrypt(r.get("user:1000")))
{'cardnum': 2211849528391929, 'exp': [2020, 9], 'cv2': 842}

Dainfo einen Wert enthält, derlist ist, müssen Sie diesen in eine von Redis akzeptierte Zeichenfolge serialisieren. (Sie können hierfürjson,yaml oder eine andere Serialisierung verwenden.) Als Nächstes verschlüsseln und entschlüsseln Sie diese Zeichenfolge mit dem Objektcipher. Sie müssen die entschlüsselten Bytes mitjson.loads() deserialisieren, damit Sie das Ergebnis wieder in den Typ Ihrer anfänglichen Eingabe, adict, zurückführen können.

Note:Fernet verwendet die AES 128-Verschlüsselung im CBC-Modus. Incryptography docs finden Sie ein Beispiel für die Verwendung von AES 256. Was auch immer Sie tun, verwenden Siecryptography, nichtpycrypto (importiert alsCrypto), das nicht mehr aktiv verwaltet wird.

Wenn Sicherheit an erster Stelle steht, ist es nie eine schlechte Idee, Zeichenfolgen zu verschlüsseln, bevor sie über eine Netzwerkverbindung übertragen werden.

Kompression

Eine letzte schnelle Optimierung ist die Komprimierung. Wenn die Bandbreite ein Problem darstellt oder Sie kostenbewusst sind, können Sie beim Senden und Empfangen von Daten von Redis ein verlustfreies Komprimierungs- und Dekomprimierungsschema implementieren. Hier ist ein Beispiel mit dem Komprimierungsalgorithmus bzip2, der in diesem Extremfall die Anzahl der über die Verbindung gesendeten Bytes um den Faktor 2.000 reduziert:

>>>

 1 >>> import bz2
 2
 3 >>> blob = "i have a lot to talk about" * 10000
 4 >>> len(blob.encode("utf-8"))
 5 260000
 6
 7 >>> # Set the compressed string as value
 8 >>> r.set("msg:500", bz2.compress(blob.encode("utf-8")))
 9 >>> r.get("msg:500")
10 b'BZh91AY&SY\xdaM\x1eu\x01\x11o\x91\x80@\x002l\x87\'  # ... [truncated]
11 >>> len(r.get("msg:500"))
12 122
13 >>> 260_000 / 122  # Magnitude of savings
14 2131.1475409836066
15
16 >>> # Get and decompress the value, then confirm it's equal to the original
17 >>> rblob = bz2.decompress(r.get("msg:500")).decode("utf-8")
18 >>> rblob == blob
19 True

Die Art und Weise, wie Serialisierung, Verschlüsselung und Komprimierung hier zusammenhängen, besteht darin, dass sie alle clientseitig erfolgen. Sie führen eine Operation für das ursprüngliche Objekt auf der Clientseite aus, die Redis effizienter nutzt, sobald Sie die Zeichenfolge an den Server senden. Die inverse Operation wird dann auf der Clientseite erneut ausgeführt, wenn Sie anfordern, was auch immer Sie an den Server gesendet haben.

Verwenden von Hiredis

Es ist üblich, dass eine Clientbibliothek wieredis-py bei ihrer Erstellung einemprotocol folgt. In diesem Fall implementiertredis-pyREdis Serialization Protocol oder RESP.

Ein Teil der Erfüllung dieses Protokolls besteht darin, ein Python-Objekt in einen rohen Bytestring zu konvertieren, es an den Redis-Server zu senden und die Antwort zurück in ein verständliches Python-Objekt zu analysieren.

Beispielsweise würde die Zeichenfolgenantwort "OK" als"+OK " zurückkommen, während die ganzzahlige Antwort 1000 als":1000 " zurückkommen würde. Dies kann bei anderen Datentypen wieRESP arrays komplexer werden.

Aparser ist ein Werkzeug im Anforderungs- / Antwortzyklus, das diese rohe Antwort interpretiert und zu etwas verarbeitet, das für den Client erkennbar ist. redis-py wird mit der eigenen Parser-KlassePythonParser ausgeliefert, die das Parsen in reinem Python durchführt. (Siehe.read_response(), wenn Sie neugierig sind.)

Es gibt jedoch auch eine C-Bibliothek,Hiredis, die einen schnellen Parser enthält, der für einige Redis-Befehle wieLRANGE erhebliche Beschleunigungen bieten kann. Sie können sich Hiredis als optionalen Beschleuniger vorstellen, den es nicht schadet, in Nischenfällen dabei zu sein.

Alles, was Sie tun müssen, umredis-py für die Verwendung des Hiredis-Parsers zu aktivieren, ist, seine Python-Bindungen in derselben Umgebung wieredis-py zu installieren:

$ python -m pip install hiredis

Was Sie hier tatsächlich installieren, isthiredis-py, ein Python-Wrapper für einen Teil der C-Bibliothek vonhiredis.

Das Schöne ist, dass Siehiredis nicht wirklich selbst aufrufen müssen. Nurpip installist es, und dies lässtredis-py sehen, dass es verfügbar ist, und verwendet seineHiredisParser anstelle vonPythonParser.

Intern versuchtredis-py,hiredis zu importieren und eineHiredisParser-Klasse zu verwenden, um sie abzugleichen, greift jedoch stattdessen aufPythonParser zurück, was in einigen Fällen langsamer sein kann ::

# redis/utils.py
try:
    import hiredis
    HIREDIS_AVAILABLE = True
except ImportError:
    HIREDIS_AVAILABLE = False


# redis/connection.py
if HIREDIS_AVAILABLE:
    DefaultParser = HiredisParser
else:
    DefaultParser = PythonParser

Verwenden von Enterprise Redis-Anwendungen

Während Redis selbst Open Source und kostenlos ist, sind mehrere verwaltete Dienste entstanden, die einen Datenspeicher mit Redis als Kern und einige zusätzliche Funktionen bieten, die auf dem Open Source Redis-Server aufbauen:

Die Entwürfe der beiden haben einige Gemeinsamkeiten. Normalerweise geben Sie einen benutzerdefinierten Namen für Ihren Cache an, der als Teil eines DNS-Namens eingebettet ist, z. B.demo.abcdef.xz.0009.use1.cache.amazonaws.com (AWS) oderdemo.redis.cache.windows.net (Azure).

Nach dem Einrichten finden Sie hier einige kurze Tipps zum Herstellen einer Verbindung.

Über die Befehlszeile ist es weitgehend dasselbe wie in unseren früheren Beispielen, aber Sie müssen einen Host mit dem Flaghangeben, anstatt den Standard-Localhost zu verwenden. Führen Sie fürAmazon AWS Folgendes aus Ihrer Instanz-Shell aus:

$ export REDIS_ENDPOINT="demo.abcdef.xz.0009.use1.cache.amazonaws.com"
$ redis-cli -h $REDIS_ENDPOINT

FürMicrosoft Azure können Sie einen ähnlichen Aufruf verwenden. Azure Cache für Redisuses SSL (Port 6380) standardmäßig anstelle von Port 6379, wodurch eine verschlüsselte Kommunikation zu und von Redis ermöglicht wird, was von TCP nicht gesagt werden kann. Sie müssen lediglich einen nicht standardmäßigen Port und einen Zugriffsschlüssel angeben:

$ export REDIS_ENDPOINT="demo.redis.cache.windows.net"
$ redis-cli -h $REDIS_ENDPOINT -p 6380 -a 

Das Flag-h gibt einen Host an, der, wie Sie gesehen haben, standardmäßig127.0.0.1 (localhost) ist.

Wenn Sieredis-py in Python verwenden, ist es immer eine gute Idee, vertrauliche Variablen aus Python-Skripten herauszuhalten und vorsichtig zu sein, welche Lese- und Schreibberechtigungen Sie diesen Dateien gewähren. Die Python-Version würde folgendermaßen aussehen:

>>>

>>> import os
>>> import redis

>>> # Specify a DNS endpoint instead of the default localhost
>>> os.environ["REDIS_ENDPOINT"]
'demo.abcdef.xz.0009.use1.cache.amazonaws.com'
>>> r = redis.Redis(host=os.environ["REDIS_ENDPOINT"])

Das ist alles dazu. Neben der Angabe eines anderenhost können Sie jetzt wie gewohnt befehlsbezogene Methoden wier.get() aufrufen.

Note: Wenn Sie nur die Kombination vonredis-py und einer AWS- oder Azure Redis-Instanz verwenden möchten, müssen Sie Redis nicht wirklich lokal auf Ihrem Computer installieren und erstellen, da Sie dies nicht tun. t benötigt entwederredis-cli oderredis-server.

Wenn Sie eine mittelgroße bis große Produktionsanwendung bereitstellen, bei der Redis eine Schlüsselrolle spielt, kann die Verwendung von AWS- oder Azure-Servicelösungen eine skalierbare, kostengünstige und sicherheitsbewusste Methode sein.

Einpacken

Damit ist unsere Wirbelsturm-Tour zum Zugriff auf Redis über Python abgeschlossen, einschließlich der Installation und Verwendung der mit einem Redis-Server verbundenen Redis REPL und der Verwendung vonredis-py in Beispielen aus der Praxis. Folgendes haben Sie gelernt:

  • Mitredis-py können Sie (fast) alles tun, was Sie mit der Redis-CLI über eine intuitive Python-API tun können.

  • Wenn Sie Themen wie Persistenz, Serialisierung, Verschlüsselung und Komprimierung beherrschen, können Sie Redis optimal nutzen.

  • Redis-Transaktionen und -Pipelines sind in komplexeren Situationen wesentliche Bestandteile der Bibliothek.

  • Mit Redis-Diensten auf Unternehmensebene können Sie Redis reibungslos in der Produktion einsetzen.

Redis verfügt über eine Reihe umfangreicher Funktionen, von denen einige hier nicht wirklich behandelt wurden, darunterserver-side Lua scripting,sharding undmaster-slave replication. Wenn Sie der Meinung sind, dass Redis in Ihrer Gasse liegt, sollten Sie die Entwicklungen verfolgen, während es einupdated protocol, RESP3 implementiert.