Étiquette : redis

  • Gestion de session avec un cluster redis – suite et fin

    suite à une remarque de Remy, j’ai modifié le test gatling pour bien vérifier qu’on récupère la même session lorsque le slave a pris la place du master.

    Pour cela, dans le test, je fais une boucle sur la page index qui indique le nom de la personne si elle est logguée. J’utilise les fonctions de gatling de check pour vérifier que l’information est bien présente.

    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.3: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.3:8080"
    
    val scn = scenario("Session")
    .exec(http("Login")
    .get("/session-webapp/login.html?userName=benoit")
    )
    .pause(7).during(5 minutes, "during_0"){
    exec(http("refresh_1")
    .get("/session-webapp/index.jsp").check(status.is(200)).check(regex("benoit").exists)).pause(1);
    };
    
    setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol)
    }

    Ce qui donne bien:

    gatling

     

    error

    Pendant le delai d’indisponibilité, on a des erreurs de type 500 et ensuite on repart bien sur la bonne session avec les bonnes données.

    L’erreur 500 peut être traitée dans la valve pour gérer un peu ce type d’erreur et rediriger l’utilisateur vers une page adaptée mais ce n’est pas l’objet de cette article.

  • 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

     

  • Partage de session avec spring-session

    Un collègue m’a fait découvrir spring-session lors d’un échange sur la façon de stocker les sessions dans un tiers.

    J’ai donc décidé de reprendre mon poc pour l’adapter à cette librairie.

    La mise en place est assez simple. Il faut juste créer 2 classes.

    1 pour la configuration (Config) et 1 pour que spring initialise mon application (Initializer). Dans une application existante la dernière classe n’est pas obligatoire. Il suffira de déclarer le bean config dans le fichier de context XML spring.

    • configuration:
    @Configuration
    @EnableRedisHttpSession
    public class Config {
    
          @Bean
          public JedisConnectionFactory connectionFactory() {
                JedisConnectionFactory factory = new JedisConnectionFactory();
                factory.setHostName("db");
                return factory;
          }
    }

    Il suffit de placer l’annotation EnableRedisHttpSession et de déclarer un bean pour la connectionFactory. Par defaut JedisConnectionFactory se connecte sur la base redis en localhost. Dans mon cas, j’ai surchargé pour aller sur le host db (container redis)

    Il faut supprimer aussi le fichier context.xml qui n’est plus nécessaire.
    Il faut passer en servlet 3.0 pour que l’initialisation fonctionne.

    et voilà, c’est tout.

    On peut tester le tout avec les commandes:

    mvn clean install
    ./start_all.sh

     

    • Changements:

    Par rapport au précédent article, le poc a subi quelques modifications pour fonctionner:

    – passage à tomcat 8
    – inclusion de spring 4
    – inclusion de spring web
    – inclusion de spring-data
    – servlet 3.0

    Les sources sont disponibles ici

    Merci Raphaël pour l’info.

     

  • docker – liens entre container suite

    Maintenant que nous avons la bonne version de docker, nous pouvons modifier le fichier context.xml de l’application pour pointer sur le hostname de redis :

    className="com.radiadesign.catalina.session.RedisSessionManager"
    host="db"
    port="6379"

    On va aussi modifier le démarrage du serveur d’applications pour créer un lien vers redis:

    docker run -i -t --link redis:db session-webapp:1.0-SNAPSHOT

    tomcat et redis sont maintenant connectée sans avoir besoin de connaître l’ip de chacun.

    Et voici le script de démarrage complet :

    docker run --rm --name redis -p 6379:6379 -v /tmp/data:/data dockerfile/redis &
    
    sleep 5
    
    docker run --rm --name appA --link redis:db session-webapp:1.0-SNAPSHOT &
    
    docker run --rm --name appB --link redis:db session-webapp:1.0-SNAPSHOT &
    
    sleep 10
    
    docker run --rm --name haproxy --link appA:app1 --link appB:app2 --env "UPSTREAMS=2" bcharret/haproxy &

    Nous avons donc réussi à déployer des applications qui sont connectées entre elles, sans connaître les ips de chacune à l’avance. En termes de scalabilité, il devient aisé de rajouter un serveur d’application. Il faut juste démarrer un nouveau conteneur et le linker au haproxy.

    Dans un prochain article, nous essayerons de voir comment faire fonctionner un cluster de redis avec docker.

    Les sources sont à jour ici: https://github.com/BenoitCharret/session-webapp/tree/article_3

  • Partage de session avec redis, tomcat et docker

    Maintenant que nous avons 2 serveurs tomcat, ça serait bien de pouvoir les arrêter un par un (pour effectuer une livraison par exemples) sans que cela n’ait d’impact pour l’utilisateur. Je pense notamment à la gestion de la session utilisateur. Je vais donc essayer de stocker (sérialiser) la session dans une base nosql. Vu qu’il faut juste stocker un couple clé/valeur, c’est beaucoup moins lourd qu’une base de données.

    Préparation du code

    Pour commencer, je vais reprendre le travail de James Coleman sur ce sujet https://github.com/jcoleman/tomcat-redis-session-manager. Tout d’abord comme je ne suis pas très à l’aise avec gradle (pour le moment), j’ai commencé par créer un pom.xml pour gérer les dépendances et plus tard créer une image docker. Comme expliqué dans le préambule du repository, la détection de la modification de la session pose des soucis si on modifie directement l’objet après avoir fait un getAttribute()

    Produit p=(Produit)session.getAttribute("product");
    p.setCount(p.getCount()+1);

    J’ai donc décidé (pour l’exemple) de forcer l’enregistrement de la session à la fin de chaque requête. Il faudra vérifier avec un test de charge que ça ne pose pas de soucis pour une mise en production. Pour utiliser cette libraire, il faut la builder et l’ajouter au répertoire lib de tomcat (ça ne fonctionne pas si la librairie est embarqué dans le projet. Pour faciliter un peu les choses, maven va créer une librairie qui contiendra ses propres dépendances:

    <plugin>
    	<artifactId>maven-assembly-plugin</artifactId>
    	<executions>
    	    <execution>
    		<phase>package</phase>
    		<goals>
    		    <goal>single</goal>
    		</goals>
    	    </execution>
    	</executions>
    	<configuration>
    	    <descriptorRefs>
    		<descriptorRef>jar-with-dependencies</descriptorRef>
    	    </descriptorRefs>
    	</configuration>
    </plugin>

    Ensuite, il suffit juste au niveau du projet de modifier le fichier META-INF/context.xml pour ajouter une valve et un manager:

     <Valve className="com.radiadesign.catalina.session.RedisSessionHandlerValve" />
        <Manager className="com.radiadesign.catalina.session.RedisSessionManager"
                 host="172.17.0.2"
        port="6379"
        database="0"
        maxInactiveInterval="180" />
    

    On peut personnaliser la machine et le port de redis ainsi que la durée d’une session (ici j’ai réglé à 3 min). Maintenant que tout est configuré, voyons comment intégré cela dans docker.

    Préparation des images

    J’ai créé un fichier Dockerfile dans le projet tomcat-redis-session-manager pour créer une nouvelle image se basant sur jolokia/tomcat-6.0 et ajoutant notre librairie dans le répertoire lib.

    FROM jolokia/tomcat-6.0
    
    ENV TOMCAT_VERSION 6.0.39
    
    ADD target/tomcat-redis-session-manager-jar-with-dependencies.jar /opt/apache-tomcat-${TOMCAT_VERSION}/lib/
    

    D’ailleurs, il semble que docker est des soucis avec des répertoires symboliques target. Si je remplace la dernière ligne par

    ADD target/tomcat-redis-session-manager-jar-with-dependencies.jar /opt/tomcat/lib/

    j’obtiens l’erreur suivante (/opt/tomcat étant un lien symbolique vers opt/apache-tomcat-${TOMCAT_VERSION})

    &nbsp;2014/05/25 17:18:25 mkdir /var/lib/docker/aufs/mnt/309ed27293f7af78691fbea762bfb364a3f6dd86fcec8c4a302d16cc91329708/opt/tomcat: file exists

    J’ai ensuite modifier le fichier Dockerfile du projet session-webapp pour se baser sur l’image qu’on vient de créer:

    FROM benoitcharret/tomcat6-session
    

    Il ne reste plus qu’à lancer toutes les images pour voir le résultat

    Lancement des images

    Je commence par lancer redis:

    docker run -d --name redis -p 6379:6379 -v /tmp/data:/data dockerfile/redis

    avec la même commande que dans le dernier article, je récupère son ip (à mettre dans le context.xml) Je lance ensuite un redis-cli pour tester la connectivité

    docker run -d --name redis -p 6379:6379 -v /tmp/data:/data dockerfile/redis
    redis-cli -h 172.17.0.2
    172.17.0.2:6379> ping
    PONG
    
    

    Mon serveur redis est donc opérationnel. Je lance maintenant 2 serveurs tomcat et haproxy. Je me connecte maintenant sur le site, je me loggue et fais des refresh sur la page index.jsp. Cela nous donne dans logs:

    INFO: Server startup in 404 ms
    917E8054D3BC7A4D9E4217136C415248
    917E8054D3BC7A4D9E4217136C415248
    value of product 1
    917E8054D3BC7A4D9E4217136C415248
    value of product 2
    917E8054D3BC7A4D9E4217136C415248
    value of product 3
    917E8054D3BC7A4D9E4217136C415248
    value of product 4
    917E8054D3BC7A4D9E4217136C415248
    value of product 5
    917E8054D3BC7A4D9E4217136C415248
    value of product 6
    917E8054D3BC7A4D9E4217136C415248
    value of product 7
    ^CMay 25, 2014 3:26:33 PM org.apache.coyote.http11.Http11Protocol pause
    INFO: Pausing Coyote HTTP/1.1 on http-8080
    May 25, 2014 3:26:34 PM org.apache.catalina.core.StandardService stop
    INFO: Stopping service Catalina

    Je suis bien resté sur le même serveur à chaque fois et j’ai bien récupéré ma donnée product. Si ne n’avais pas fait la modification dans RedisSessionManager, la valeur de product serait toujours de 1. J’ai ensuite arrêté le serveur tomcat et fais des refresh sur index.jsp. Cela nous donne:

    INFO: Server startup in 392 ms
    917E8054D3BC7A4D9E4217136C415248
    value of product 8
    917E8054D3BC7A4D9E4217136C415248
    value of product 9
    917E8054D3BC7A4D9E4217136C415248
    value of product 10
    917E8054D3BC7A4D9E4217136C415248
    value of product 11
    917E8054D3BC7A4D9E4217136C415248
    value of product 12

    J’ai donc bien récupérer ma session et la valeur de product. Pour voir le contenu des clés de redis, on peut utiliser la commande (a ne pas faire en production):

    KEYS *

    Voila comment tester facilement la persistance de session avec redis et docker.

    Les sources sont ici: