Sonntag, 2. September 2012

Redis und Python

Redis ist ein sehr schnelles Key-Value Store, zu Deutsch: Schlüssel-Werte Datenbank. Eine Besonderheit von Redis ist zusätzlich, dass es als Werte nicht nur einfache Strings kennt, sondern fünf verschiedenen Datentypen. Da die offizielle Dokumentation von Redis ganz hervorragend ist und auch in der September-Ausgabe von FreiesMagazin ein ausführlicher Artikel zu Redis vorhanden ist, wird an dieser Stelle auf eine weitere Beschreibung der Datenbank verzichtet und "nur" die Python-Anbindung näher beschrieben. In der Python-Welt erfreut sich Redis übrigens scheinbar recht großer Beliebtheit. Wird auf der Webseite des Python Package Index "redis" als Suchbegriff eingegeben, wird eine ziemliche lange Ergebnisliste angezeigt. So gibt es z.B. Anbindungen für Django, Flask und Celery an Redis sowie eine Vielzahl anderer Module, die Python auf die ein oder andere Weise mit Redis kombinieren.

Installation

Das empfohlene Python-Modul heißt einfach nur "redis" und lässt sich ganz einfach via pip oder easy_install installieren. Die die Ergebnisse der Abfragen des Redis-Servers werden von einem in Python implementierten Parser verarbeitet - was ohne Probleme funktioniert, aber nicht ultimativ schnell ist. Wer die volle Geschwindigkeit braucht, der installiert noch das Modul "hiredis" (via pip oder easy_install). So wird der in C geschriebene Parser inklusive Python-Bindings installiert. Die Entwickler nennen eine Geschwindigkeitssteigerung um das 10-fache, im Vergleich zum Python-Parser.

Mit dem Server verbinden

Das Verbinden mit einem laufenden Server ist einfach:

>>> r = redis.StrictRedis(host='localhost',port=6379,db=0)

Im Gegensatz zur z.B. Kommandozeile von Redis muss hier eine Datenbanknummer angegeben werden. Das Python-Modul erlaubt es übrigens nicht, die Datenbank im laufenden Betrieb zu wechseln (was z.B. via Redis Kommandozeile problemlos geht), weil die Datenbankverbindung dann nicht mehr "thread-safe" wäre. Wer also mehrere Datenbanken für sein Programm benötigt, der muss mehrere Instanzen von redis.StrictRedis(...) anlegen.

Connection Pooling

Im Hintergrund legt redis.StrictRedis(...) für jede neu Instanz einen neuen, eigenen Connection Pool an. Was je nach Anwendung aber gar nicht nötig ist. Aber es ist auch möglich, dass sich mehrere Instanzen einen Connection Pool teilen:

>>> pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
>>> r = redis.Redis(connection_pool=pool)

Auch hier gilt: Ein Wechsel der Datenbank innerhalb eines Connection Pools ist nicht möglich bzw. - andersherum - ein Connection Pool kann immer nur für eine Datenbank gelten.

Datentypen

Natürlich unterstützt das Python-Modul allen Datentypen voll. Dabei werden so gut wie alle Befehle von Redis 1:1 in Python umgesetzt, so dass die Redis Befehlsreferenz bei Fragen zu Rate gezogen werden kann. Im folgenden ein paar einfache Beispiele zur Nutzung der Datentypen aus Python heraus: Strings sind der einfachste Datentyp:
>>> r.set('foo','bar')
True
>>> r.get('foo')
'bar'

Und ein paar Beispiele zu Listen in Redis:
>>> r.lpush('liste','foo')
1L
>>> r.lpush('liste','bar')
2L
>>> r.lrange('liste',0,-1)
['bar', 'foo']

Hashs lassen sich mit den bekannten Befehlen generieren:
>>> r.hset('hash','foo','bar')
1L
>>> r.hset('hash','spam','egg')
1L

Werden alle Werte innerhalb eines Hashs abgefragt, dann wird ein Python Dictionary zurück geliefert:
>>> r.hgetall('hash')
{'foo': 'bar', 'spam': 'egg'}

bei Einzelwerten logischerweise ein String:
>>> r.hget('hash','foo')
'bar'

Hashs lassen sich auch direkt aus einem Python Dictionary generieren:
>>> my_dict = {'Rhythmbox':'Audio','Totem':'Video'}
>>> r.hmset('progs',my_dict)
>>> r.hmget('progs','Rhythmbox','Totem')
['Audio', 'Video']

Anlegen eines Redis-Sets aus Python heraus:
>>> r.sadd('set','foo')
1
>>> r.sadd('set','bar')
1

Wird versucht, einen bereits vorhanden Wert zu einem Redis-Set hinzuzufügen, dann liefert die Datenbank einfach "0" (für "False") zurück:
>>> r.sadd('set','bar')
0
Werden alle Werte aus einem Redis-Set abgefragt, wird als Ergebnis ein Python-Set zurück geliefert:
>>> r.smembers('set')
set(['foo', 'bar'])
Ordered Sets werden wie folgt in Redis via Python angelegt:
>>> r.zadd('orderset',10,'bar')
1
>>> r.zadd('orderset',5,'foo')
1
Oder alternativ auch:
>>> r.zadd('orderset',spam=1.0,egg=2.0)
2
Die als Ergebnis einer Abfrage eines Ordered Sets wird entweder eine Liste von Strings oder eine Liste von Tuplen geliefert. Je nach dem, ob die Gewichtung der Werte mit abgefragt wird oder nicht:

>>> r.zrange('orderset',0,-1,withscores=True)
[('spam', 1.0), ('egg', 2.0), ('foo', 5.0), ('bar', 10.0)]
>>> r.zrange('orderset',0,-1,withscores=True,desc=True)
[('bar', 10.0), ('foo', 5.0), ('egg', 2.0), ('spam', 1.0)]

Transaktionen

Das Python Modul unterstützt auch das Zusammenfassen von mehreren Befehlen zu einer atomaren Transaktion. Allerdings setzt das Modul nicht die Redis-Befehle multi und exec um, sondern geht den Weg über Pipelines:

>>> pipe = r.pipeline()
>>> pipe.set('counter',1)
<redis.client.StrictPipeline object at 0x18cfb10>
>>> pipe.set('wort','irgendwas')
<redis.client.StrictPipeline object at 0x18cfb10>
>>> pipe.incr('counter')
<redis.client.StrictPipeline object at 0x18cfb10>
>>> pipe.execute()
[True, True, 2]

Alle Befehle werden also gepuffert und erst nach dem Aufruf von pipe.execute() im Block atomar ausgeführt. Das Ergebnis der Ausführung der einzelnen Befehle wird als Liste zurück geliefert.

Weitere Dokumentation

Hier im Blogeintrag wurden zwar die meisten, aber nicht alle Möglichkeiten des Python Redis-Moduls gezeigt. Diese sind aber natürlich in der Dokumentation aufgeführt.

Redis und ORM

Zu guter Letzt sei noch erwähnt, dass es auch auch eine Reihe von Object Relational Mapperns (ORM) für Python und Redis gibt. Diese tragen aber teilweise noch eine relativ niedrige Versionsnummer. Weiter entwickelt - wenn auch noch im Beta-Stadium - scheint Redisco zu sein. Andere ORMs bringt eine Suche im Python Package Index hervor. 

Alle Beispiele hier im Blogartikel sind unter Ubuntu 12.04, Python 2.7, redis-py 2.6.2 und dem Redis Server 2.4.16 getestet.

Keine Kommentare:

Kommentar posten