Load balancing avec JBoss et Apache 2

Les applications réparties étant de plus en plus diffusées auprès des partenaires, des clients et des collaborateurs, elles se doivent de tenir des charges toujours plus importantes. Le seul matériel ne peut répondre à un coût raisonnable. C'est là qu'intervient le "load balancing" en répartissant la charge entre plusieurs serveurs plus modestes. Ce tutoriel doit vous amener à entrevoir ce que ces solutions peuvent vous apporter dans un cas très concret.

17 commentaires Donner une note à l'article (4.5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Suite à la formation JBoss plusieurs voies d'approfondissement s'offrent à moi mais il y en a une qui me tient plus à coeur : le load balancing. Pourquoi cela? Après tout en tant qu'Architecte logiciel certains considèrent que notre problématique est de savoir que cela existe. La technique restant le domaine de prédilections des administrateurs et architectes SI. Personnellement ce n'est pas ma conception. Même si je ne prétends pas avoir les mêmes compétences, j'aime savoir de quoi je parle.

Image non disponible

Tout d'abord une petite définition du load balancing : le « load balancing » ou « répartition de charge » est une technique utilisée en informatique pour distribuer un travail entre plusieurs processus, ordinateurs, disques ou autres ressources ( source wikipedia ).

Le load balancing peut se faire de différentes manières :

  • matériel : solution permettant de résister à la plus forte charge. Elle aura des problèmes en cas de répartition sur un protocole haut niveau utilisant les sessions. Cette solution a un coût élevé.
  • logiciel : cette solution permet de gérer facilement des répartitions de charge avec respect de session et a un coût faible. Plus lent que le hard, elle dépend du serveur qui l'héberge pour définir ses performances.

Pour ce tutoriel, nous allons nous intéresser uniquement à la partie logicielle qui est plus simple à mettre en oeuvre. Nous utiliserons pour ce faire la configuration la plus courante dans le monde Java :

  • deux serveurs d'applications JBoss ( avec des configurations différentes mais une application commune. Habituellement les deux serveurs seront totalement iso ),
  • un serveur Apache 2 sur lequel nous activerons le module mod_jk.

II. Préparation

Je vous conseille de commencer par créer un répertoire où vous installerez les différents serveurs nécessaires.

II-A. Installation JBoss

Pour ce tutoriel, j'ai utilisé une version 6.0M4 de JBoss que vous pouvez télécharger sur le site officiel de JBoss http://jboss.org/jbossas/downloads/ ( La version actuellement la plus diffusée étant la 5.1, vous trouverez dans des encadrés les différences par rapport à la version 6 actuellement disponible ).

Une fois l'archive obtenue, decompilez la dans le répertoire précédemment créé. Vous devez alors avoir un répertoire « jboss-6.0.0.xxxxxxxx-M4 » qui contient un répertoire bin. Dans ce répertoire bin, double cliquez sur le fichier run.bat sous windows ( sous unix, lancez le run.sh ).

Si tout se passe bien, vous ne devez pas avoir d'erreur dans les traces et la dernière ligne doit se finir par :

Started in 58s:376ms

Si ce n'est pas le cas, reportez-vous à la documentation très complète de JBoss(1).

Une fois cette étape franchie, il nous reste à dupliquer l'installation pour avoir un deuxième serveur JBoss.

Pour ce faire, arrêtez le serveur en cours ( fermez la fenêtre de commande ), puis recopiez entièrement le répertoire « jboss-6.0.0.xxxxxxxx-M4 » en un répertoire « jboss-6.0.0.xxxxxxxx-M4 -server2 ».

Si maintenant vous lancez les deux serveurs, vous allez avoir une erreur du style :

java.lang.Exception: Port 8083 already in use

Cette erreur est normale ! Effectivement, vous lancez en réalité deux serveurs avec exactement la même configuration. Il va nous falloir changer les ports d'écoutes sur le deuxième serveur. Pour faciliter l'opération et éviter toute erreur nous ajouterons 100 à tous les numéros de ports trouvés.

A cette fin dans « jboss-6.0.0.xxxxxxxx-M4-server2/server/deploy/(2) » supprimez tous les répertoires sauf «jbossweb-standalone». Renommez «jbossweb-standalone» en « default ». Suite à cela cherchez le fichier « jboss-6.0.0.xxxxxxxx-M4-server2/server/default/conf/bindingservice.beans/META-INF/bindings-jboss-beans.xml » et incrémentez tout les numéros de port de 100.

Relancez les deux serveurs et ouvrez un navigateur. Vous avez maintenant accès aux urls :

http://localhost:8080

http://localhost:8180

En version 5.x de JBoss, il faudra garder et renommer le server «  web  » en «  default  », puis incrémenter tout les numéros de port dans le fichier «  jboss-5.1.0.GA-server2/server/default/deploy/jbossweb.sar/server.xml  »

Accédez, via la console JMX, à chacune des urls précédentes, afin de vérifier le bon fonctionnement des serveurs.

Image non disponible

Pour accéder aux consoles avec la version 5.X de JBoss utiliser le compte admin / admin.

Nous avons donc maintenant deux serveurs d'applications actifs. Il reste à mettre en place notre load balancer, dans notre cas un serveur web.

II-B. Installation d'Apache

Apache s'avèrera plus simple à installer vu que nous n'avons besoin que d'un serveur de ce type. Pour des problèmes de compatibilité avec le module mod_jk que nous allons utiliser, je vous conseille de télécharger la dernière version disponible d'Apache 2.0 ( dans mon cas la 2.0.63 ) sur le site d'Apache http://httpd.apache.org/download.cgi.

Exécutez l'installation et vérifiez en lançant le serveur que tout s'est bien passé.

Remarque 1 : La relance du serveur apache se fait :

Dans votre navigateur, accédez alors à l'url , http://localhost/ , vous devez avoir un écran comme celui-ci:

Image non disponible

Remarque2 : Attention sous Windows! Il vous faudra désactiver le serveur IIS qui utilise le port 80 sinon Apache ne pourra se lancer correctement. Pour ce faire aller dans « menu démarrer>panneau de configuration>outils d'administration » et lancer le « gestionnaire de services internet » ; là, vous aurez tout le loisir d'arrêter complétement IIS. Relancer à nouveau le serveur Apache et tout devrait être bon.

Nous avons maintenant 3 serveurs indépendants et nous allons pouvoir entrer dans le coeur du sujet en commençant par une simple répartition de charge.

III. Configuration du load balancer avec le module mod_jk

Nous allons commencer par ajouter le module nécessaire à Apache afin de dialoguer avec les serveurs d'applications. Nous concernant, il existe plusieurs modules pour se connecter à JBoss :

  • mod_proxy : module officiel livré avec Apache, il existe depuis 1996 et a été entierement réécrit pour Apache 2.2 afin de concurencer les autres modules.
  • mod_cluster : fourni par JBoss, c'est une solution jeune mais très prometteuse. Configurable dynamiquement, tenant compte de la charge des serveurs et capable de gérer l'arrêt des applications en douceur.
  • mod_jk : longtemps seul module professionnel, il est encore le module recommandé avec tomcat. Comme mod_cluster, il permet la modification à chaud par page web de la configuration.

Nous utiliserons mod_jk qui est le plus répandu. Pour ce faire, rendez-vous sur http://tomcat.apache.org/download-connectors.cgi et téléchargez la version binaire qui correspond à votre plateforme. Copier le fichier « mod_jk***.so » dans le répertoire « apache2/modules » et renommer le en « mod_jk.so ».

Ici nous ne toucherons pas aux serveurs d'applications ceux ci étant considérés comme fonctionnels et à l'écoute de connexions avec le protocole AJP13.

Nous allons commencer par déclarer ces serveurs d'applications à notre load balancer ( Apache 2 ). Pour ce faire rendez vous dans « apache2/conf/ » et créez un fichier « workers.properties » dans lequel nous allons inscrire les serveurs de la manière suivante :

Workers.properties
# Define 1 real worker using ajp13
worker.list=loadbalancer,status
# Set properties for worker1 (ajp13)
worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=8009
worker.worker1.lbfactor=1
worker.worker1.connection_pool_size=10
# Set properties for worker2 (ajp13)
worker.worker2.type=ajp13
worker.worker2.host=localhost
worker.worker2.port=8109
worker.worker2.lbfactor=1
worker.worker2.connection_pool_size=10
#fonctionnement de l'equilibrage de charge
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=worker1,worker2
worker.loadbalancer.sticky_session=True
worker.status.type=status


Si on analyse ce fichier, on peut voir plusieurs zones :

  • une zone bleue qui nous permet de définir les workers (3) qui seront visibles de l'extérieur. Ici le seul à nous intéresser est « loadbalancer ».
  • En vert et rouge, nous voyons la définition de connexion avec les deux serveurs d'applications. On voit que les deux connexions sont de type AJP13 ( standard de communication Apache-Tomcat ). Les deux serveurs sont hébergés sur la machine d'où le localhost, par contre on peut voir que les ports de connexions sont différents.
    Ici nous avons réparti de manière égale les requêtes entre les serveurs car nous avons mis le même lbfactor. On voit aussi que l'on prévoit une dizaine de connexions par serveurs qui seront ouvertes au fur et à mesure.
  • En orange, enfin le vrai loadbalancer. On le reconnaît au « type=lb ». Ensuite on indique tous les serveurs à prendre en compte. Enfin la propriété « sticky_session » indique si le load balancing se fait avec affinité de session.

Remarque 3 : L'affinité de session indique que si une requête est retransmises sur le serveur 1 pour un client toutes les autres requêtes appartenant à la même session de ce client seront retransmises sur le serveur 1.

Il nous suffit maintenant de déclarer l'utilisation du module mod_jk à Apache ainsi que le fichier que nous venons de créer.

Pour cela nous allons modifier le fichier httpd.conf du même répertoire de la manière suivante :

Httpd.conf
...
LoadModule jk_module modules/mod_jk.so
...
# Where to find workers.properties
# Update this path to match your conf directory location (put workers.properties next to httpd.conf)
JkWorkersFile conf/workers.properties
# Where to put jk shared memory
# Update this path to match your local state directory or logs directory
JkShmFile logs/mod_jk.shm
# Where to put jk logs
# Update this path to match your logs directory location (put mod_jk.log next to access_log)
JkLogFile logs/mod_jk.log
# Set the jk log level [debug/error/info]
JkLogLevel info
# Select the timestamp log format
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
JkMount /* loadbalancer
<IfModule worker.c>
StartServers2
MaxClients150
MinSpareThreads25
MaxSpareThreads75
ThreadsPerChild25
MaxRequestsPerChild0
</IfModule>


La ligne bleue permet d'ajouter mod_jk à la liste des modules chargés. En orange, nous indiquons le fichier de configuration à prendre en compte. En rouge, les lignes de configurations de sortie du module, à ne pas toucher.

La ligne verte nous intéresse plus, elle permet d'indiquer quelles URL nous redirigeons et vers qui.

Ici toutes les requêtes sont redirigées sur le worker loadbalancer. Elles seront donc, aux vues de notre configuration, réparties équitablement entre les deux serveurs.

Il vous reste un dernier fichier à ajouter dans le répertoire conf pour faire un lien entre le worker status et une url. Cette page status permettra d'afficher l'état du connecteur mod_jk. Ceci n'est en rien obligatoire mais conseillé en mode de développement/Pré-production.

Uriworkermap.properties
/status=status


A présent c'est le moment, relancez le serveur Apache pour prendre en compte cette nouvelle configuration ( voir remarque 1 ).

Maintenant, vérifions le bon fonctionnement de notre configuration. Pour ce faire, ouvrez un navigateur et connectez vous sur http://localhost/, vous devez maintenant voir la page d'accueil de JBoss et non plus celle d'Apache. Entrez sur la console JMX comme au début et cliquez sur « JBoss.ws » à gauche puis sur « service=ServerConfig ». Dans la fenêtre ainsi apparue vous voyez le port d'écoute du serveur : 8080 ou 8180.

Image non disponible

Ouvrez alors une nouvelle fenêtre avec un autre navigateur ( si vous travaillez avec le même navigateur vous aurez la même session et donc accès au même serveur grâce à l'affinité de session ). En reproduisant la même procédure, vous verrez que le serveur sera en écoute sur l'autre port.

IV. Continuité de service

Maintenant que nous sommes capables de repartir la charge d'un site entre plusieurs machines, on peut se demander ce qui se passerait si un des serveur venait à ne plus fonctionner. Et bien horreur rien du tout ! La communication étant rompue une erreur parviendra au client ce qui vous en conviendrez n'est pas du meilleur effet.

Pour répondre à cela tout a été prévu. On peut ainsi, au cas où Apache détecterai que le serveur auquel il tente d'accéder n'existe plus, lui dire de rediriger les requêtes vers un autre serveur.

Image non disponible Alors comment cela se passe-t'il?

Et bien au moment où Apache reçoit une requête, il détermine le serveur qui doit répondre :
  -  Soit une connexion libre est ouverte vers celui ci et la requête est lancée,
  -  Soit aucune connexion n'est disponible, Apache va alors pinger la machine pour savoir si elle existe ou non. Si le ping échoue, Apache regarde s'il existe une directive de redirection. Si c'est le cas, il va tenter de communiquer avec le serveur de remplacement.

Dans le cas qui nous intéresse, le serveur 1 étant en panne, Apache essaie d'ouvrir une connexion sur le serveur 2. Comme la connexion peut être créée alors la requête est émise vers ce nouveau serveur.
   

Pour ce faire, nous allons ajouter une ligne au worker1 dans notre fichier workers.properties.

Workers.properties
...
# Set properties for worker1 (ajp13)
worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=8009
worker.worker1.lbfactor=1
worker.worker1.connection_pool_size=10
# Define preferred failover node for worker1
worker.worker1.redirect=worker2
...


Nous venons d'indiquer que si le serveur worker1 n'est plus accessible les requêtes sont redirigées sur le worker2.

Pour tester cette configuration, arrêtez le serveur1 et retentez d'accéder depuis les deux navigateurs. L'un des deux navigateurs va afficher une erreur comme suit.

Image non disponible

Relancez Apache pour qu'il prenne en compte la nouvelle configuration et refaite la manipulation. Maintenant, si vous vérifier dans la console JMX les deux navigateurs accèdent au même serveur.

Si nous nous contentons de cela, nous redirigeons toute la charge vers un serveur avec le risque qu'il ne puisse répondre à l'ensemble de la charge. Pour ce faire, on préfère ajouter un worker comme les autres mais que l'on met en attente. Ainsi, il ne recevra de requêtes que si l'un des serveurs tombe.

Workers.properties
...
# Set properties for worker2 (ajp13)
worker.worker3.type=ajp13
worker.worker3.host=localhost
worker.worker3.port=8209
worker.worker3.lbfactor=1
worker.worker3.connection_pool_size=10
# Disable worker3 for all requests except failover
worker.worker3.activation=disabled
...


Ajoutez le worker3 dans la propriété worker.loadbalancer.balance_workers pour le rendre disponible et bien se rappeler qu'un worker disabled est un worker qui ne reçoit pas de requête directement ; ainsi si vous définissez uniquement 2 workers et que l'un est disabled toutes les requêtes iront sur l'autre serveur.

Encore quelques optimisations et nous voici avec un site prêt à répondre à la charge d'une situation réelle.

V. Optimisation annexe

Tout à l'heure nous avions vu une zone d'optimisation des connexions entre Apache et JBoss, il est temps de mieux comprendre à quoi elle sert(4).

Httpd.conf
...
<IfModule worker.c>
StartServers2
MaxClients150
MinSpareThreads25
MaxSpareThreads75
ThreadsPerChild25
MaxRequestsPerChild0
</IfModule>


Cette zone permet de définir le nombre de requêtes qui peuvent être acceptées en parallèle ainsi que le nombre de requêtes pouvant être mises en attente. Ainsi nous indiquons que 2 processus serveurs sont créés au démarrage d'Apache, il n'influe que peu car Apache va au fur et à mesure des requêtes arrêter et lancer de nouveaux processus serveurs. On indique aussi que l'on pourra avoir au maximum 150 clients simultanément.

Remarque 4 : Il faut normalement compter 20Mo par client actif, on peut donc à partir de la mémoire libre définir la valeur de MaxClients à appliquer. Ainsi pour 1Go, on pourra accepter 50 clients parallèles.

Les valeurs MinSpareThreads et MaxSpareThreads permettent d'entretenir des processus serveur de secours en fonction du nombre de requêtes.

Remarque 5 : Les valeurs MinSpareThreads et MaxSpareThreads doivent être en cohérence avec les valeurs présentes sur les serveurs d'applications.

ThreadsPerChild définit le nombre de threads au sein de chaque processus enfant.

La propriété MaxRequestPerChild est ici désactivée, elle permet de limiter le nombre de requêtes acceptées par processus serveur. Si ce maximum est atteint le processus serveur est cloné puis arrêté.

VI. Conclusion

Il ne reste plus qu'à appliquer ce que nous avons vu sur un projet concret et se frotter au réglage plus fin du serveur. Mais là, je ne me fait pas de soucis la documentation existe en nombre, particulièrement pour JBoss et Apache.

VII. Remerciement

Je tiens à remercier mon entourage pour leurs patiences, Christophe pour avoir pris le temps de lire la béta. Merci à l'équipe de Developpez et plus particulièrement à Kerod pour m'avoir aiguillé, longbeach et ra77 pour vos nombreuses remarques et littledaem pour sa relecture bien utile !

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2010 noel perez. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.