Revue de code : la face cachée d’une application distribuée. Reverse engineering

 

Pourquoi pendant 32 secondes, au lancement de l’application, l’ingénieur réseau remarque trop de requêtes multicast ?

Principe du cluster : comment les algorithmes fonctionnent pour qu’il y ait trafic réseau uniquement quand il y a changement de charge. Avec Corba c’est parfait, avec JEE c’est pas évident en natif, il faut des librairies.

Blocking queue d’éléments booléens, parce qu’un booléen en java ça ne prend pas beaucoup de place :

 

1 2 3              

 

p.poll(5,TimeUnit.SECONDS) ;

On attend 5 secondes. L’implémentation de poll est basée sur « Lock » : Lock.newCondition , routines bas niveau au niveau de la JVM.

On aurait pu utiliser object.signal ou bien wait pour faire une pause. La routine signal a un peu le même genre de signature.

Collections.synchronizedSet rend un élément Threadsafe, c'est-à-dire que sur l’accès à l’objet lui-même, c'est-à-dire : soit tous les accès leture/écriture sont garantis par un seul thread uniquement : aucun autre thread ne peut y toucher.

Est-ce qu’on a intêret de bloquer 2 reads, c'est-à-dire 2 threads qui viennent lire en même temps ? Non, on s’en fiche qu’il y ait 2 lectures au même moment. Par contre, si on a un write, c’est différent. Le write fait un lock, donc si j’ai un read ensuite, on ne pourra pas lire car il est locké.

 

Donc là, les accès concurrents multithreadés sont interdits, à la fois en lecture et en écriture. ( voir http://www.consultingit.fr/fr/formation-devenez-developpeur-en-langage-java/programmation-parallele-multitache-processus-et-fils-d-execution-threads )

 

Attention le choix d’un objet est très important : si on a des milliers d’enregistrement dans la structure, il faut bien choisir si l’on part sur un tableau ou sur on part sur une liste chainée LinkHashSet.

Voir en algorithmique, l’implémentation d’une liste chainée : elle contient une tête, une queue. Toutes les méthodes de l’objet peuvent être synchronisées.

Par exemple, Zookeeper, c’est magique, parce qu’on a un process zookeeper, et on a les process métier : on s’enregistre sur le process et ensuite on partage des données entre contextes très facilement sans grosse implémentation.

On se base sur des solutions qui nous apportent des algorithmes tout faits, on n’a pas à réinventer la roue.

 

 

Une question? Posez-la ici

 

Sur la MainClient.java

 

On a l’utilisation d’une librairie très connue dans le monde des applications distribuées : import net.sf.ehcache, ca sert à faire du cache distribué ; Si j’ai 2 process, chacun ayant un cache, quand l’un va écrire dans le cache, AUTOMATIQUEMENT, ça va écrire dans l’autre. C’est comme quand on parle d’un registre réparti : un registre répliqué sur chacun des contextes.

Ehcache repose sur sur pas mal de choses. On peut faire du ehcache au dessus d’un certain nombre de protocoles.

On a le cache manager, c’est en gros un design pattern singleton qui nous permet d’associer d’autres caches.

On voit qu’on a un cche déclaré private Cache lbcache ;

// lb comme load balancing

Ligne 101 on voit qu’un cache est déclaré à partir d’une config, et le cache est ajouté au cache manager.

Qu’est-ce qui se passe ensuite pour le cache en question ?

 

privateclass ShowCacheTask implements Callable<Void> {

             @Override

             public Void call() throws Exception {

                    while (true) {

                          StringBuffer b = new StringBuffer();

                          intk = 0;

                          for(Object key : lbCache.getKeys()) {

                                 final Element el = lbCache.get(key);

                                 final UUID id = (UUID)el.getObjectKey();

                                 finalintload = (Integer)el.getObjectValue();

                                 b.append(k++>0?",":"").append("[").append(id).append(",").append(load).append("]");

                          }

                          logger.debug(b.toString());

                          Thread.sleep(1000);

                    }

             }

       }

 

Ligne 203, je vois une méthode qui est une tache : c’est un callable, et cette tache tourne en arrière plan, c’est une boucle infinie qui se contente d’afficher le contenu du cache.

Qu’y a-t-il dans ce contenu ? Ce cache est organisé en [cla, valeurs] donc on parcours pour chaque clé, on récupère l’élément qui est un élément ehcache. Cet élément embarque une donnée sérialisé, et je peux donc faire un getObject, puis un getObjectvalue() ;

 

Key

Element

Objet {uuid, Integer}

 

La variable Integer est un pourcentage, une valeur entre 0 et 100. L’idée c’est d’avoir comme support une valeur qui remonte au client la charge. Sur 4 serveurs par exemple, on veut savoir quel est le moins chargé . Pour le savoir on doit connaitre l’information en temps réel.

 

Une question? Posez-la ici

 

 

Exemple avec 1 client et 3 services

Souvent qu’est-ce qui se passe ? On a par exemple 3 services, avec des charges correspondantes :

S1(25)

S2(75)

S3(10)

On considère 1 client : C1

C1 doit invoquer le service le moins chargé. Il connait la liste des service sans connaitre la charge de chaque service.

Comment savoir quel est le taux de charge ? En mode cluster, S1 connait la charge de S2, S2 connait la charge de S3…

Hypothèse 2 : le client connait les charges en parcourant une table contenant le taux de charge. ShowCacheTask montre le taux d’occupation des services.

Comprendre le raisonnement qu’il y a derrière : ce n’est pas une recette de cuisine. A chaque recette, on met à jour la charge Une requete supplémentaire est supposée faire augmenter la charge CPU, la charge mémoire. L’idée c’est que la charge augmente. La charge peut être aussi le nombre de requêtes en attente. S’il y a trop de requêtes en attente, on sera de moins en moins invoqué. A un moment donné, il faut diffuser la charge quand elle augmente. On peut le faire soit à chaque requête : le taux sera linéaire. Ou alors on peut faire une diffusion par palier. Ca veut dire qu’on va diffuser la charge à chaque fois qu’on atteint un palier.

 

                    FactoryConfiguration peerListenerConf = new FactoryConfiguration()

                                 .className("net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory")

                                 .properties("hostName=localhost, port=" + port + ", socketTimeoutMillis=2000");

                    FactoryConfiguration peerProviderConf = new FactoryConfiguration()

                                 .className("net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory")

                                 .properties("peerDiscovery=automatic, multicastGroupAddress=221.159.32.09, multicastGroupPort=4446, timeToLive=32");

                    Configuration config = new Configuration();

                    config.addCacheManagerPeerListenerFactory(peerListenerConf);

                    config.addCacheManagerPeerProviderFactory(peerProviderConf);

 

Tiens, il y a du RMI ! 10 secondes de pub  <DEBUT DE LA PUB> Besoin de formation JAVA RMI ? voir la page Consultingit <FIN DE LA PUB>

LB_CACHE c’est le nom du cache. Ensuite 2 choses sont très intéressantes :

 

  1. FactoryConfiguration peerListenerCong ligne 81
  2. Ligne 87, multicastGroupAdress , cache autonome

 

Lorsqu’un cache est créé un système de découverte multicast est mis en place. Quand on va lancer les process, si chacun instancie localement un cache avec RMICacjeManagerPEerProviderFactory, on aura une adresse IP et un port pour découvrir tous les autres caches sur le réseau.

Je remarque que le timeToLive=32, c’est 32 secondes ! Comme le temps pendant lequel le programme « pollue » le réseau (d’après la demande de l’ingénieur réseau). Découverte pendant les 32 premières secondes. Si il y avait timeToLive=32 il y aurait fait des découvertes à l’infini. Voilà donc pourquoi, pendant 32 secondes, l’ingénieur réseau remarque beaucoup de requêtes multicast (trop pour lui, mais normales pour moi).

 

Besoin d'une revue de code? Remplissez ce formulaire: