Étiquette : redis cluster

  • Gestion de session avec un cluster redis – suite

    Maintenant que nous avons une application à tester, nous pouvons utiliser gatling pour simuler de la charge et voir comment se comporte l’application en cas de chute d’un noeud master.

    Le script se contente de se logguer, et de rafraîchir 3 fois la page index. Il va injecter 20 users par seconde pendant 5 minutes:

    package com.axioconsulting
    
    import scala.concurrent.duration._
    
    import io.gatling.core.Predef._
    import io.gatling.http.Predef._
    import io.gatling.jdbc.Predef._
    
    class Session extends Simulation {
    
    val httpProtocol = http
    .baseURL("http://172.17.0.83:8080")
    .inferHtmlResources(WhiteList(), BlackList())
    .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
    .acceptEncodingHeader("gzip, deflate")
    .acceptLanguageHeader("en")
    .connection("keep-alive")
    .doNotTrackHeader("1")
    .userAgentHeader("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0")
    
    val headers_1 = Map("Accept" -> "image/png,image/*;q=0.8,*/*;q=0.5")
    
    val uri1 = "http://172.17.0.83:8080"
    
    val scn = scenario("Session")
    .exec(http("Login")
    .get("/session-webapp/login.html?userName=benoit")
    )
    .pause(7)
    .exec(http("refresh_1")
    .get("/session-webapp/index.jsp")
    .resources(http("request_4")
    .get(uri1 + "/favicon.ico")))
    .pause(1)
    .exec(http("refresh_2")
    .get("/session-webapp/index.jsp")
    )
    .pause(1)
    .exec(http("refresh_3")
    .get("/session-webapp/index.jsp")
    )
    
    /*setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol)*/
    setUp(scn.inject(constantUsersPerSec(20) during(5 minutes))).protocols(httpProtocol)
    }

     

    J’ai arrêté un noeud master avec la commande supervisor :

    supervisor> stop redis-1
    redis-1: stopped

    Ce qui nous donne:

    gatling-session

    On peut voir qu’il y a des erreurs pendant près de 10s, le temps que le slave prenne le pas sur le master.

    Il faudrait peut être modifier le session-manager pour tenir de ce problème. A suivre…

  • Gestion de session avec un cluster redis

    Voyons comment mettre en pratique les deux articles précédents (ici et ici).

    Les choses n’étant pas encore très évolués du cote de spring-data-redis pour le mode cluster, je suis revenu sur la version de session-webapp utilisant une valve tomcat.

    Les seules modifications apportées sont la suppression de l’image redis (je vais utiliser la version cluster) et l’utilisation d’une nouvelle image docker de base pour les serveurs Tomcat.

    En effet, j’ai modifié la valve pour utiliser la nouvelle classe JedisCluster à la place de Jedis.
    Mais pour que cela fonctionne, j’ai du utilisé la version 3.0.0-SNAPSHOT de jedis. La version releasée actuelle n’implémente pas les commandes binary par la lecture/ecriture.

    J’ai du également modifié l’image docker pour le cluster redis car par default, le cluster est configuré en 127.0.0.1. Mais quand le client java essaye de découvrir la topologie du cluster, il obtient ces ips, et ne peux pas se connecter aux autres noeuds. La modification permet d’établir la connexion sur l’ip du conteneur.

    Il ne reste plus qu’à lancer tout ce petit monde:

    redis:

    /docker-redis-cluster$ make-clean && make-build && make-run

    tomcat & haproxy

    /session-webapp$ ./stop_all.sh ;  mvn clean install && ./start_all.sh

     

    Lorsque je crée une session, je la vois bien sur un noeud redis et sur un autre noeud, je vois bien le message de redirection:

    http://172.17.0.29:8080/session-webapp/login.html?userName=toto

     

    docker-redis-cluster$ redis-cli -p 7000
    127.0.0.1:7000> get 9C58CB03A3E0898CEA70420FA681B626
    "\xac\xed\x00\x05w\b\x00\x00\x01M\xcf\x82%\xc6sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01M\xcf\x82%\xc6sq\x00~\x00\x00\x00\x00\x01M\xcf\x84\xd43sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexq\x00~\x00\x01\x00\x02\xbf sr\x00\x11java.lang.Boolean\xcd r\x80\xd5\x9c\xfa\xee\x02\x00\x01Z\x00\x05valuexp\x00sq\x00~\x00\x06\x01sq\x00~\x00\x00\x00\x00\x01M\xcf\x84\xd43t\x00 9C58CB03A3E0898CEA70420FA681B626sq\x00~\x00\x04\x00\x00\x00\x02t\x00\buserNamet\x00\x06benoitt\x00\aproductsr\x00$com.axioconsulting.test.bean.ProduitW\x02ts6\x14\xa0\xee\x02\x00\x02J\x00\x05countL\x00\x02idt\x00\x12Ljava/lang/String;xp\x00\x00\x00\x00\x00\x00\x00\x0et\x00\x05pates"
    127.0.0.1:7000> 
    docker-redis-cluster$ redis-cli -p 7001
    127.0.0.1:7001> get 9C58CB03A3E0898CEA

     

     

    Les sources sont ici:
    tomcat-session-manager
    webapp
    docker-redis-cluster

     

  • Redis Cluster

    Voici le résultat de quelques tests réalisé sur le cluster redis (3.0)

    Je suis parti cette image docker https://github.com/Grokzen/docker-redis-cluster

    Après un inévitable

    make build

    l’image est prête à être utilisé. Par défaut, cette image va lancer 3 nœuds masters et 3 nœuds slaves (1 par nœud master).

    Lançons la commande

    $ make run
    Running docker image...
    docker run -d -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 -p 7006:7006 -p 7007:7007 --cidfile /tmp/grokzen-redis-cluster.cid -i -t grokzen/redis-cluster
    eb5c09e1053fd008e6da693b2bb5a60548cbd41c8c2d8139f9bd89328c255d3b

    On peut voir que les ports 7000 à 7005 sont remontés sur le système hôte. Ce sera plus facile pour interroger redis directement.

    $ redis-cli -p 7000
    127.0.0.1:7000> cluster info
    cluster_state:ok
    cluster_slots_assigned:16384
    cluster_slots_ok:16384
    cluster_slots_pfail:0
    cluster_slots_fail:0
    cluster_known_nodes:6

    En interrogeant redis, il y a bien 6 nœuds qui constituent le cluster (cluster_known_nodes).

    La répartition des clés sautent aux yeux quand on essaye d’insérer une valeur

    127.0.0.1:7000> set toto 4
    (error) MOVED 9738 127.0.0.1:7001

    En lisant ce blog, on voit que la clé de répartition, se calcule comme suit:

    HASH_SLOT = CRC16(key) mod 16384

    Dans notre cas, key=toto ce qui donne hash_slots=9548

    Si on inspecte le cluster, on obtient

    $ docker exec -it 4c1589c09148 /bin/bash
    root@4c1589c09148:/# /redis/src/redis-trib.rb check 127.0.0.1:7000
    Connecting to node 127.0.0.1:7000: OK
    Connecting to node 127.0.0.1:7005: OK
    Connecting to node 127.0.0.1:7001: OK
    Connecting to node 127.0.0.1:7002: OK
    Connecting to node 127.0.0.1:7003: OK
    Connecting to node 127.0.0.1:7004: OK
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: c6f94a5f136caf7eccf2d207dc5d6cdccdc44885 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    1 additional replica(s)
    S: fa1ba98d286a7db6ad6e4f7b37999d79c2671a97 127.0.0.1:7005
    slots: (0 slots) slave
    replicates 359cc89af7c1654d9fa408ad91abbd1ddc1906f5
    M: 63e73580b843d499b665a7385967268528e836e9 127.0.0.1:7001
    slots:5461-10922 (5462 slots) master
    1 additional replica(s)
    M: 359cc89af7c1654d9fa408ad91abbd1ddc1906f5 127.0.0.1:7002
    slots:10923-16383 (5461 slots) master
    1 additional replica(s)
    S: e7520e9f5c6a43fca5bd1dd3e514278fa036fa80 127.0.0.1:7003
    slots: (0 slots) slave
    replicates c6f94a5f136caf7eccf2d207dc5d6cdccdc44885
    S: f86b2c4604bebddec390f032faa7df484d576afe 127.0.0.1:7004
    slots: (0 slots) slave
    replicates 63e73580b843d499b665a7385967268528e836e9
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.

    On peut donc voir que notre valeur est contenu entre 5461 et 10922 et donc est géré par le master 63e73580b843d499b665a7385967268528e836e9 soit le l’instance redis sur le port 7001. Ce qui est bien conforme avec l’information fourni par redis.

    Que se passe t’il si on arrête un slave:

    root@4c1589c09148:/# supervisorctl 
    redis-1 RUNNING pid 14, uptime 0:09:39
    redis-2 RUNNING pid 16, uptime 0:09:39
    redis-3 RUNNING pid 15, uptime 0:09:39
    redis-4 RUNNING pid 11, uptime 0:09:39
    redis-5 RUNNING pid 10, uptime 0:09:39
    redis-6 RUNNING pid 13, uptime 0:09:39
    redis-7 RUNNING pid 12, uptime 0:09:39
    redis-8 RUNNING pid 17, uptime 0:09:39
    supervisor> stop redis-6
    redis-6: stopped

    En relançant la commande

    root@4c1589c09148:/# /redis/src/redis-trib.rb check 127.0.0.1:7000
    Connecting to node 127.0.0.1:7000: OK
    Connecting to node 127.0.0.1:7005: [ERR] Sorry, can't connect to node 127.0.0.1:7005
    OK
    Connecting to node 127.0.0.1:7001: OK
    Connecting to node 127.0.0.1:7002: OK
    Connecting to node 127.0.0.1:7003: OK
    Connecting to node 127.0.0.1:7004: OK
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: c6f94a5f136caf7eccf2d207dc5d6cdccdc44885 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    1 additional replica(s)
    M: 63e73580b843d499b665a7385967268528e836e9 127.0.0.1:7001
    slots:5461-10922 (5462 slots) master
    1 additional replica(s)
    M: 359cc89af7c1654d9fa408ad91abbd1ddc1906f5 127.0.0.1:7002
    slots:10923-16383 (5461 slots) master
    0 additional replica(s)
    S: e7520e9f5c6a43fca5bd1dd3e514278fa036fa80 127.0.0.1:7003
    slots: (0 slots) slave
    replicates c6f94a5f136caf7eccf2d207dc5d6cdccdc44885
    S: f86b2c4604bebddec390f032faa7df484d576afe 127.0.0.1:7004
    slots: (0 slots) slave
    replicates 63e73580b843d499b665a7385967268528e836e9
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.

    On voit bien l’erreur de connexion à l’instance sur le port 7005 mais cela n’a pas d’incidence sur la santé du cluster.

    Que se passe t’il si on arrête un master.

    supervisor> stop redis-2
    redis-2: stopped
    root@4c1589c09148:/# /redis/src/redis-trib.rb check 127.0.0.1:7000
    Connecting to node 127.0.0.1:7000: OK
    Connecting to node 127.0.0.1:7005: OK
    Connecting to node 127.0.0.1:7001: [ERR] Sorry, can't connect to node 127.0.0.1:7001
    OK
    Connecting to node 127.0.0.1:7002: OK
    Connecting to node 127.0.0.1:7003: OK
    Connecting to node 127.0.0.1:7004: OK
    *** WARNING: 127.0.0.1:7004 claims to be slave of unknown node ID 63e73580b843d499b665a7385967268528e836e9.
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: c6f94a5f136caf7eccf2d207dc5d6cdccdc44885 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    1 additional replica(s)
    S: fa1ba98d286a7db6ad6e4f7b37999d79c2671a97 127.0.0.1:7005
    slots: (0 slots) slave
    replicates 359cc89af7c1654d9fa408ad91abbd1ddc1906f5
    M: 359cc89af7c1654d9fa408ad91abbd1ddc1906f5 127.0.0.1:7002
    slots:10923-16383 (5461 slots) master
    1 additional replica(s)
    S: e7520e9f5c6a43fca5bd1dd3e514278fa036fa80 127.0.0.1:7003
    slots: (0 slots) slave
    replicates c6f94a5f136caf7eccf2d207dc5d6cdccdc44885
    S: f86b2c4604bebddec390f032faa7df484d576afe 127.0.0.1:7004
    slots: (0 slots) slave
    replicates 63e73580b843d499b665a7385967268528e836e9
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [ERR] Not all 16384 slots are covered by nodes.

    Le cluster voit bien le problème de connexion à l’instance défaillante.
    il voit aussi qu’il y a un slave qui n’a pas de master. A la dernière ligne, on voit qu’il y a un problème de cohérence sur le cluster car tous les slots de répartition ne sont pas couverts par le cluster

    Mais cela est corrigé quand le slave devient master:

    root@4c1589c09148:/# /redis/src/redis-trib.rb check 127.0.0.1:7000
    Connecting to node 127.0.0.1:7000: OK
    Connecting to node 127.0.0.1:7005: OK
    Connecting to node 127.0.0.1:7002: OK
    Connecting to node 127.0.0.1:7003: OK
    Connecting to node 127.0.0.1:7004: OK
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: c6f94a5f136caf7eccf2d207dc5d6cdccdc44885 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    1 additional replica(s)
    S: fa1ba98d286a7db6ad6e4f7b37999d79c2671a97 127.0.0.1:7005
    slots: (0 slots) slave
    replicates 359cc89af7c1654d9fa408ad91abbd1ddc1906f5
    M: 359cc89af7c1654d9fa408ad91abbd1ddc1906f5 127.0.0.1:7002
    slots:10923-16383 (5461 slots) master
    1 additional replica(s)
    S: e7520e9f5c6a43fca5bd1dd3e514278fa036fa80 127.0.0.1:7003
    slots: (0 slots) slave
    replicates c6f94a5f136caf7eccf2d207dc5d6cdccdc44885
    M: f86b2c4604bebddec390f032faa7df484d576afe 127.0.0.1:7004
    slots:5461-10922 (5462 slots) master
    0 additional replica(s)
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.

    J’ai modifié le programme Java initié dans le précédent article et je mesure à 6s, le temps pour que le cluster soit de nouveaux opérationnels;

    23:37:19.CEST / res - bar
    23:37:20.CEST / Too many Cluster redirections?
    23:37:21.CEST / Too many Cluster redirections?
    23:37:22.CEST / Too many Cluster redirections?
    23:37:23.CEST / Too many Cluster redirections?
    23:37:24.CEST / Too many Cluster redirections?
    23:37:25.CEST / CLUSTERDOWN The cluster is down
    23:37:26.CEST / res - bar

    Il est peut être possible de jouer sur le paramétrage pour descendre ce temps, mais je n’ai pas encore trouvé comment.

    On voit bien ici le problème lorsque que les données ne soit pas répliquées entre les différents masters. Lorsqu’un master est absent, il y a un « petit » laps de temps ou les données ne sont pas connues.

  • Redis Cluster en java

    En commençant à regarder le mode cluster de redis à travers une image docker déjà construire https://github.com/Grokzen/docker-redis-cluster, je me suis aperçu que ce n’est pas si transparent. En effaçant, si on essaye d’ajouter une valeur mais que la clé ne correspond au bon noeud, redis ne renvoi pas OK mais plutôt l’indication de ou insérer la donnée:

    $ redis-cli -p 7000
    127.0.0.1:7000> set toto foo
    (error) MOVED 9738 127.0.0.1:7001

    Je me suis demandé comment gérer cela dans un programme Java. Est-ce le driver qui va faire le travail ou il faut le gérer à la main.

    Si on utilise le driver standard de Jedis, voici ce qu’on obtiens:

    Jedis jedis=new Jedis("localhost",7000);
    jedis.connect();
    jedis.set("toto","bar");
    String value=jedis.get("toto");
    System.out.println("res - "+value);
    Exception in thread "main" redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 9738 127.0.0.1:7001
    at redis.clients.jedis.Protocol.processError(Protocol.java:108)
    at redis.clients.jedis.Protocol.process(Protocol.java:142)
    at redis.clients.jedis.Protocol.read(Protocol.java:196)
    at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:288)
    at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:187)
    at redis.clients.jedis.Jedis.set(Jedis.java:66)
    at com.axioconsulting.redis.TestCluster.main(TestCluster.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

    Heureusement, Jedis propose une classe pour résoudre ce problème (Je n’ai rien trouvé coté JRedis).

    Set jedisClusterNodes=new HashSet(); jedisClusterNodes.add(new HostAndPort("localhost",7000)); 
    jc.set("toto","bar");
    String value=jc.get("toto");
    System.out.println("res - "+value);
    res - bar

     

    Le driver est même capable de récupérer les autres noeuds. Dans l’exemple j’ai indiqué un seul noeud, mais avec le code suivant :

    for (Map.Entry<string,jedispool> entry:jc.getClusterNodes().entrySet()){
       System.out.println(entry.getKey());
    }

    on voit bien que le driver a pu récupérer les autres noeuds:

    localhost:7000
    127.0.0.1:7004
    127.0.0.1:7005
    127.0.0.1:7002
    127.0.0.1:7003
    127.0.0.1:7000
    127.0.0.1:7001
    
    

    les sources sont ici

    Quelques liens utiles:
    – commandes redis cluster

    – À la découverte de Redis Cluster 3.0