Séminaire "Expertise Tomcat" : compte-rendu

Mardi 21 janvier se tenait un séminaire d'une demi-journée organisé par SpringSource, sur les différents aspects de l'utilisation de Tomcat en production : déploiement, performance, débuggage et monitoring.
Pour l'occasion, SpringSource avait mis les petits plats dans les grands : récéption dans un grand hôtel à la Défense avec vue panoramique sur Paris et repas gastronomique, et Filip Hanik sur la scène, un des principaux committers sur Tomcat.

Large scale Tomcat deployment

Pour cette première séance, Filip a montré comment déployer plusieurs instances de Tomcat de manière simple et flexible. Il suffit pour cela de comprendre la différence entre les variables d'environnement CATALINA_HOME et CATALINA_BASE, et le mécanisme de lancement de Tomcat.

Plus, c'est mieux

Filip Hanik Mais tout d'abord, pourquoi voudrait-on lancer plusieurs instances de Tomcat sur une machine ?
Selon Filip, il est plus intéressant d'héberger chaque application sur une instance dédiée :

  • Pour éviter que les fuites mémoire d'une application ne contaminent les autres
  • Parce que les ressources consommées et le temps de lancement additionnels sont négligeables
  • Pour définir des paramètres distincts par application (réglages mémoire, répertoire de log, port d'écoute...)
  • Parce qu'il vaut mieux lancer deux JVM avec 2Go de Heap qu'une seul avec 4Go, à cause du garbage collector.

La mystérieuse histoire de CATALINA

Si l'on connaît bien la variable d'environnement CATALINA_HOME, qui est décrite dans la documentation, CATALINA_BASE est en revanche généralement méconnue. Leur combinaison est pourtant très utile :

  • CATALINA_HOME représente le répertoire d'installation de Tomcat, contenant les éléments communs aux instances (server.xml, webapps...). Pour une installation mono-instance, il s'agit du répertoire obtenu par décompression de l'archive d'installation ; mais il est également possible de séparer les binaires (spécifiques à chaque version) de la configuration, comme dans l'exemple ci-dessous.
  • CATALINA_BASE pointe sur un répertoire contenant la configuration spécifique d'une instance de Tomcat. Toutes les valeurs spécifiées ici remplacent celles par défaut (nous verrons comment plus loin).

Exemple de configuration :

/opt/tomcat/run.sh
/opt/tomcat/tomcat-6.0.18 
  +-- bin : startup.sh, shutdown.sh
  +-- shared
  +-- lib
/opt/tomcat/shared (CATALINA_HOME)
  +-- conf : logging.properties, server.xml, tomcat-users.xml...
  +-- webapps
/opt/tomcat/instance1 (CATALINA_BASE)
  +-- bin : setenv.sh, setclasspath.sh
  +-- conf : catalina.properties
  +-- logs
  +-- work
/opt/tomcat/instance2 (CATALINA_BASE)
  +-- bin : setenv.sh, setclasspath.sh
  +-- conf : catalina.properties
  +-- logs
  +-- work

La gestion (lancement/arrêt) des instances est confiée au script run.sh. En fonction du paramètre "numéro d'instance" qui lui est passé, il positionne la variable d'environnement CATALINA_BASE puis appelle les scripts standards de Tomcat. On peut ainsi mettre à jour les binaires de Tomcat sans pour autant toucher à sa configuration, et ajouter ou supprimer des instances facilement.

Bien démarrer son tigre, mode d'emploi

Voyons maintenant le processus de lancement de Tomcat.

Dans le répertoire bin, se trouvent les scripts startup.sh et shutdown.sh, qui délèguent le travail à catalina.sh. Après avoir détecté son environnement d'exécution (AS400, Cygwin, VMWare...), celui-ci recherche et exécute les scripts setclasspath.sh et setenv.sh dans CATALINA_BASE/bin s'ils existent, sinon dans CATALINA_HOME/bin. C'est là l'occasion de redéfinir, globalement ou par instance, des variables comme JAVA_HOME, JAVA_OPTS ou CATALINA_OPTS.

Au démarrage, Tomcat lit sa configuration est lue depuis les fichiers server.xml, logging.properties, etc. Chaque instance possédant ses propres paramètres (port d'écoute, répertoire de log...), il pourrait être tentant de copier et modifier ces fichiers manuellement.
Mais Tomcat propose une solution plus élégante : les placeholders, ou variables de remplacement. Il est alors possible de créer un modèle universel de configuration possédant des portions dynamiques de la forme "${variable}", dont les valeurs spécifiques sont redéfinies au niveau de chaque instance.
Dans notre exemple d'installation, les fichiers de configuration placés dans CATALINA_HOME/conf sont les modèles, et les valeur spécifiques sont définies dans les fichiers CATALINA_BASE/conf/catalina.properties des instances.

Performance Tuning : Harder, Better, Faster, Stronger

En introduction de cette séance, Filip a surpris tout le monde en indiquant qu'il ne voyait pas l'intérêt de la réplication d'état entre serveurs.
Son raisonnement est simple : si vous avez une architecture simple qui fonctionne à 99.99% du temps, il est stupide de mettre en place des systèmes complexes et coûteux pour espérer gérer les 0.01% restants. Au pire, si un serveur montre des signes de faiblesse, il est possible de le drainer avec un load-balancer HTTP, puis de le retirer du cluster sans perdre de requêtes...

Maintenant que plusieurs instances sont lancées, voyons comment les optimiser.

Le processus

Comme toute tentative d'optimisation, le processus est simple :

  1. Définir un objectif quantifiable
  2. Mesurer l'existant
  3. Améliorer
  4. Mesurer l'amélioration
  5. Retour au point 2

Pour l'amélioration, le hardware étant nettement moins cher que les compétences informatiques, il faut toujours tenter en premier d'ajouter de la puissance CPU et/ou de la RAM, et vérifier si cela suffit à atteindre les objectifs fixés. Aujourd'hui, un développeur expérimenté coûte environ 600€/jour ; au DSI de choisir entre une semaine d'optimisation manuelle aux résultats incertains et un nouveau serveur...

Quelques pistes d'optimisation

Plus de 90% du temps de réponse à une requête étant consommés par les applications (accès à une base de données, application de règles métier, préparation des vues...), il est impératif d'optimiser celles-ci avant de commencer à modifier Tomcat.
Voici tout de même les pistes d'optimisation les plus courantes.

Logging

Comme l'avait indiqué Mark Thomas à l'occasion des Rencontres Spring, les logs sont souvent mal configurés. Tout d'abord, supprimez le logger "console" (dans logging.properties), car il est synchrone et ne fait que dupliquer les informations déjà présentes dans les logs des applications. Ensuite, définissez une politique de rotation des fichiers, par jour ou par taille.

Connecteur

Le connecteur utilisé a également son importance. Trois connecteurs existent :

  • BIO : Standard, le plus ancien. Synchrone, stable mais gère SSL via JSSE qui est lent.
  • APR : Natif, asynchrone, gère SSL via OpenSSL qui est rapide.
  • NIO : Utilise java.nio. Asynchrone, mais moins stable et utilise également JSSE.

Le choix de l'un ou l'autre dépend donc :

  1. de la configuration réseau : les load-balancers opèrent-ils couche 4 (TCP) ou couche 7 (HTTP) ?
  2. de la sécurisation HTTPS dans l'application.
  3. de la configuration du paramètre "keep-alive" des connexions.
  4. du niveau de stabilité souhaité

Récapitulatif :

But                        Bon   Moyen Mauvais
Stabilité                  BIO   APR   NIO
SSL                        APR   NIO   BIO
Faible charge              BIO   APR   NIO
Forte charge - Keep-Alive  BIO   APR   NIO
Forte charge + Keep-Alive  APR   NIO   BIO
Ressources statiques

Il est souvent intéressant de placer des serveur Apache HTTPD devant les serveurs d'applications, car ils sont généralement plus performants pour servir les ressources statiques (images, scripts javascript...). Mais dans le cas où ces ressources sont de taille modeste (c'est-à-dire inférieure à la taille du buffer de réponse de Tomcat), Tomcat offre des performances équivalentes.
Il peut également être intéressant de jouer sur la taille du cache des données statiques, et sur leur délai de revalidation.

Taille du buffer de réponse

Ce buffer (socketBuffer) joue un rôle particulièrement important dans les performances de Tomcat. Selon Filip, les gains de performance réalisés en déterminant sa taille optimale peuvent représenter jusqu'à 80% des gains totaux réalisés lors du procesus d'optimisation !

Autres réglages

Quelques autres pistes d'optimisation :

  • Nombre de threads (maxThreads) : 400 est une bonne première approximation. Si le CPU encaisse la charge, augmentez la valeur.
  • Nombre de connexions maintenues (maxKeepAlive) : mettre -1 (pas de keep-alive) en l'absence de SSL, si la charge est forte, ou si les load-balancers opèrent sur la couche 4 (TCP) ; sinon, une première valeur entre 100 et 200 paraît raisonnable.
  • Timeout (connectionTimeout): la valeur par défaut (20s) était adéquate pour les connexions RTC, mais en ces temps de haut débit, elle peut être dsecendue à 4s sans problème.

Troubleshooting in production

Pour cette troisième séance, Filip a démontré comment on pouvait diagnostiquer les problèmes en production, sans arrêter les serveurs.

La technique consiste à générer et analyser des thread dumps, avec :

  • kill -3 <pid>
  • jstack -l

Filip a présenté trois cas pratiques :

  • Un deadlock, repéré en suivant les locks obtenus par les différents threads.
  • Un cas de lenteur applicative résultant de la création de trop nombreux objets temporaires : la zone "young" de la mémoire étant pleine, le garbage collector passait plus de 100 fois par seconde, laissant ainsi peu de CPU pour l'application.
  • Un cas de paralysie totale due à une surcharge d'objets non temporaires. Tous les threads étaient bloqués sur des opérations de création d'objet (<init>), et le garbage collector ne parvenait même plus à libérer de la mémoire, mais ne déclenchait pas d'OutOfMemoryError pour autant. Dans ce cas de figure, l'option "-XX:+UseGCOverheadLimit" permet de brider un peu le garbage collector et de récupérer du CPU pour l'application (et la JVM).

La démonstration étant très visuelle et interactive, je ne peux vous en faire un compte-rendu plus détaillé... Mais c'était tout de même assez impressionnant. L'analyse des thread dumps est définitivement une compétence intéressante à acquérir.

Enterprise capabilities

Pour finir, SpringSource TC Server nous a été présenté.
Il s'agit tout simplement d'un Tomcat auquel ont été rajoutées des capacités de gestion et de supervision. Contrairement à SpringSource DM Server, il s'agit d'une version "normale" de Tomcat (la dernière version stable), vos applications ne nécessitent donc aucune modification. De plus, sa configuration est pré-optimisée : logging asynchrone, timeouts, etc.

TC Server collecte en permanence des statistiques comme les temps de réponse, le nombre de connexions réussies ou ratées aux bases de données, les passages du garbage collector... Ces informations sont inestimables en cas de problème, car elles en donnent le contexte sans avoir besoin de reproduire l'erreur.

Enfin, une console d'administration impressionnante, véritable centre de commande, permet de superviser tout un cluster, d'en modifier la configuration (et revenir à la dernière version stable connue si nécessaire), de programmer des déploiements...

Pour finir, un officiel Spring a indiqué que TC Server passerait en version beta dans les semaines à venir, et que le coût du support serait de 500 à 750 € par CPU physique, ce qui est plus que raisonnable au regard du racket de la politique d'IBM par exemple.

Conclusion et Photos

Ce séminaire fut très instructif. Tomcat, le "petit" serveur, possède des ressources insoupçonnées, et son grand frère TC Server pourrait bien être le best-seller de 2009, si la crise se poursuit et pousse les DSI à envisager des solutions alternatives aux grands éditeurs logiciels.

Vous pouvez télécharger tous les slides et démonstrations.

La salle.jpg
Panoramique Paris


Commentaires

1. Le lundi 26 janvier 2009, 10:45 par Stéphan

Plusieurs instances de Tomcat sans ce prendre la tête et de manière propre. Génial!

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.