Einführung in Jedis - die Java Redis Client Library

1. Übersicht

Dieser Artikel ist eine Einführung in Jedis , eine Client-Bibliothek in Java für Redis - den beliebten speicherinternen Datenstrukturspeicher, der auch auf der Festplatte gespeichert werden kann. Es wird von einer Keystore-basierten Datenstruktur gesteuert, um Daten zu speichern, und kann als Datenbank, Cache, Nachrichtenbroker usw. verwendet werden.

Zunächst werden wir erklären, in welchen Situationen Jedis nützlich sind und worum es geht.

In den folgenden Abschnitten werden die verschiedenen Datenstrukturen erläutert und Transaktionen, Pipelining und die Publish / Subscribe-Funktion erläutert. Wir schließen mit Verbindungspooling und Redis Cluster.

2. Warum Jedis?

Redis listet die bekanntesten Client-Bibliotheken auf ihrer offiziellen Website auf. Es gibt mehrere Alternativen zu Jedis, aber nur zwei weitere verdienen derzeit ihren Empfehlungsstern, Salat und Redisson.

Diese beiden Clients verfügen über einige einzigartige Funktionen wie Thread-Sicherheit, transparente Wiederverbindungsbehandlung und eine asynchrone API, die Jedis alle Funktionen fehlen.

Es ist jedoch klein und erheblich schneller als die beiden anderen. Außerdem ist es die Client-Bibliothek der Wahl der Spring Framework-Entwickler und hat die größte Community von allen dreien.

3. Maven-Abhängigkeiten

Beginnen wir damit, die einzige Abhängigkeit zu deklarieren, die wir in der pom.xml benötigen :

 redis.clients jedis 2.8.1  

Wenn Sie nach der neuesten Version der Bibliothek suchen, lesen Sie diese Seite.

4. Redis Installation

Sie müssen eine der neuesten Versionen von Redis installieren und starten. Wir führen derzeit die neueste stabile Version (3.2.1) aus, aber jede Post 3.x-Version sollte in Ordnung sein.

Hier finden Sie weitere Informationen zu Redis für Linux und Macintosh. Die grundlegenden Installationsschritte sind sehr ähnlich. Windows wird offiziell nicht unterstützt, aber dieser Port ist gut gepflegt.

Danach können wir direkt in unseren Java-Code eintauchen und eine Verbindung herstellen:

Jedis jedis = new Jedis();

Der Standardkonstruktor funktioniert einwandfrei, es sei denn, Sie haben den Dienst an einem nicht standardmäßigen Port oder einem Remotecomputer gestartet. In diesem Fall können Sie ihn korrekt konfigurieren, indem Sie die richtigen Werte als Parameter an den Konstruktor übergeben.

5. Redis Datenstrukturen

Die meisten nativen Operationsbefehle werden unterstützt und haben normalerweise denselben Methodennamen.

5.1. Saiten

Zeichenfolgen sind die grundlegendste Art von Redis-Wert. Sie sind nützlich, wenn Sie einfache Schlüsselwert-Datentypen beibehalten möchten:

jedis.set("events/city/rome", "32,15,223,828"); String cachedResponse = jedis.get("events/city/rome");

Die Variable cachedResponse enthält den Wert 32,15,223,828 . In Verbindung mit der später beschriebenen Ablaufunterstützung kann die Cache-Schicht blitzschnell und einfach für HTTP-Anforderungen verwendet werden, die in Ihrer Webanwendung empfangen werden, sowie für andere Caching-Anforderungen.

5.2. Listen

Redis-Listen sind einfach Listen von Zeichenfolgen, die nach Einfügereihenfolge sortiert sind und sich ideal zum Implementieren von Nachrichtenwarteschlangen eignen:

jedis.lpush("queue#tasks", "firstTask"); jedis.lpush("queue#tasks", "secondTask"); String task = jedis.rpop("queue#tasks");

Die variable Task enthält den Wert firstTask . Denken Sie daran, dass Sie jedes Objekt serialisieren und als Zeichenfolge beibehalten können, damit Nachrichten in der Warteschlange bei Bedarf komplexere Daten enthalten können.

5.3. Sets

Redis-Sets sind eine ungeordnete Sammlung von Strings, die nützlich sind, wenn Sie wiederholte Mitglieder ausschließen möchten:

jedis.sadd("nicknames", "nickname#1"); jedis.sadd("nicknames", "nickname#2"); jedis.sadd("nicknames", "nickname#1"); Set nicknames = jedis.smembers("nicknames"); boolean exists = jedis.sismember("nicknames", "nickname#1");

Die Java Set- Spitznamen haben eine Größe von 2, die zweite Hinzufügung des Spitznamens Nr. 1 wurde ignoriert. Außerdem hat die vorhandene Variable den Wert true . Mit der Methode sismember können Sie schnell überprüfen, ob ein bestimmtes Mitglied vorhanden ist.

5.4. Hashes

Redis Hashes zuordnen zwischen String Feldern und String - Werte:

jedis.hset("user#1", "name", "Peter"); jedis.hset("user#1", "job", "politician"); String name = jedis.hget("user#1", "name"); Map fields = jedis.hgetAll("user#1"); String job = fields.get("job");

Wie Sie sehen können, sind Hashes ein sehr praktischer Datentyp, wenn Sie einzeln auf die Objekteigenschaften zugreifen möchten, da Sie nicht das gesamte Objekt abrufen müssen.

5.5. Sortierte Sets

Sortierte Sätze sind wie ein Satz, dem jedes Mitglied eine Rangfolge zugeordnet hat, die zum Sortieren verwendet wird:

Map scores = new HashMap(); scores.put("PlayerOne", 3000.0); scores.put("PlayerTwo", 1500.0); scores.put("PlayerThree", 8200.0); scores.entrySet().forEach(playerScore -> { jedis.zadd(key, playerScore.getValue(), playerScore.getKey()); }); String player = jedis.zrevrange("ranking", 0, 1).iterator().next(); long rank = jedis.zrevrank("ranking", "PlayerOne");

Der variable Spieler behält den Wert PlayerThree, da wir den Top-1-Spieler abrufen und er derjenige mit der höchsten Punktzahl ist. Der Rang Variable einen Wert von 1, weil PlayerOne die zweite in der Rangliste und das Ranking basiert auf Null.

6. Transaktionen

Transaktionen garantieren Atomizität und Thread-Sicherheitsvorgänge, was bedeutet, dass Anfragen von anderen Clients während Redis-Transaktionen niemals gleichzeitig bearbeitet werden:

String friendsPrefix = "friends#"; String userOneId = "4352523"; String userTwoId = "5552321"; Transaction t = jedis.multi(); t.sadd(friendsPrefix + userOneId, userTwoId); t.sadd(friendsPrefix + userTwoId, userOneId); t.exec();

Sie können einen Transaktionserfolg sogar von einem bestimmten Schlüssel abhängig machen, indem Sie ihn unmittelbar vor dem Instanziieren Ihrer Transaktion „beobachten“ :

jedis.watch("friends#deleted#" + userOneId);

If the value of that key changes before the transaction is executed, the transaction will not be completed successfully.

7. Pipelining

When we have to send multiple commands, we can pack them together in one request and save connection overhead by using pipelines, it is essentially a network optimization. As long as the operations are mutually independent, we can take advantage of this technique:

String userOneId = "4352523"; String userTwoId = "4849888"; Pipeline p = jedis.pipelined(); p.sadd("searched#" + userOneId, "paris"); p.zadd("ranking", 126, userOneId); p.zadd("ranking", 325, userTwoId); Response pipeExists = p.sismember("searched#" + userOneId, "paris"); Response
    
      pipeRanking = p.zrange("ranking", 0, -1); p.sync(); String exists = pipeExists.get(); Set ranking = pipeRanking.get();
    

Notice we do not get direct access to the command responses, instead, we're given a Response instance from which we can request the underlying response after the pipeline has been synced.

8. Publish/Subscribe

We can use the Redis messaging broker functionality to send messages between the different components of our system. Make sure the subscriber and publisher threads do not share the same Jedis connection.

8.1. Subscriber

Subscribe and listen to messages sent to a channel:

Jedis jSubscriber = new Jedis(); jSubscriber.subscribe(new JedisPubSub() { @Override public void onMessage(String channel, String message) { // handle message } }, "channel");

Subscribe is a blocking method, you will need to unsubscribe from the JedisPubSub explicitly. We have overridden the onMessage method but there are many more useful methods available to override.

8.2. Publisher

Then simply send messages to that same channel from the publisher's thread:

Jedis jPublisher = new Jedis(); jPublisher.publish("channel", "test message");

9. Connection Pooling

It is important to know that the way we have been dealing with our Jedis instance is naive. In a real-world scenario, you do not want to use a single instance in a multi-threaded environment as a single instance is not thread-safe.

Luckily enough we can easily create a pool of connections to Redis for us to reuse on demand, a pool that is thread safe and reliable as long as you return the resource to the pool when you are done with it.

Let's create the JedisPool:

final JedisPoolConfig poolConfig = buildPoolConfig(); JedisPool jedisPool = new JedisPool(poolConfig, "localhost"); private JedisPoolConfig buildPoolConfig() { final JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(128); poolConfig.setMaxIdle(128); poolConfig.setMinIdle(16); poolConfig.setTestOnBorrow(true); poolConfig.setTestOnReturn(true); poolConfig.setTestWhileIdle(true); poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis()); poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis()); poolConfig.setNumTestsPerEvictionRun(3); poolConfig.setBlockWhenExhausted(true); return poolConfig; }

Since the pool instance is thread safe, you can store it somewhere statically but you should take care of destroying the pool to avoid leaks when the application is being shutdown.

Now we can make use of our pool from anywhere in the application when needed:

try (Jedis jedis = jedisPool.getResource()) { // do operations with jedis resource }

We used the Java try-with-resources statement to avoid having to manually close the Jedis resource, but if you cannot use this statement you can also close the resource manually in the finally clause.

Make sure you use a pool like we have described in your application if you do not want to face nasty multi-threading issues. You can obviously play with the pool configuration parameters to adapt it to the best setup in your system.

10. Redis Cluster

This Redis implementation provides easy scalability and high availability, we encourage you to read their official specification if you are not familiar with it. We will not cover Redis cluster setup since that is a bit out of the scope for this article, but you should have no problems in doing so when you are done with its documentation.

Once we have that ready, we can start using it from our application:

try (JedisCluster jedisCluster = new JedisCluster(new HostAndPort("localhost", 6379))) { // use the jedisCluster resource as if it was a normal Jedis resource } catch (IOException e) {}

We only need to provide the host and port details from one of our master instances, it will auto-discover the rest of the instances in the cluster.

This is certainly a very powerful feature but it is not a silver bullet. When using Redis Cluster you cannot perform transactions nor use pipelines, two important features on which many applications rely for ensuring data integrity.

Transactions are disabled because, in a clustered environment, keys will be persisted across multiple instances. Operation atomicity and thread safety cannot be guaranteed for operations that involve command execution in different instances.

Some advanced key creation strategies will ensure that data that is interesting for you to be persisted in the same instance will get persisted that way. In theory, that should enable you to perform transactions successfully using one of the underlying Jedis instances of the Redis Cluster.

Unfortunately, currently you cannot find out in which Redis instance a particular key is saved using Jedis (which is actually supported natively by Redis), so you do not know which of the instances you must perform the transaction operation. If you are interested about this, you can find more information here.

11. Conclusion

Die überwiegende Mehrheit der Funktionen von Redis ist bereits in Jedis verfügbar, und die Entwicklung schreitet zügig voran.

Es gibt Ihnen die Möglichkeit, eine leistungsstarke In-Memory-Speicher-Engine mit sehr geringem Aufwand in Ihre Anwendung zu integrieren. Vergessen Sie jedoch nicht, das Verbindungspooling einzurichten, um Thread-Sicherheitsprobleme zu vermeiden.

Codebeispiele finden Sie im GitHub-Projekt.