Redis ist sowohl ein Key-Value-Store als auch ein Message-Broker. In unserer Infrastruktur ist automatisch mit dem Erstellen eines Docker-Containers eine von hopper und natürlich aus dem Docker-Container erreichbare, individuelle Redis-Instanz verfügbar.
Mit Redis können Sie verteilte, atomare Operationen durchführen: Sie können einen atomaren Zähler verwalten, Sie können Listen, Mengen, Hashes atomar verwalten und - vor allem aufbauend auf Listen - können Sie Stacks und Queues nutzen.
Loggen Sie sich auf hopper ein, geben Sie die einzelnen Kommandos ein und verfolgen Sie genau, was geschieht:
$ redis-cli set counter 0
0
$ redis-cli get counter
"0"
$ redis-cli incr counter
(integer) 1
$ redis-cli decr counter
(integer) 0
Das mag zunächst nicht aufregend erscheinen: besonders daran ist aber, dass über Prozess- und Rechnergrenzen hinweg diese Operationen garantiert atomar sind. Damit ist gemeint, dass von gleichzeitig auf unterschiedlichen Rechnern laufenden Prozessen garantiert jeweils der Zähler um eins erhöht oder verringert wird.
Redis lässt sich ebenfalls gut als Message-Broker einsetzen: Prozess können sich an Themen hängen und wann immer ein anderer Prozess an dieses Thema eine Nachricht schickt, wird sie an alle zuhörenden Prozess verteilt. Bei diesen Themen werden keine Nachrichten gesammelt - wer nicht zuhört, erfährt auch nichts.
Sie benötigen zum Ausprobieren mindestens zwei Terminals. Loggen Sie sich mit einem Terminal auf hopper ein und mit dem anderen gehen Sie darüber hinaus in Ihren Docker-Container.
$ redis-cli --csv subscribe foo-topic
Reading messages... (press Ctrl-C to quit)
"subscribe","foo-topic",1
In dem zweiten Terminal senden Sie an dieses Thema eine Nachricht mit:
$ redis-cli publish foo-topic bar-message
(integer) 1
Mit Redis lassen sich Listen mit den Operation lpush, rpush, lpop, rpop, lrange, lrem verwalten.
Beobachten Sie eine Liste mylist am besten in einem Terminal mit einer kleinen while-Schleife, indem Sie sie mit lrange von Element 0 bis zum letzten (-1) ausgeben:
$ while true; do redis-cli lrange mylist 0 -1; sleep 0.2; clear; done
... und probieren Sie in einem anderen Terminal die Operationen durch:
$ redis-cli rpush mylist eins
$ redis-cli rpush mylist zwei
$ redis-cli rpush mylist Null
$ redis-cli lset mylist 0 null
Stellen Sie sich die Liste als von links nach rechts vor: lpush fügt links (Head), rpush fügt rechts an (Tail).
Bei einigen Operationen gibt es noch eine Variante mit vorangestelltem B: blpop, brpop. B steht für blocking und sorgt dafür, dass, falls die Operation gerade nicht möglich ist, vorerst blockiert.
redis-cli --csv blpop mylist 0
Ist die Liste leer, blockiert die Operation, bis ein Wert verfügbar ist - mit einem unbeschränkten Timeout (0).
In PHP gibt es das - bei uns aktivierte - Redis-Modul. Sie verbinden sich mit Redis, indem Sie mit new ein neues Redis-Objekt erzeugen und dann mit den Methoden pconnect, auth, set, publish etc. über die Verbindung auf den Redis-Server zugreifen.
//in redis.inc.php halten Sie für $redishost,
//$redisport und $redispassword
//die passenden Werte bereit.
//Auf hopper/shannon: 'turing', $(hbv_dockerport redis)
//In dem docker-Container: 'localhost', 6379
include "private/redis.inc.php";
$start = microtime(TRUE);
$redis = new Redis();
$redis->pconnect($redishost, $redisport);
$redis->auth($redispassword);
$redis->publish("kanal","nachricht ".$start);
$redis->close();
$ende = microtime(TRUE);
echo number_format($ende-$start,5)."sec\n";
Auch lauschen lässt sich auf einem Channel natürlich mit php - dann aber auch nur das. Und es ergibt keinen Sinn, das einfach in einem php-Http Request zu tun - dann bleibt der lauschende Prozess dort hängen. Für die Kommandozeile aber ist das durchaus hilfreich:
include "private/redis.inc.php";
ob_implicit_flush(TRUE);
ini_set('default_socket_timeout', -1);
$redis = new Redis();
$redis->connect($redishost, $redisport);
$redis->auth($redispassword);
function callback($redis, $channel, $msg) {
print "$msg\n";
}
$redis->subscribe(['all'], 'callback');
// Alternative 1
// $redis->subscribe(['all'], function ($redis,$channel,$msg){
// print "$msg\n";
// };
// Alternative 2
// $callback=function ($redis,$channel,$msg){
// print "$msg\n";
// };
// $redis->subscribe(['all'], $callback);
// Alternative 3 (Hipster-Style)
// $redis->subscribe(['all'], fn($r,$c,$m)=> print "$m\n");
Die Struktur ist ungewöhnlich für php: Wir deklarieren eine Funktion namens callback, die wir als zweites Argument (Achtung: als Zeichenkette!) an subscribe übergeben. Das erste Argument ist ein Array von Strings, womit die Channels angegeben werden, auf denen gelauscht wird. Für jede Nachricht, die auf einem der Channels gepublished wird, wird dann einmal die Funktion aufgerufen. Alternativ könnte man die Funktion auch als anonyme Funktion direkt in den subscribe-Aufruf hineinschreiben. Oder Alternative 2: Die anonyme Funktion einer Variablen zuweisen und dann als Argument übergeben. Und falls es eine Arrow-Funktion sein soll, dann geht auch das.
ob_implicit_autoflush bestimmt, dass StdOut am Zeilenende automatisch geflushed wird. ini_set('default_socket_timeout', -1); sorgt dafür, dass Sockets nicht bei Inaktivität geschlossen werden.