Introduction à Jedis - la bibliothèque client Java Redis

1. Vue d'ensemble

Cet article est une introduction à Jedis , une bibliothèque client en Java pour Redis - le magasin de structure de données en mémoire populaire qui peut également persister sur le disque. Il est piloté par une structure de données basée sur un keystore pour conserver les données et peut être utilisé comme base de données, cache, courtier de messages, etc.

Tout d'abord, nous allons expliquer dans quel genre de situations Jedis est utile et de quoi il s'agit.

Dans les sections suivantes, nous développons les différentes structures de données et expliquons les transactions, le pipelining et la fonctionnalité de publication / abonnement. Nous concluons avec le pooling de connexions et Redis Cluster.

2. Pourquoi Jedis?

Redis répertorie les bibliothèques clientes les plus connues sur leur site officiel. Il existe plusieurs alternatives aux Jedis, mais seulement deux autres méritent actuellement leur étoile de recommandation, la laitue et le Redisson.

Ces deux clients ont des fonctionnalités uniques telles que la sécurité des threads, la gestion transparente de la reconnexion et une API asynchrone, toutes les fonctionnalités dont Jedis manque.

Cependant, il est petit et considérablement plus rapide que les deux autres. En outre, c'est la bibliothèque cliente de choix des développeurs Spring Framework, et elle possède la plus grande communauté des trois.

3. Dépendances de Maven

Commençons par déclarer la seule dépendance dont nous aurons besoin dans le pom.xml :

 redis.clients jedis 2.8.1  

Si vous recherchez la dernière version de la bibliothèque, consultez cette page.

4. Installation de Redis

Vous devrez installer et lancer l'une des dernières versions de Redis. Nous utilisons la dernière version stable en ce moment (3.2.1), mais toute version post 3.x devrait convenir.

Trouvez ici plus d'informations sur Redis pour Linux et Macintosh, ils ont des étapes d'installation de base très similaires. Windows n'est pas officiellement pris en charge, mais ce port est bien entretenu.

Après cela, nous pouvons directement plonger et nous y connecter à partir de notre code Java:

Jedis jedis = new Jedis();

Le constructeur par défaut fonctionnera parfaitement à moins que vous n'ayez démarré le service sur un port autre que celui par défaut ou sur une machine distante, auquel cas vous pouvez le configurer correctement en passant les valeurs correctes en tant que paramètres dans le constructeur.

5. Structures de données Redis

La plupart des commandes d'opération natives sont prises en charge et, de manière assez pratique, elles partagent normalement le même nom de méthode.

5.1. Cordes

Les chaînes sont le type de valeur Redis le plus basique, utile lorsque vous devez conserver des types de données clé-valeur simples:

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

La variable cachedResponse contiendra la valeur 32,15,223,828 . Couplé à la prise en charge de l'expiration, abordée plus tard, il peut fonctionner comme une couche de cache rapide et simple à utiliser pour les requêtes HTTP reçues sur votre application Web et d'autres exigences de mise en cache.

5.2. Listes

Les listes Redis sont simplement des listes de chaînes, triées par ordre d'insertion et en font un outil idéal pour implémenter, par exemple, des files d'attente de messages:

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

La tâche variable contiendra la valeur firstTask . N'oubliez pas que vous pouvez sérialiser n'importe quel objet et le conserver sous forme de chaîne, de sorte que les messages de la file d'attente peuvent transporter des données plus complexes si nécessaire.

5.3. Ensembles

Les ensembles Redis sont une collection non ordonnée de chaînes qui sont utiles lorsque vous souhaitez exclure des membres répétés:

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");

Les surnoms Java Set auront une taille de 2, le deuxième ajout du surnom # 1 a été ignoré. De plus, la variable exists aura la valeur true , la méthode sismember vous permet de vérifier rapidement l'existence d'un membre particulier.

5.4. Hashs

Redis hachages sont mappage entre les cordes des champs et des cordes valeurs:

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");

Comme vous pouvez le voir, les hachages sont un type de données très pratique lorsque vous souhaitez accéder individuellement aux propriétés de l'objet, car vous n'avez pas besoin de récupérer l'objet entier.

5.5. Ensembles triés

Les ensembles triés sont comme un ensemble où chaque membre a un classement associé, qui est utilisé pour les trier:

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");

Le joueur variable conservera la valeur PlayerThree car nous récupérons le premier joueur et il est celui avec le score le plus élevé. La variable de classement aura une valeur de 1 car PlayerOne est le deuxième du classement et le classement est basé sur zéro.

6. Transactions

Les transactions garantissent l'atomicité et les opérations de sécurité des threads, ce qui signifie que les demandes d'autres clients ne seront jamais traitées simultanément pendant les transactions Redis:

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();

Vous pouvez même faire dépendre le succès d'une transaction d'une clé spécifique en la «surveillant» juste avant d'instancier votre transaction :

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

La grande majorité des fonctionnalités de Redis sont déjà disponibles dans Jedis et son développement avance à un bon rythme.

Il vous donne la possibilité d'intégrer un puissant moteur de stockage en mémoire dans votre application avec très peu de tracas, n'oubliez pas de configurer le pool de connexions pour éviter les problèmes de sécurité des threads.

Vous pouvez trouver des exemples de code dans le projet GitHub.