Introduction à la laitue - le client Java Redis

Introduction à la laitue - le client Java Redis

1. Vue d'ensemble

Cet article est une introduction àLettuce, un client Java deRedis.

Redis est un magasin clé-valeur en mémoire pouvant être utilisé comme base de données, cache ou courtier de messages. Les données sont ajoutées, interrogées, modifiées et supprimées avec descommands qui fonctionnent sur les clés dans la structure de données en mémoire de Redis.

Lettuce prend en charge à la fois l'utilisation de communication synchrone et asynchrone de l'API Redis complète, y compris ses structures de données, la messagerie pub / sous et les connexions serveur à haute disponibilité.

2. Pourquoi de la laitue?

Nous avons couvert Jedisin one of the previous posts. Qu'est-ce qui différencie la laitue?

La différence la plus significative est sa prise en charge asynchrone via l'interfaceCompletionStage de Java 8 et la prise en charge des flux réactifs. Comme nous le verrons ci-dessous, Lettuce offre une interface naturelle pour effectuer des requêtes asynchrones depuis le serveur de base de données Redis et pour créer des flux.

Il utilise également Netty pour communiquer avec le serveur. Cela crée une API «plus lourde», mais la rend également plus adaptée au partage d'une connexion avec plusieurs threads.

3. Installer

3.1. Dépendance

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


    io.lettuce
    lettuce-core
    5.0.1.RELEASE

La dernière version de la bibliothèque peut être vérifiée sur lesGithub repository ou surMaven Central.

3.2. Installation de Redis

Nous devrons installer et exécuter au moins une instance de Redis, deux si nous souhaitons tester le clustering ou le mode sentinel (bien que le mode sentinel nécessite trois serveurs pour fonctionner correctement.) Pour cet article, nous utilisons 4.0.x - le dernière version stable en ce moment.

Plus d'informations sur la mise en route de Redis peuvent être trouvéeshere, y compris les téléchargements pour Linux et MacOS.

Redis ne prend pas officiellement en charge Windows, mais il existe un port du serveurhere. Nous pouvons également exécuter Redis dansDocker, ce qui est une meilleure alternative pour Windows 10 et un moyen rapide de démarrer.

4. Connexions

4.1. Connexion à un serveur

La connexion à Redis comprend quatre étapes:

  1. Créer un URI Redis

  2. Utilisation de l'URI pour se connecter à unRedisClient

  3. Ouvrir une connexion Redis

  4. Générer un ensemble deRedisCommands

Voyons la mise en œuvre:

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

UnStatefulRedisConnection est ce que cela ressemble; une connexion thread-safe à un serveur Redis qui maintiendra sa connexion au serveur et se reconnectera si nécessaire. Une fois la connexion établie, nous pouvons l’utiliser pour exécuter les commandes Redis de manière synchrone ou asynchrone.

RedisClient utilise des ressources système importantes, car il contient des ressources Netty pour communiquer avec le serveur Redis. Les applications qui nécessitent plusieurs connexions doivent utiliser un seulRedisClient.

4.2. URI Redis

Nous créons unRedisClient en passant un URI à la méthode de fabrique statique.

Lettuce exploite une syntaxe personnalisée pour les URI Redis. C'est le schéma:

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

Il existe quatre types d'URI:

  • redis - un serveur Redis autonome

  • rediss - un serveur Redis autonome via une connexion SSL

  • redis-socket - un serveur Redis autonome via un socket de domaine Unix

  • redis-sentinel - un serveur Redis Sentinel

L'instance de base de données Redis peut être spécifiée en tant que partie du chemin d'URL ou en tant que paramètre supplémentaire. Si les deux sont fournis, le paramètre a une priorité plus élevée.

Dans l'exemple ci-dessus, nous utilisons une représentationString. La laitue a également une classeRedisURI pour les connexions de bâtiments. Il propose le modèleBuilder:

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

Et un constructeur:

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

4.3. Commandes synchrones

Semblable à Jedis, Lettuce fournit un jeu de commandes Redis complet sous forme de méthodes.

Toutefois, Lettuce implémente les versions synchrone et asynchrone. Nous allons examiner brièvement la version synchrone, puis utiliser la mise en œuvre asynchrone pour le reste du didacticiel.

Après avoir créé une connexion, nous l’utilisons pour créer un jeu de commandes:

RedisCommands syncCommands = connection.sync();

Nous avons maintenant une interface intuitive pour communiquer avec Redis.

Nous pouvons définir et obtenirString values:

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

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

Nous pouvons travailler avec des hashes:

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

Nous couvrirons plus de Redis plus tard dans l’article.

L'API synchrone Lettuce utilise l'API asynchrone. Le blocage est fait pour nous au niveau de la commande. This means that more than one client can share a synchronous connection.

4.4. Commandes asynchrones

Jetons un coup d’œil aux commandes asynchrones:

RedisAsyncCommands asyncCommands = connection.async();

Nous récupérons un ensemble deRedisAsyncCommands de la connexion, de la même manière que nous avons récupéré l'ensemble synchrone. Ces commandes renvoient unRedisFuture (qui est unCompletableFuture en interne):

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

Un guide pour travailler avec unCompletableFuture peut être trouvéhere.

4.5. API réactive

Enfin, voyons comment utiliser des API réactives non bloquantes:

RedisStringReactiveCommands reactiveCommands = connection.reactive();

Ces commandes renvoient des résultats encapsulés dans unMono ou unFlux deProject Reactor.

Un guide d'utilisation de Project Reactor est disponiblehere.

5. Structures de données Redis

Nous avons brièvement examiné les chaînes et les hachages ci-dessus, voyons comment Lettuce implémente le reste des structures de données de Redis. Comme on pouvait s'y attendre, chaque commande Redis a une méthode portant le même nom.

5.1. Listes

Lists are lists of Strings with the order of insertion preserved. Les valeurs sont insérées ou récupérées à l'une ou l'autre extrémité:

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

String nextTask = redisFuture.get();

Dans cet exemple,nextTask est égal à «firstTask». Lpush pousse les valeurs en tête de la liste, puisrpop fait apparaître les valeurs à partir de la fin de la liste.

Nous pouvons également extraire des éléments de l'autre extrémité:

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

String nextTask = redisFuture.get();

Nous commençons le deuxième exemple en supprimant la liste avecdel. Ensuite, nous insérons à nouveau les mêmes valeurs, mais nous utilisonslpop pour faire apparaître les valeurs de la tête de la liste, donc lenextTask contient le texte «secondTask».

5.2. Sets

Les ensembles Redis sont des collections non ordonnées deStrings similaires à JavaSets; il n'y a pas d'éléments en double:

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

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

Lorsque nous récupérons l'ensemble Redis en tant queSet, la taille est de deux, car le“cat” en double a été ignoré. Lorsque nous interrogeons Redis sur l'existence de“dog” avecsismember,, la réponse esttrue.

5.3. Des hachis

Nous avons brièvement examiné un exemple de hachage plus tôt. Ils valent une explication rapide.

Redis Hashes are records with String fields and values. Chaque enregistrement a également une clé dans l'index primaire:

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

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

Nous utilisonshset pour ajouter des champs au hachage, en passant le nom du hachage, le nom du champ et une valeur.

Ensuite, nous récupérons une valeur individuelle avechget, le nom de l'enregistrement et le champ. Enfin, nous récupérons l'intégralité de l'enregistrement sous forme de hachage avechgetall.

5.4. Ensembles triés

Sorted Sets contains values and a rank, by which they are sorted. Le rang est une valeur à virgule flottante de 64 bits.

Les articles sont ajoutés avec un rang et récupérés dans une plage:

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

Le deuxième argument dezadd est un rang. Nous récupérons une plage par rang aveczrange pour l'ordre croissant etzrevrange pour décroissant.

Nous avons ajouté «zero» avec un rang de 4, donc il apparaîtra à la fin devaluesForward et au début devaluesReverse.

6. Transactions

Les transactions permettent l'exécution d'un ensemble de commandes en une seule étape atomique. Ces commandes sont garanties d'être exécutées dans l'ordre et exclusivement. Les commandes d'un autre utilisateur ne seront exécutées qu'une fois la transaction terminée.

Toutes les commandes sont exécutées ou aucune d'entre elles ne le sont. Redis will not perform a rollback if one of them fails. Une foisexec() appelé, toutes les commandes sont exécutées dans l'ordre spécifié.

Regardons un exemple:

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

L'appel àmulti démarre la transaction. Lorsqu'une transaction est lancée, les commandes suivantes ne sont exécutées que lorsqueexec() est appelé.

En mode synchrone, les commandes renvoientnull.. En mode asynchrone, les commandes renvoientRedisFuture. Exec renvoie unTransactionResult qui contient une liste de réponses.

Comme lesRedisFutures reçoivent également leurs résultats, les clients API asynchrones reçoivent le résultat de la transaction à deux endroits.

7. Batching

Dans des conditions normales, Lettuce exécute les commandes dès qu’elles sont appelées par un client API.

C’est ce que souhaitent la plupart des applications normales, en particulier si elles reposent sur la réception en série des résultats de commande.

Cependant, ce comportement n'est pas efficace si les applications n'ont pas besoin de résultats immédiatement ou si de grandes quantités de données sont importées en masse.

Les applications asynchrones peuvent remplacer ce comportement:

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

Avec setAutoFlushCommands défini surfalse, l'application doit appelerflushCommands manuellement. Dans cet exemple, nous avons mis en file d'attente plusieurs commandesset, puis vidé le canal. AwaitAll attend que tous lesRedisFutures se terminent.

Cet état est défini sur une base par connexion et affecte tous les threads qui utilisent la connexion. Cette fonctionnalité n'est pas applicable aux commandes synchrones.

8. Publish/Subscribe

Redis propose un système de messagerie simple pour publier / souscrire. Les abonnés consomment les messages des canaux avec la commandesubscribe. Les messages ne sont pas conservés; ils ne sont livrés aux utilisateurs que lorsqu'ils sont abonnés à une chaîne.

Redis utilise le système pub / sous-système pour les notifications sur le jeu de données Redis, donnant ainsi aux clients la possibilité de recevoir des événements sur les clés en cours de définition, de suppression, d'expiration, etc.

Voir la documentationhere pour plus de détails.

8.1. Abonné

UnRedisPubSubListener reçoit des messages pub / sub. Cette interface définit plusieurs méthodes, mais nous allons simplement montrer la méthode de réception des messages ici:

public class Listener implements RedisPubSubListener {

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

Nous utilisons lesRedisClient pour connecter un canal pub / sous et installer l'écouteur:

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

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

Avec un écouteur installé, nous récupérons un ensemble deRedisPubSubAsyncCommands et nous nous abonnons à un canal.

8.2. Éditeur

La publication consiste simplement à connecter un canal Pub / Sub et à récupérer les commandes:

StatefulRedisPubSubConnection connection
  = client.connectPubSub();

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

La publication nécessite un canal et un message.

8.3. Abonnements réactifs

Lettuce offre également une interface réactive pour s’abonner aux messages pub / sub:

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

LeFlux retourné parobserveChannels reçoit des messages pour tous les canaux, mais comme il s'agit d'un flux, le filtrage est facile à faire.

9. La haute disponibilité

Redis propose plusieurs options pour la haute disponibilité et l'évolutivité. Une compréhension complète nécessite une connaissance des configurations de serveur Redis, mais nous allons passer en revue un bref aperçu de la façon dont Lettuce les prend en charge.

9.1. Master/Slave

Les serveurs Redis se répliquent dans une configuration maître / esclave. Le serveur maître envoie à l'esclave un flux de commandes qui répliquent le cache maître sur l'esclave. Redis doesn’t support bi-directional replication, so slaves are read-only.

Lettuce peut se connecter aux systèmes maître / esclave, les interroger sur la topologie, puis sélectionner les esclaves pour les opérations de lecture, ce qui peut améliorer le débit:

RedisClient redisClient = RedisClient.create();

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

connection.setReadFrom(ReadFrom.SLAVE);

9.2. Sentinelle

Redis Sentinel surveille les instances maître et esclave et orchestre les basculements d’esclaves en cas de basculement maître.

Lettuce peut se connecter au Sentinel, l'utiliser pour découvrir l'adresse du maître actuel, puis lui renvoyer une connexion.

Pour ce faire, nous construisons unRedisURI différent et connectons nosRedisClient avec lui:

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

RedisConnection connection = client.connect();

Nous avons construit l'URI avec le nom d'hôte (ou l'adresse) de la première Sentinel et un nom de cluster, suivis d'une deuxième adresse sentinelle. Lorsque nous nous connectons à Sentinel, Lettuce l'interroge sur la topologie et nous renvoie une connexion au serveur maître actuel.

La documentation complète est disponiblehere.

9.3. Clusters

Redis Cluster utilise une configuration distribuée pour assurer une haute disponibilité et un débit élevé.

Clusters de clés de partition sur jusqu'à 1000 nœuds, par conséquent les transactions ne sont pas disponibles dans un cluster:

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

RedisAdvancedClusterCommands contient l'ensemble des commandes Redis prises en charge par le cluster, les acheminant vers l'instance qui contient la clé.

Une spécification complète est disponiblehere.

10. Conclusion

Dans ce didacticiel, nous avons expliqué comment utiliser Lettuce pour connecter et interroger un serveur Redis à partir de notre application.

Lettuce prend en charge l'ensemble des fonctionnalités de Redis, avec en prime une interface asynchrone entièrement sécurisée pour les threads. Il utilise également largement l’interfaceCompletionStagede Java 8 pour donner aux applications un contrôle précis sur la manière dont elles reçoivent les données.

Des échantillons de code, comme toujours, peuvent être trouvésover on GitHub.