Einführung in Lettuce - der Java Redis Client

Einführung in Salat - der Java Redis Client

1. Überblick

Dieser Artikel ist eine Einführung inLettuce, einenRedis Java-Client.

Redis ist ein speicherinterner Schlüsselwertspeicher, der als Datenbank, Cache oder Nachrichtenbroker verwendet werden kann. Daten werden mitcommands hinzugefügt, abgefragt, geändert und gelöscht, die mit Schlüsseln in der speicherinternen Datenstruktur von Redis arbeiten.

Lettuce unterstützt sowohl die synchrone als auch die asynchrone Kommunikation der gesamten Redis-API, einschließlich ihrer Datenstrukturen, Pub / Sub-Messaging und hochverfügbaren Serververbindungen.

2. Warum Salat?

Wir haben Jedisin one of the previous posts.behandelt. Was unterscheidet Kopfsalat?

Der wichtigste Unterschied ist die asynchrone Unterstützung über dieCompletionStage-Schnittstelle von Java 8 und die Unterstützung für reaktive Streams. Wie wir weiter unten sehen werden, bietet Lettuce eine natürliche Schnittstelle zum Erstellen asynchroner Anforderungen vom Redis-Datenbankserver und zum Erstellen von Streams.

Es verwendet auch Netty für die Kommunikation mit dem Server. Dies führt zu einer "schwereren" API, eignet sich aber auch besser für die gemeinsame Nutzung einer Verbindung mit mehr als einem Thread.

3. Konfiguration

3.1. Abhängigkeit

Beginnen wir damit, die einzige Abhängigkeit zu deklarieren, die wir inpom.xmlbenötigen:


    io.lettuce
    lettuce-core
    5.0.1.RELEASE

Die neueste Version der Bibliothek kann aufGithub repository oder aufMaven Central. überprüft werden

3.2. Redis Installation

Wir müssen mindestens eine Instanz von Redis installieren und ausführen, zwei, wenn wir den Clustering- oder Sentinel-Modus testen möchten (obwohl für den Sentinel-Modus drei Server erforderlich sind, um ordnungsgemäß zu funktionieren). Für diesen Artikel verwenden wir 4.0.x - the neueste stabile Version in diesem Moment.

Weitere Informationen zu den ersten Schritten mit Redis finden Sie unterhere, einschließlich Downloads für Linux und MacOS.

Redis unterstützt Windows offiziell nicht, aber es gibt einen Port des Servershere. Wir können Redis auch inDocker ausführen. Dies ist eine bessere Alternative für Windows 10 und eine schnelle Möglichkeit, den Betrieb aufzunehmen.

4. Verbindungen

4.1. Verbindung zu einem Server herstellen

Die Verbindung zu Redis besteht aus vier Schritten:

  1. Erstellen eines Redis-URI

  2. Verwenden des URI zum Herstellen einer Verbindung mitRedisClient

  3. Öffnen einer Redis-Verbindung

  4. Generieren einer Menge vonRedisCommands

Sehen wir uns die Implementierung an:

RedisClient redisClient = RedisClient
  .create("redis://[email protected]:6379/");
StatefulRedisConnection connection
 = redisClient.connect();

AStatefulRedisConnection ist, wie es sich anhört; Eine thread-sichere Verbindung zu einem Redis-Server, die die Verbindung zum Server aufrechterhält und bei Bedarf erneut eine Verbindung herstellt. Sobald wir eine Verbindung haben, können wir sie verwenden, um Redis-Befehle entweder synchron oder asynchron auszuführen.

RedisClient verwendet erhebliche Systemressourcen, da es Netty-Ressourcen für die Kommunikation mit dem Redis-Server enthält. Anwendungen, die mehrere Verbindungen erfordern, sollten ein einzelnesRedisClient. verwenden

4.2. Redis URIs

Wir erstellen einRedisClient, indem wir einen URI an die statische Factory-Methode übergeben.

Salat nutzt eine benutzerdefinierte Syntax für Redis-URIs. Dies ist das Schema:

redis :// [[email protected]] host [: port] [/ database]
  [? [timeout=timeout[d|h|m|s|ms|us|ns]]
  [&_database=database_]]

Es gibt vier URI-Schemata:

  • redis - ein eigenständiger Redis-Server

  • rediss - Ein eigenständiger Redis-Server über eine SSL-Verbindung

  • redis-socket - Ein eigenständiger Redis-Server über einen Unix-Domain-Socket

  • redis-sentinel - ein Redis Sentinel-Server

Die Redis-Datenbankinstanz kann als Teil des URL-Pfads oder als zusätzlicher Parameter angegeben werden. Wenn beide angegeben werden, hat der Parameter eine höhere Priorität.

Im obigen Beispiel verwenden wir eineString-Darstellung. Kopfsalat hat auch eineRedisURI-Klasse zum Aufbau von Verbindungen. Es bietet das Muster vonBuilder:

RedisURI.Builder
  .redis("localhost", 6379).auth("password")
  .database(1).build();

Und ein Konstruktor:

new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);

4.3. Synchrone Befehle

Ähnlich wie Jedis bietet Lettuce einen vollständigen Redis-Befehlssatz in Form von Methoden.

Lettuce implementiert jedoch sowohl synchrone als auch asynchrone Versionen. Wir werden uns die synchrone Version kurz ansehen und dann die asynchrone Implementierung für den Rest des Tutorials verwenden.

Nachdem wir eine Verbindung erstellt haben, verwenden wir sie, um einen Befehlssatz zu erstellen:

RedisCommands syncCommands = connection.sync();

Jetzt haben wir eine intuitive Oberfläche für die Kommunikation mit Redis.

Wir könnenString values: setzen und erhalten

syncCommands.set("key", "Hello, Redis!");

String value = syncommands.get(“key”);

Wir können mit Hashes arbeiten:

syncCommands.hset("recordName", "FirstName", "John");
syncCommands.hset("recordName", "LastName", "Smith");
Map record = syncCommands.hgetall("recordName");

Wir werden später in diesem Artikel auf weitere Redis eingehen.

Die synchrone Salat-API verwendet die asynchrone API. Die Sperrung erfolgt für uns auf Befehlsebene. This means that more than one client can share a synchronous connection.

4.4. Asynchrone Befehle

Werfen wir einen Blick auf die asynchronen Befehle:

RedisAsyncCommands asyncCommands = connection.async();

Wir rufen eine Menge vonRedisAsyncCommands aus der Verbindung ab, ähnlich wie wir die synchrone Menge abgerufen haben. Diese Befehle geben einRedisFuture zurück (was intern einCompletableFuture ist):

RedisFuture result = asyncCommands.get("key");

Eine Anleitung zum Arbeiten mitCompletableFuture finden Sie unterhere.

4.5. Reaktive API

Lassen Sie uns abschließend sehen, wie Sie mit der nicht blockierenden reaktiven API arbeiten:

RedisStringReactiveCommands reactiveCommands = connection.reactive();

Diese Befehle geben Ergebnisse zurück, die inMono oderFlux vonProject Reactor. eingeschlossen sind

Eine Anleitung zur Arbeit mit Project Reactor finden Sie inhere.

5. Redis Datenstrukturen

Wir haben uns oben kurz die Zeichenfolgen und Hashes angesehen. Schauen wir uns an, wie Lettuce die restlichen Datenstrukturen von Redis implementiert. Wie zu erwarten, verfügt jeder Redis-Befehl über eine Methode mit ähnlichem Namen.

5.1. Listen

Lists are lists of Strings with the order of insertion preserved. Werte werden an beiden Enden eingefügt oder abgerufen:

asyncCommands.lpush("tasks", "firstTask");
asyncCommands.lpush("tasks", "secondTask");
RedisFuture redisFuture = asyncCommands.rpop("tasks");

String nextTask = redisFuture.get();

In diesem Beispiel entsprichtnextTaskfirstTask“. Lpush verschiebt Werte an den Kopf der Liste, undrpop fügt Werte am Ende der Liste ein.

Wir können auch Elemente vom anderen Ende einfügen:

asyncCommands.del("tasks");
asyncCommands.lpush("tasks", "firstTask");
asyncCommands.lpush("tasks", "secondTask");
redisFuture = asyncCommands.lpop("tasks");

String nextTask = redisFuture.get();

Wir beginnen das zweite Beispiel, indem wir die Liste mitdel entfernen. Dann fügen wir dieselben Werte erneut ein, verwenden jedochlpop, um die Werte aus dem Kopf der Liste zu entfernen, sodassnextTask den Text „secondTask“ enthält.

5.2. Sets

Redis-Sets sind ungeordnete Sammlungen vonStrings ähnlich wie JavaSets; Es gibt keine doppelten Elemente:

asyncCommands.sadd("pets", "dog");
asyncCommands.sadd("pets", "cat");
asyncCommands.sadd("pets", "cat");

RedisFuture> pets = asyncCommands.smembers("nicknames");
RedisFuture exists = asyncCommands.sismember("pets", "dog");

Wenn wir den Redis-Satz alsSet abrufen, beträgt die Größe zwei, da das Duplikat“cat” ignoriert wurde. Wenn wir Redis nach der Existenz von“dog” mitsismember, abfragen, lautet die Antworttrue.

5.3. Hashes

Wir haben uns kurz ein Beispiel für Hashes angesehen. Sie sind eine kurze Erklärung wert.

Redis Hashes are records with String fields and values. Jeder Datensatz hat auch einen Schlüssel im Primärindex:

asyncCommands.hset("recordName", "FirstName", "John");
asyncCommands.hset("recordName", "LastName", "Smith");

RedisFuture lastName
  = syncCommands.hget("recordName", "LastName");
RedisFuture> record
  = syncCommands.hgetall("recordName");

Wir verwendenhset, um dem Hash Felder hinzuzufügen, wobei wir den Namen des Hash, den Namen des Feldes und einen Wert übergeben.

Dann rufen wir einen einzelnen Wert mithget, dem Namen des Datensatzes und dem Feld ab. Schließlich rufen wir den gesamten Datensatz als Hash mithgetall. ab

5.4. Sortierte Sets

Sorted Sets contains values and a rank, by which they are sorted. Der Rang ist ein 64-Bit-Gleitkommawert.

Elemente werden mit einem Rang hinzugefügt und in einem Bereich abgerufen:

asyncCommands.zadd("sortedset", 1, "one");
asyncCommands.zadd("sortedset", 4, "zero");
asyncCommands.zadd("sortedset", 2, "two");

RedisFuture> valuesForward = asyncCommands.zrange(key, 0, 3);
RedisFuture> valuesReverse = asyncCommands.zrevrange(key, 0, 3);

Das zweite Argument fürzadd ist ein Rang. Wir rufen einen Bereich nach Rang mitzrange für aufsteigende Reihenfolge undzrevrange für absteigend ab.

Wir haben "zero" mit einem Rang von 4 hinzugefügt, sodass es am Ende vonvaluesForward und am Anfang vonvaluesReverse. erscheint

6. Transaktionen

Transaktionen ermöglichen die Ausführung einer Reihe von Befehlen in einem einzelnen atomaren Schritt. Diese Befehle werden garantiert in der richtigen Reihenfolge und ausschließlich ausgeführt. Befehle eines anderen Benutzers werden erst ausgeführt, wenn die Transaktion abgeschlossen ist.

Entweder werden alle Befehle ausgeführt oder keiner von ihnen. Redis will not perform a rollback if one of them fails. Nach dem Aufruf vonexec() werden alle Befehle in der angegebenen Reihenfolge ausgeführt.

Schauen wir uns ein Beispiel an:

asyncCommands.multi();

RedisFuture result1 = asyncCommands.set("key1", "value1");
RedisFuture result2 = asyncCommands.set("key2", "value2");
RedisFuture result3 = asyncCommands.set("key3", "value3");

RedisFuture execResult = asyncCommands.exec();

TransactionResult transactionResult = execResult.get();

String firstResult = transactionResult.get(0);
String secondResult = transactionResult.get(0);
String thirdResult = transactionResult.get(0);

Der Aufruf vonmulti startet die Transaktion. Wenn eine Transaktion gestartet wird, werden die nachfolgenden Befehle erst ausgeführt, wennexec() aufgerufen wird.

Im synchronen Modus geben die Befehlenull. zurück. Im asynchronen Modus geben die BefehleRedisFuture zurück. Exec gibtTransactionResult zurück, das eine Liste von Antworten enthält.

Da dieRedisFutures auch ihre Ergebnisse erhalten, erhalten asynchrone API-Clients das Transaktionsergebnis an zwei Stellen.

7. Batching

Unter normalen Bedingungen führt Lettuce Befehle aus, sobald sie von einem API-Client aufgerufen werden.

Dies ist der Wunsch der meisten normalen Anwendungen, insbesondere wenn sie darauf angewiesen sind, Befehlsergebnisse seriell zu empfangen.

Dieses Verhalten ist jedoch nicht effizient, wenn Anwendungen nicht sofort Ergebnisse benötigen oder wenn große Datenmengen in großen Mengen hochgeladen werden.

Asynchrone Anwendungen können dieses Verhalten außer Kraft setzen:

commands.setAutoFlushCommands(false);

List> futures = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
    futures.add(commands.set("key-" + i, "value-" + i);
}
commands.flushCommands();

boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS,
  futures.toArray(new RedisFuture[0]));

Wenn setAutoFlushCommands auffalse gesetzt ist, muss die AnwendungflushCommands manuell aufrufen. In diesem Beispiel haben wir den Befehl mehrerersetin die Warteschlange gestellt und dann den Kanal geleert. AwaitAll wartet, bis alleRedisFutures abgeschlossen sind.

Dieser Status wird pro Verbindung festgelegt und wirkt sich auf alle Threads aus, die die Verbindung verwenden. Diese Funktion gilt nicht für synchrone Befehle.

8. Publish/Subscribe

Redis bietet ein einfaches Publish / Subscribe-Messaging-System. Teilnehmer verbrauchen Nachrichten von Kanälen mit dem Befehlsubscribe. Nachrichten werden nicht beibehalten. Sie werden nur an Benutzer gesendet, wenn sie einen Kanal abonniert haben.

Redis verwendet das Pub / Sub-System für Benachrichtigungen über das Redis-Dataset, sodass Clients Ereignisse über das Festlegen, Löschen, Ablaufen von Schlüsseln usw. empfangen können.

Weitere Informationen finden Sie in der Dokumentationhere.

8.1. Teilnehmer

ARedisPubSubListener empfängt Pub / Sub-Nachrichten. Diese Schnittstelle definiert mehrere Methoden, aber wir zeigen hier nur die Methode zum Empfangen von Nachrichten:

public class Listener implements RedisPubSubListener {

    @Override
    public void message(String channel, String message) {
        log.debug("Got {} on channel {}",  message, channel);
        message = new String(s2);
    }
}

Wir verwendenRedisClient, um einen Pub / Sub-Kanal zu verbinden und den Listener zu installieren:

StatefulRedisPubSubConnection connection
 = client.connectPubSub();
connection.addListener(new Listener())

RedisPubSubAsyncCommands async
 = connection.async();
async.subscribe("channel");

Wenn ein Listener installiert ist, rufen wir eine Reihe vonRedisPubSubAsyncCommands ab und abonnieren einen Kanal.

8.2. Verleger

Zum Veröffentlichen muss lediglich ein Pub / Sub-Kanal verbunden und die folgenden Befehle abgerufen werden:

StatefulRedisPubSubConnection connection
  = client.connectPubSub();

RedisPubSubAsyncCommands async
  = connection.async();
async.publish("channel", "Hello, Redis!");

Für die Veröffentlichung sind ein Kanal und eine Nachricht erforderlich.

8.3. Reaktive Abonnements

Lettuce bietet auch eine reaktive Schnittstelle zum Abonnieren von Pub / Sub-Nachrichten:

StatefulRedisPubSubConnection connection = client
  .connectPubSub();

RedisPubSubAsyncCommands reactive = connection
  .reactive();

reactive.observeChannels().subscribe(message -> {
    log.debug("Got {} on channel {}",  message, channel);
    message = new String(s2);
});
reactive.subscribe("channel").subscribe();

Die vonobserveChannels zurückgegebenenFlux empfangen Nachrichten für alle Kanäle. Da es sich jedoch um einen Stream handelt, ist das Filtern einfach.

9. Hohe Verfügbarkeit

Redis bietet verschiedene Optionen für hohe Verfügbarkeit und Skalierbarkeit. Um ein umfassendes Verständnis zu erlangen, müssen Sie die Redis-Serverkonfigurationen kennen. Wir werden jedoch einen kurzen Überblick darüber geben, wie Lettuce diese unterstützt.

9.1. Master/Slave

Redis-Server replizieren sich in einer Master / Slave-Konfiguration. Der Master-Server sendet dem Slave einen Befehlsstrom, der den Master-Cache an den Slave repliziert. Redis doesn’t support bi-directional replication, so slaves are read-only.

Salat kann eine Verbindung zu Master / Slave-Systemen herstellen, diese nach der Topologie abfragen und dann Slaves für Lesevorgänge auswählen, wodurch der Durchsatz verbessert werden kann:

RedisClient redisClient = RedisClient.create();

StatefulRedisMasterSlaveConnection connection
 = MasterSlave.connect(redisClient,
   new Utf8StringCodec(), RedisURI.create("redis://localhost"));

connection.setReadFrom(ReadFrom.SLAVE);

9.2. Wächter

Redis Sentinel überwacht Master- und Slave-Instanzen und orchestriert Failovers zu Slaves im Falle eines Master-Failovers.

Kopfsalat kann eine Verbindung zum Sentinel herstellen, die Adresse des aktuellen Masters ermitteln und dann eine Verbindung zum Sentinel herstellen.

Dazu erstellen wir verschiedeneRedisURI und verbinden unsereRedisClient damit:

RedisURI redisUri = RedisURI.Builder
  .sentinel("sentinelhost1", "clustername")
  .withSentinel("sentinelhost2").build();
RedisClient client = new RedisClient(redisUri);

RedisConnection connection = client.connect();

Wir haben den URI mit dem Hostnamen (oder der Adresse) des ersten Sentinels und einem Clusternamen erstellt, gefolgt von einer zweiten Sentinel-Adresse. Wenn wir eine Verbindung zum Sentinel herstellen, fragt Lettuce ihn nach der Topologie ab und gibt eine Verbindung zum aktuellen Masterserver für uns zurück.

Die vollständige Dokumentation ist verfügbarhere.

9.3. Cluster

Redis Cluster verwendet eine verteilte Konfiguration, um hohe Verfügbarkeit und hohen Durchsatz zu gewährleisten.

Cluster teilen Shard-Schlüssel auf bis zu 1000 Knoten, daher sind Transaktionen in einem Cluster nicht verfügbar:

RedisURI redisUri = RedisURI.Builder.redis("localhost")
  .withPassword("authentication").build();
RedisClusterClient clusterClient = RedisClusterClient
  .create(rediUri);
StatefulRedisClusterConnection connection
 = clusterClient.connect();
RedisAdvancedClusterCommands syncCommands = connection
  .sync();

RedisAdvancedClusterCommands enthält die vom Cluster unterstützten Redis-Befehle und leitet sie an die Instanz weiter, die den Schlüssel enthält.

Eine vollständige Spezifikation ist verfügbarhere.

10. Fazit

In diesem Tutorial haben wir uns angesehen, wie Sie mit Lettuce eine Verbindung zu einem Redis-Server innerhalb unserer Anwendung herstellen und ihn abfragen können.

Lettuce unterstützt den kompletten Satz von Redis-Funktionen mit dem Bonus einer vollständig threadsicheren asynchronen Schnittstelle. Darüber hinaus wird dieCompletionStage-Schnittstelle von Java 8 umfassend genutzt, um Anwendungen eine differenzierte Kontrolle darüber zu ermöglichen, wie sie Daten empfangen.

Codebeispiele finden sich wie immer inover on GitHub.