Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |
|||||||
Niveau : | Avancé |
Le clustering est un terme qui désigne une solution technique dont le but est d'améliorer la disponibilité (fail-over) et/ou la montée en charge (load-balancing) d'une application. Concrètement cela se traduit par un groupement de serveurs qui sont vus comme un seul serveur logique. Cette redondance peut être utilisée pour :
Toutes les solutions de clustering sont propriétaires puisqu'aucune des plates-formes Java (même Java EE) ne propose de spécifications relatives au clustering.
Pour permettre une meilleure montée en charge et un meilleur taux de disponibilité, les applications Java sont de plus en plus réparties sur différentes JVM.
Terracotta est une solution open source puissante de clustering au niveau de la JVM qui permet de facilement mettre en clusters une application dans plusieurs JVM. Ceci permet à une application d'être exécutée dans plusieurs JVM, Terracotta prenant en charge de façon transparente les interactions entre ces JVM pour faire en sorte que l'application s'exécute comme dans une seule JVM.
Terracotta fournit un environnement d'exécution qui facilite grandement la mise en clusters d'une application Java en proposant cette mise en clusters au niveau de la JVM plutôt qu'au niveau de l'application.
Le coeur de Terracotta est composé de deux parties :
Terracotta agit directement sur les JVM pour maintenir l'état des objets gérés dans ces différentes JVM notamment en permettant entre autres :
L'état des objets gérés est conservé dans le serveur (ou un ensemble de serveurs) qui est une application Java donc exécutée dans sa propre JVM.
Terracotta permet à une application exécutée dans plusieurs JVM d'accéder à des objets partagés dans une mémoire virtuelle.
Terracotta permet le partage de graphes d'objets entre plusieurs JVM sans avoir recours à une API dédiée : pour cela, Terracotta manipule dynamiquement le bytecode de l'application ce qui évite d'avoir à mettre en oeuvre une API particulière dans le code de l'application. Il n'est donc pas utile de mettre en oeuvre des API dédiées à l'échange de données comme des sockets, RMI, JMS, des services web, ...
L'accès aux données se fait de façon transparente : Terracotta s'occupe de stocker les données sur le serveur, de les copier localement lors de leur utilisation et de maintenir leur état sur le serveur et sur les clients.
Côté client, un classloader particulier instrumente les objets dans la JVM pour assurer le dialogue avec le serveur. Ainsi, si l'application est prévue pour fonctionner en multithread dans une JVM, elle pourra bénéficier de sa mise en cluster grâce à Terracotta sans altération de son code source.
Les échanges entre les JVM et le serveur sont particulièrement optimisés : ils ne reposent pas sur le transfert complet des objets sérialisés mais utilisent un mécanisme propre à Terracotta qui réduit les échanges au minimum.
Le fichier de configuration tc-config.xml permet de configurer la mise en oeuvre de Terracotta.
Terracotta est un projet open source pour lequel une version commerciale permet d'avoir du support et des fonctionnalités avancées.
Le site web officiel est à l'url : https://www.terracotta.org/
La version de Terracotta utilisée dans ce chapitre est la 3.2.
Ce chapitre contient plusieurs sections :
Terracotta permet à une application d'être déployée sur plusieurs JVM et de s'exécuter comme si elle l'était sur une seule. Terracotta permet d'appliquer le modèle de gestion de la mémoire d'une JVM dans plusieurs JVM qui peuvent être sur différentes machines.
En pratique, pour le développeur, Terracotta permet de voir un cluster de JVM comme une unique JVM : il est par exemple possible d'utiliser un singleton dans le cluster sans code supplémentaire.
Terracotta permet le partage d'objets et la coordination de threads entre différentes JVM.
La gestion des accès concurrents est assurée simplement en utilisant les mécanismes fournis par Java (moniteur avec le mot clé synchronized, utilisation de la bibliothèque java.util.concurrent, ...).
Terracotta prend aussi en charge des fonctionnalités Java de base au travers du cluster comme la gestion des accès concurrents (synchronized), la gestion de la mémoire (ramasse-miettes), la gestion des threads (wait(), notify(), ...). Terracotta prend en charge ces mécanismes de façon distribuée sous réserve qu'ils soient mis en oeuvre dans l'application par le développeur.
Les mécanismes de sérialisation, très coûteux, ne sont pas utilisés et les modifications ne sont pas broadcastées à tous les noeuds du cluster mais simplement à ceux qui en ont besoin : ceci permet de maximiser les performances des échanges réseaux réalisés par Terracotta.
Les objets partagés dans le cluster sont nommés Distributed Shared Objects (DSO). Ces objets sont vus comme des objets standards dans le heap de chaque client mais c'est Terracotta qui se charge de la gestion de ces objets de façon transparente grâce à l'instrumentation des objets gérés et de ceux qui les utilisent.
Terracotta met en oeuvre un ramasse-miettes distribué (DGC : Distributed Garbage Collector) qui s'assure avant de récupérer la mémoire d'un objet que celui-ci n'a plus de référence sur le serveur mais aussi sur chacun des clients.
Les solutions de mise en oeuvre de clusters d'applications Java repose généralement sur la sérialisation des objets modifiés pour les répliquer dans les différentes JVM. Ce type de solution est coûteux du fait même de l'utilisation de la sérialisation :
Terracotta propose une solution alternative qui réduit ces coûts en n'échangeant que les modifications faites sur un objet. Il utilise son propre mécanisme natif pour l'échange des données : il n'est donc pas utile que les objets gérés soient sérialisables. L'état d'un objet est stocké sur un serveur et les clients ne reçoivent les modifications que lorsque l'objet est utilisé en local. Terracotta se charge alors de rafraichir l'objet à partir du serveur de façon transparente. Les échanges réseaux sont ainsi réduits.
Terracotta met en oeuvre le principe de Network Attached Memory (NAM). Ceci permet d'avoir une sorte de super JVM qui coordonne les JVM clientes du cluster. Comme certains objets et les threads sont partagés et synchronisés, les clients du cluster sont vus comme s'il ne s'agissait que d'une seule JVM.
La définition des objets gérés par Terracotta se fait dans un fichier de configuration au format XML : cette définition peut se faire de façon très fine et évite d'avoir à partager tous les objets de chaque JVM cliente.
Le bytecode de certaines classes de l'application est enrichi au chargement des classes : ceci permet une instrumentation des classes grâce à un classloader dédié.
La bibliothèque Terracotta analyse chaque classe chargée définie dans le fichier de configuration et injecte du bytecode au besoin pour permettre de dialoguer avec le serveur et d'effectuer les traitements que le serveur impose (mise à jour de l'état d'un ou plusieurs objets, pose ou libération de verrous sur les moniteurs, synchronisation des threads, ...).
L'instrumentation du bytecode permet donc entre autres de capturer les modifications faites sur un objet et de les envoyer au serveur. Ces modifications ne seront envoyées aux autres noeuds que lorsque ceux-ci auront besoin d'accéder à l'objet. Ainsi tous les noeuds ont accès aux objets mais ceux-ci ne sont mis à jour dans la JVM locale que si nécessaire.
Terracotta assure la cohérence d'une donnée gérée au travers du cluster.
Les objets gérés sont stockés dans le serveur Terracotta. Terracotta définit une racine pour un graphe d'objets qui sont partagés. Cette racine est identifiée par un champ nommé root dans le fichier de configuration.
Lorsqu'un objet racine est instancié, lui-même et toutes ses dépendances sont partagées par Terracotta. Toutes les données de ces objets sont stockées sur le serveur Terracotta.
Lors d'une modification sur l'état d'un objet géré, celle-ci est automatiquement répercutée au besoin dans les autres noeuds du cluster par le serveur Terracotta.
Terracotta utilise la notion de transaction pour gérer les accès concurrents aux objets gérés dans le cluster. Cette gestion repose sur les mêmes mécanismes que ceux utilisés dans une JVM mais de façon distribuée entre les noeuds du cluster : cette distribution se fait par l'intermédiaire du serveur.
Ainsi les méthodes synchronized, les blocs synchronized et les méthodes déclarées locked dans le fichier de configuration de Terracotta sont utilisés comme délimiteurs pour ces transactions.
A la fin de la transaction, les modifications sont envoyées au serveur pour les diffuser aux autres noeuds du cluster.
Un objet géré par le cluster vit dans la JVM d'un noeud de la même façon qu'un objet non géré sauf que son état est synchronisé par le serveur :
Chaque noeud possède sa propre instance d'un objet géré par le cluster dont l'état est synchronisé grâce au serveur du cluster.
Aucune API liée à Terracotta n'est à mettre en oeuvre dans le code pour faire fonctionner le cluster. Cependant, certaines modifications dans le code sont parfois nécessaires notamment pour améliorer les performances : ces modifications ne sont pas directement liées à Terracotta mais à la bonne mise en oeuvre d'une programmation concurrente. La transparence de Terracotta est possible grâce à l'instrumentation du bytecode des classes à leur chargement dans la JVM.
Pour le développeur, la seule préoccupation est de s'assurer de la bonne gestion des accès concurrents sur les objets gérés.
Terracotta utilise le concept de virtual heap qui permet de gérer dans le cluster de grandes quantités de données qui peuvent être largement supérieures à la mémoire disponible sur les clients. Ceci est possible car les objets gérés par le cluster ne sont fournis aux clients qu'au moment où ceux-ci en ont besoin.
L'instrumentation du bytecode côté client permet au moment de l'accès à un champ de demander sa valeur au serveur et de l'instancier dans la mémoire locale si celui-ci n'existe pas encore en local. Il est possible que ces références locales soient supprimées localement par le ramasse-miettes de la JVM. Si cette référence est de nouveau requise en local, elle sera de nouveau obtenue du serveur.
Terracotta garantit qu'une instance d'un objet géré par le cluster sera unique pour un classloader d'une JVM. Toutes les modifications sont répliquées sur les clients du cluster pour la même instance du classloader. Pour faciliter ce travail, Terracotta génère et attribue un identifiant unique qui lui est propre aux objets gérés dans le cluster.
Lors du chargement d'une classe, Terracotta utilise un classloader particulier pour enrichir le bytecode des classes à instrumenter définies dans le fichier de configuration. Les traitements ajoutés permettent à Terracotta de synchroniser l'état des objets gérés dans le cluster dans les différentes JVM qui le composent.
Les instances des objets gérés par le cluster sont créées grâce aux traitements ajoutés à la classe lors de son instrumentation :
Les classes qui accèdent à des objets gérés par le cluster doivent aussi être instrumentées même si elles ne sont pas elle-même gérées par le cluster.
Les dépendances d'un objet géré sont également gérées par le cluster : ceci permet aux graphes d'objets des objets racines d'être gérés automatiquement par le cluster assurant ainsi de maintenir la cohérence de l'état de l'objet racine au travers du cluster.
Un objet peut être géré de deux façons dans le cluster Terracotta :
Certaines classes ne peuvent pas être gérées dans le cluster comme par exemple la classe Thread. Il en va de même pour les classes qui héritent de ces classes même si elles sont instrumentées. Si une classe non gérable est incluse dans le graphe d'objets gérés par le cluster alors une exception de type TCNonPortableObjectException est levée.
Il est possible de définir certains champs d'un objet gérés par le cluster comme transient. Ces champs ne seront alors pas gérés par le cluster.
Par défaut, les champs marqués avec le modificateur transient ne sont pas ignorés par Terracotta mais il est possible de demander qu'ils le soient grâce au fichier de configuration. N'importe quel champ peut aussi être ignoré grâce à une définition particulière dans le fichier de configuration.
Terracotta propose de définir des traitements exécutés à l'instanciation de la classe qui permettent une initialisation correcte des champs transient.
L'élégance de cette solution est de ne pas être intrusive dans le code de l'application mise en cluster :
Le grand intérêt est de pouvoir utiliser une application de façon distribuée entre plusieurs instances dans des JVM dédiées sans avoir à modifier le code de l'application. Terracotta permet une mise en oeuvre d'un cluster de façon transparente pour le développeur : il n'y a pas de code intrusif à rajouter pour faire fonctionner l'application en cluster, sous réserve que le code soit déjà prévu pour fonctionner dans un mode multithread.
Le projet Terracotta met en oeuvre le clustering au niveau de la JVM en utilisant des mécanismes de partage de mémoire au travers du réseau (NAM : Network Attached Memory) qui permettent de mettre en commun des instances d'objets entre plusieurs JVM.
L'architecture d'un cluster Terracotta est composée de deux types d'éléments : des noeuds clients et un ou plusieurs serveurs :
Un serveur Terracotta assure plusieurs fonctionnalités :
Le serveur gère les verrous posés ou demandés pour les différents threads des clients. Il coordonne aussi les notifications sur les threads concernés des clients lors de l'utilisation des méthodes notify() et notifyAll() appliquées à des objets gérés par le cluster.
Le serveur gère les objets dont il a la charge : ces objets sont instanciés sur un client qui en informe le serveur pour stockage et diffusion aux autres clients selon leurs besoins. Le serveur fournit aussi l'état des objets lors des sollicitations des clients pour qu'ils puissent créer une instance en local.
Il est possible de mettre en place plusieurs serveurs Terracotta en mode fail over. Un de ces serveurs est le serveur actif, les autres sont passifs (un des serveurs passifs prend le relais en devenant actif en cas d'arrêt du serveur actif). Ce sont les clients qui vont essayer de se connecter au serveur actif.
Le serveur Terracotta peut sauvegarder les données des objets partagés sur disque pour ne pas les perdre en cas d'arrêt du serveur.
Terracotta met en oeuvre plusieurs concepts :
Tous les objets d'un noeud d'un cluster Terracotta ne sont pas gérés par le cluster mais uniquement ceux qui sont déclarés comme étant la racine (root) d'un graphe d'objets gérés par le cluster.
Une racine permet d'identifier un objet comme devant être géré dans le cluster. La définition d'une racine se fait de manière déclarative dans le fichier de configuration. Une racine (root) constitue le sommet d'un graphe d'objets qui seront gérés et partagés par le cluster Terracotta. Le graphe est constitué de la racine et des objets qui sont accessibles depuis cette instance.
Une racine est un champ d'une classe dont le nom pleinement qualifié est défini dans le fichier de configuration.
Lorsqu'une racine est instanciée pour la première fois par un client du cluster, l'instance et ses éventuelles dépendances sont créées sur le serveur Terracotta.
Une fois qu'une instance d'une racine est créée sur le serveur, celle-ci ne peut plus être réaffectée : ceci n'empêche pas de modifier les autres instances du graphe d'objets de l'instance.
Une racine peut être une variable de type littéral :
La valeur d'un objet racine de type littéral peut évoluer durant le cycle de vie du cluster. Les verrous sur un littéral sont posés par Terracotta sur leurs valeurs et non sur leur référence.
La classe qui encapsule la racine doit obligatoirement être instrumentée par Terracotta.
Des dépendances d'un objet géré par le cluster peuvent être configurées pour ne pas l'être par ce dernier.
Une transaction est un ensemble de modifications faites de façon atomique sur un ou plusieurs objets gérés par le cluster afin de garantir la cohérence de leurs états.
La portée d'une transaction est définie grâce à la pose et la libération d'un verrou. Lorsqu'un verrou est posé, Terracotta débute une transaction qui va enregistrer les modifications faites dans les objets gérés. Lorsque le verrou est libéré, la transaction est validée en reportant les modifications sur le serveur.
Chaque changement sur un objet géré par le cluster doit se faire dans une transaction : le thread qui veut faire la modification doit obligatoirement poser un verrou avant de changer l'état d'un objet géré par le cluster sinon une exception de type UnlockedSharedObjectException est levée.
Il faut donc obligatoirement :
Aucune transaction n'est définie pour une méthode :
Important : il est impératif d'instrumenter toutes les classes qui peuvent modifier l'état d'un objet géré par le cluster. Dans le cas contraire, Terracotta ne verra pas les modifications et l'objet pourra se retrouver dans un état inconsistant.
Les modifications faites sur des objets gérés par le cluster doivent l'être dans le cadre d'une transaction. Une transaction enregistre les modifications sur les objets et les données primitives des objets. A la fin de la transaction, les modifications faites sur les objets sont envoyées au serveur.
Les verrous ont deux utilités essentielles dans le fonctionnement de Terracotta :
Les verrous sont assez similaires au rôle du mot clé synchronized. D'ailleurs la définition d'un verrou peut se faire par le mot clé synchronized et/ou par définition dans le fichier de configuration.
Les verrous sont définis dans le fichier de configuration grâce à des expressions régulières qui définissent une méthode ou un ensemble de méthodes.
Il est impératif que les classes des méthodes sur lesquelles des verrous sont définis soient instrumentées.
Les verrous sont définis dans le fichier de configuration avec les éléments <autolock> parents de l'élément <locks>.
Ceci permet à Terracotta d'ajouter aux méthodes définies qui possèdent une synchronisation sur leur classe le code nécessaire à la pose de verrous au travers du cluster.
Exemple : |
<locks>
<autolock auto-synchronized="false">
<method-expression>* fr.jmdoudoux.dej.MaClasse.*(..)</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
La ou les méthodes qui seront verrouillées sont identifiées avec une expression dont la syntaxe est celle d'AspectWerkz.
Le type de verrou est précisé dans le fichier de configuration avec un tag <lock-level>.
Les verrous de Terracotta peuvent être posés selon plusieurs niveaux :
Le type et la portée d'un verrou peuvent avoir de gros impacts sur les performances de l'application exécutée dans le cluster.
Il n'est pas toujours possible de modifier le code existant pour ajouter des portions de code synchronized. Terracotta permet de les ajouter lors de l'instrumentation des classes grâce à deux fonctionnalités :
Il peut être tentant de mettre auto-synchronized à toutes les méthodes mais l'effet sur les performances serait similaire à mettre le mot clé synchronized sur toutes les méthodes : les performances sont alors catastrophiques car il y a énormément de contention. Les verrous doivent donc être posés judicieusement et de préférence dans le code.
La pose de verrous est ajoutée par Terracotta sur les méthodes synchronized et leurs portions de code synchronized qui sont identifiés dans un autolock.
Une méthode définie dans un autolock qui n'a pas de synchronized n'a aucun effet. Dans ce cas, il est possible d'utiliser l'attribut auto-synchronized pour que Terracotta ajoute dynamiquement le mot clé synchronized à la méthode.
La mise en oeuvre des autolock de Terracotta se fait lors de l'instrumentation du bytecode (MONITORENTER et MONITOREXIT) en ajoutant du bytecode pour poser et lever un verrou au niveau du cluster.
Le verrou doit donc se faire sur un objet géré par le cluster car l'acquisition du verrou se fait en utilisant l'identifiant unique attribué par Terracotta. Pour une méthode synchronized qui n'est pas définit dans la partie autolock, le verrou est posé uniquement dans la machine virtuelle locale.
Terracotta propose, en plus des autolocks, des named locks qui permettent de définir des verrous identifiés par un nom. Il est préférable d'utiliser des autolocks plutôt que des named locks lorsque cela est possible car ces derniers peuvent avoir de gros impacts sur les performances de l'application.
Exemple : |
<named-lock>
<lock-name>MonVerrou</lock-name>
<method-expression>* fr.jmdoudoux.dej.MaClasse.*(..)</method-expression>
<lock-level>read</lock-level>
</named-lock>
Les traitements relatifs au clustering sont ajoutés par instrumentation du bytecode de l'application : ce code est injecté au chargement de la classe par un classloader dédié. Il permet notamment de poser les verrous et échanger l'état des objets gérés par le cluster.
Les classes à instrumenter sont définies dans le fichier de configuration. Il est inutile d'instrumenter toutes les classes mais uniquement celles qui doivent être gérées par le cluster ou qui manipulent des objets gérés par le cluster. Il est d'autant plus important de limiter le nombre de classes à instrumenter que cette instrumentation est coûteuse au chargement de la classe et à l'exécution du fait des échanges réseau avec le serveur.
La définition des classes à instrumenter dans le fichier de configuration est indépendante de la définition des racines (roots) et des verrous (locks) : la définition de l'un ou l'autre n'implique pas une instrumentation explicite.
Terracotta doit instrumenter certaines classes par le classloader de boot. Ces classes ne peuvent pas être instrumentées dynamiquement puisque chargées avant Terracotta.
Celles-ci doivent donc être pré-instrumentées et placée dans un fichier jar particulier qui sera ajouté dans le classpath de boot. Ce fichier jar est nommé "boot jar" par Terracotta.
Terracotta propose un outil dédié pour générer ce boot jar. Une classe qui est chargée par le classloader de boot ne peut pas être gérée par le cluster : cette classe doit être instrumentée dans le boot jar.
La bibliothèque boot jar est précisée au lancement de chaque JVM cliente avec l'option :
-Xbootclasspath/p:chemin_du_fichier_jar_de_boot_terracotta.jar
L'invocation distribuées de méthodes (DMI : Distributed Method Invocation) permet l'invocation d'une méthode d'un objet géré par le cluster dans tous les noeuds du cluster.
L'objet sur lequel la méthode sera invoquée doit être instrumenté et être géré par le cluster.
Un serveur Terracotta dispose d'un ramasse-miettes distribué (DGC Distributed Garbage Collector) dont le rôle est de purger les objets gérés par le cluster qui ne sont plus référencés ni dans le serveur ni dans aucun client.
Malgré son nom, ce n'est pas un processus distribué : il supprime uniquement des objets côté serveur (en mémoire et éventuellement dans le système de persistance des données du cluster) après s'être assuré qu'ils ne sont plus référencés par aucun objet gérés sur le serveur et qu'aucun client n'en possède encore une référence.
Un objet est géré par le cluster de son instanciation jusqu'à sa libération par le ramasse-miettes distribué (DGC Distributed Garbage Collector) de Terracotta.
Les objets racines ne sont pas traités par le DGC. Il y a plusieurs façons d'exécuter le DGC :
L'activité du DGC est journalisée dans le fichier de log du serveur. Cette activité peut aussi être surveillée en utilisant la console d'administration de Terracotta.
Cette section va fournir quelques informations pour mettre en oeuvre Terracotta.
Terracotta peut être téléchargé gratuitement sur le site terracotta.org après un enregistrement obligatoire.
Il faut télécharger le fichier terracotta-3.2.0-installer.jar et l'exécuter :
C:\java>java -jar terracotta-3.2.0-installer.jar
Un assistant guide l'installation qui se fait par défaut dans le répertoire C:\Program Files\terracotta\terracotta-3.2.0
Pour faciliter son utilisation, il est possible d'ajouter le sous-répertoire bin, issu de l'installation, à la variable système PATH.
Le répertoire d'installation contient plusieurs sous-répertoires :
Nom | Contenu |
bin | les scripts de commande |
config-examples | les exemples de fichier de configuration |
distributed-cache | ehcache |
docs | la javadoc et un lien vers la doc en ligne |
hibernate | |
icons | |
lib | les bibliothèques de Terracotta et de ses dépendances |
modules | les modules permettant l'intégration de différentes technologies |
quartz-1.7.0 | l'api de scheduling Quartz |
samples | des exemples |
schema | le schéma du fichier de configuration et sa documentation |
tools | des outils notamment le sessions-configurator |
vendors | des outils tiers préconfigurés avec Terracotta |
Pour faciliter la mise en oeuvre sans une connaissance approfondie de certains outils ou bibliothèques, Terracotta propose des modules d'intégration (TIM : Terracotta Integration Module).
Ces modules proposent une configuration out of the box :
La commande tim-get permet de gérer les modules installés avec Terracotta.
La syntaxe de cette commande est de la forme :
tim-get.bat [command] [arguments] {options}
Elle possède en premier argument une commande en fonction de l'action à réaliser :
Option | Rôle |
list | obtenir une liste des TIM utilisables |
install | installer un module |
install-for | installer les modules précisés dans le fichier de configuration |
info | afficher des informations détaillées sur un module |
update | installer la dernière version d'un module |
upgrade | mettre à jour le fichier de configuration et installer les dernières versions des modules |
help | afficher une aide sur les commandes |
L'option -h ou --help de chaque commande permet d'afficher une aide sur les options de la commande.
Cette commande requiert un accès à internet.
Certaines propriétés de la commande peuvent être modifiées dans le fichier tim-get.properties du sous-répertoire lib/resources d'installation de Terracotta. C'est notamment le cas si l'accès à internet passe par un proxy.
Les modules disposent d'un fichier terracotta.xml qui contient un fragment de la configuration qui sera fusionné avec le fichier de configuration de Terracotta.
Terracotta propose un ensemble de scripts permettant de mettre en oeuvre le cluster.
Ces scripts sont livrés pour Unix (*.sh) ou windows (*.bat).
Script | Rôle |
archive-tool | collecter des informations sur l'environnement pour les fournir au support de Terracotta |
boot-jar-path | utiliser l'outil dso-env pour déterminer le chemin de la JVM. Cet outil ne devrait pas être utilisé directement |
dev-console | lancer la console du développeur qui est un outil graphique pour surveiller et piloter le cluster |
dso-env | permet de configurer un environnement client en
valorisant une variable d'environnement système TC_JAVA_OPTS à partir des
informations fournies par les variables JAVA_HOME,
TC_INSTALL_DIR et TC_CONFIG_PATH Microsoft Windows set TC_INSTALL_DIR="C:\Program Files\terracotta\terracotta-3.2.0" set TC_CONFIG_PATH="localhost:9510" call "%TC_INSTALL_DIR%\bin\dso-env.bat" -q set JAVA_OPTS=%TC_JAVA_OPTS% %JAVA_OPTS% call "%JAVA_HOME%\bin\java" %JAVA_OPTS% ... UNIX/Linux (bash) TC_INSTALL_DIR="/usr/local/terracotta-3.2.0" export TC_INSTALL_DIR TC_CONFIG_PATH="localhost:9510" export TC_CONFIG_PATH . ${TC_INSTALL_DIR}/bin/dso-env.sh -q JAVA_OPTS="${JAVA_OPTS} ${TC_JAVA_OPTS}" ${JAVA_HOME}/bin/java ${JAVA_OPTS} ... |
dso-java | permet de lancer un environnement d'exécution pour un client du cluster |
make-boot-jar | permet de créer si nécessaire le boot jar qui devra être ajouté au classpath de boot. Le boot jar est spécifique à une plate-forme, une JVM et à la version de cette JVM |
run-dgc | demander l'exécution du ramasse-miettes distribué |
scan-boot-jar | permet de vérifier la cohérence entre le boot jar et le fichier de configuration |
server-stat | (server status tool) permet de vérifier le statut courant du cluster |
start-tc-server | démarrer un serveur du cluster |
stop-tc-server | arrêter un serveur du cluster |
tc-stats | (Terracotta cluster statistics recorder) permet de configurer et de gérer l'enregistrement des statistiques dans le cluster |
tim-get | gérer les modules installés de Terracotta (TIM) |
version | afficher la version de Terracotta |
Tous les éléments du cluster (clients et serveurs) doivent utiliser la même JVM (vendeur et version) et la même version de Terracotta.
Attention : toutes les JVM ne sont pas supportées. Vu le mode de fonctionnement impliquant des interactions de bas niveau avec la JVM, il n'est pas surprenant que Terracotta ne fonctionne qu'avec certaines implémentations de la JVM ou même certaines versions de ces implémentations.
Terracotta peut gérer dans le cluster la plupart des objets Java mais il y a cependant des restrictions notamment des classes qui encapsulent une ressource externe (fichier, socket réseau, connexion vers des ressources, ...) ou spécifique à la JVM (Runtime, Thread, ...)
Un objet ne peut donc être géré par le cluster que s'il est portable. Toute tentative de faire gérer par le cluster un objet non portable lève une exception de type TCNonPortableObjectException.
Terracotta propose du clustering d'objets Java au niveau de la JVM dans un but généraliste. Les cas d'utilisation sont donc nombreux parmi lesquels :
Certains de ces cas sont détaillés dans les sections suivantes.
Chaque conteneur web propose sa propre implémentation concernant le stockage des sessions. Les données d'une session particulière sont retrouvées grâce à un identifiant unique nommé jsessionid.
La tendance est aux applications web stateless côté serveur mais cela implique que l'état soit stocké côté client (dans une application de type RIA) ou soit stocké dans la base de données mais ces deux solutions ne sont pas toujours possibles à mettre en oeuvre. Si le contexte est stocké dans une session, ce qui est le cas dans beaucoup d'applications web, il est nécessaire de répliquer ces sessions dans les différents noeuds du cluster ne serait-ce que pour garantir le fail-over.
Dans la réplication des sessions d'un cluster de conteneurs web, Terracotta peut être plus efficace que les mécanismes proposés par ces conteneurs qui utilisent généralement la sérialisation. Le mode de fonctionnement de Terracotta peut aussi permettre l'utilisation d'une affinité de session : tant que le serveur répond, il n'y pas de réplication des données sur les autres noeuds du cluster. Dès que le serveur tombe, un autre noeud répond et va obtenir les données de la session du serveur Terracotta. Les échanges réseaux sont donc limités.
De plus les objets stockés dans la session n'ont pas l'obligation d'implémenter l'interface Serializable.
Fréquemment pour optimiser la réplication des sessions fournies par les conteneurs, les objets de la session sont répartis dans différents attributs. Le mécanisme optimisé de la réplication des objets proposés par Terracotta évite d'avoir à sérialiser le graphe d'objets.
Pour faciliter la configuration, Terracotta propose des configurations par défaut pour conteneurs (Tomcat, Jetty, JBoss, GlassFish, ...) qui vont permettre de gérer les objets de stockages de la session dans le cluster Terracotta.
La configuration est très simple puisqu'il suffit
Exemple : |
<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
...
<application>
<dso>
<instrumented-classes>
<include>
<class>fr.jmdoudoux.dej.terracotta.MaClasse</class>
</include>
</instrumented-classes>
<web-applications>
<web-application>MaWebApp</web-application>
</web-applications>
</dso>
</application>
</tc:tc-config>
Pour limiter les échanges de données entre les noeuds du cluster, il peut être utile d'utiliser l'affinité de session au niveau du load balancer pour permettre que cela soit toujours le même noeud qui réponde à un même client dans des conditions d'utilisation normale.
La mise en cluster d'un graphe d'objets se prête particulièrement bien à une utilisation sous la forme d'un cache distribué. Ainsi les données du cache sont répliquées dans les différents noeuds du cluster avec un chargement unique dans un noeud et la réplication dans les autres évitant ainsi un chargement à chaque noeud.
Terracotta peut distribuer un cache dont il propose un TIM par exemple EhCache ou une solution maison plus simple utilisant par exemple une collection de type Map gérant les accès concurrents comme ConcurrentHashMap.
Si le cache est fréquemment mis à jour, il faut être vigilent sur le positionnement des verrous pour ne pas dégrader les performances d'accès au cache ce qui annihilerait l'intérêt de sa mise en oeuvre.
Ce cas d'utilisation met en oeuvre le motif de conception master-worker. Un unique master crée des tâches et les empiles dans une collection partagée généralement de type Queue. Plusieurs workers se chargent du traitement des tâches placées dans la file.
Ceci permet de paralléliser les traitements de ces tâches dans les différentes JVM qui composent le cluster.
Terracotta permet de partager des objets entre plusieurs instances d'une même application mais peut aussi permettre le partage d'objets entre différentes applications. Bien sûr, ces objets doivent avoir des caractéristiques communes notamment celles définies comme racine.
Ceci peut être très pratique pour par exemple partager des données entre une version standalone et une version web d'une application.
Terracotta est fourni avec plusieurs applications d'exemples notamment une application de type chat, de partage de données, d'édition graphique, de type Queue, ...
Cette section va proposer quelques exemples simples de mise en oeuvre de Terracotta. Dans un contexte concret, les principes utilisés sont les mêmes mais sont généralement plus complexes notamment en ce qui concerne la définition du contenu du fichier de configuration.
L'application utilisée dans cet exemple incrémente simplement un compteur static.
Exemple : |
package fr.jmdoudoux.dej.terracotta;
public class MainTest {
private static int compteur;
public static void main(String[] args) {
compteur++;
System.out.println("Compteur = " + compteur);
}
}
Remarque : cet exemple est basique car il ne gère pas les accès concurrents à la variable compteur. Il ne peut donc fonctionner correctement que si une seule instance de l'application est en cours d'exécution.
Chaque exécution de cette application affiche toujours la valeur 1 pour le compteur puisque sa valeur est initialisée à chaque lancement d'une JVM.
Résultat : |
C:\eclipse34\workspace\TestTerracotta\bin>java -cp . fr.jmdoudoux.dej.terracot
ta.MainTest
Compteur = 1
C:\eclipse34\workspace\TestTerracotta\bin>java -cp . fr.jmdoudoux.dej.terracot
ta.MainTest
Compteur = 1
La mise en cluster de cette application va permettre de maintenir la valeur du compteur dans le cluster et permettre ainsi son incrémentation à chaque exécution.
Le fichier de configuration de Terracotta contient uniquement la définition d'une racine qui correspond au compteur.
Exemple : |
<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
<application>
<dso>
<roots>
<root>
<field-name>fr.jmdoudoux.dej.terracotta.MainTest.compteur</field-name>
</root>
</roots>
</dso>
</application>
</tc:tc-config>
Il faut lancer le serveur Terracotta dans une boîte de commandes dédiée.
Résultat : |
C:\>start-tc-server.bat
2010-04-25 21:29:58,555 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-04-25 21:30:00,040 INFO - Configuration loaded from the Java resource at '/
com/tc/config/schema/setup/default-config.xml', relative to class com.tc.config.
schema.setup.StandardXMLFileConfigurationCreator.
2010-04-25 21:30:01,009 INFO - Log file: 'C:\Documents and Settings\jmd\terracot
ta\server-logs\terracotta-server.log'.
2010-04-25 21:30:04,383 INFO - Available Max Runtime Memory: 504MB
2010-04-25 21:30:08,305 INFO - JMX Server started. Available at URL[service:jmx:
jmxmp://0.0.0.0:9520]
2010-04-25 21:30:10,399 INFO - Terracotta Server instance has started up as ACTI
VE node on 0.0.0.0:9510 successfully, and is now ready for work.
Il faut ensuite exécuter l'application dans une JVM configurée pour être utilisable dans le cluster. Pour une application standalone, Terracotta propose le script dso-java qui lance une JVM configurée pour devenir un client du cluster.
Par défaut, l'outil dso-java utilise le fichier tc-config.xml présent dans le répertoire courant. Il est possible de préciser un autre fichier en utilisant l'option -Dtc.config
Résultat : |
C:\eclipse34\workspace\TestTerracotta\bin>dso-java -cp . fr.jmdoudoux.dej.terr
acotta.MainTest
Starting Terracotta client...
2010-04-25 21:42:00,615 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-04-25 21:42:01,891 INFO - Configuration loaded from the file at 'C:\eclipse
34\workspace\TestTerracotta\bin\tc-config.xml'.
2010-04-25 21:42:02,343 INFO - Log file: 'C:\eclipse34\workspace\TestTerracotta\
bin\logs-172.16.0.50\terracotta-client.log'.
2010-04-25 21:42:13,204 INFO - Connection successfully established to server at
127.0.0.1:9510
Compteur = 1
C:\eclipse34\workspace\TestTerracotta\bin>dso-java -cp . fr.jmdoudoux.dej.terr
acotta.MainTest
Starting Terracotta client...
2010-04-25 21:42:18,167 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-04-25 21:42:18,728 INFO - Configuration loaded from the file at 'C:\eclipse
34\workspace\TestTerracotta\bin\tc-config.xml'.
2010-04-25 21:42:18,852 INFO - Log file: 'C:\eclipse34\workspace\TestTerracotta\
bin\logs-172.16.0.50\terracotta-client.log'.
2010-04-25 21:42:20,906 INFO - Connection successfully established to server at
127.0.0.1:9510
Compteur = 2
C:\eclipse34\workspace\TestTerracotta\bin>dir
Volume in drive C has no label.
Volume Serial Number is 1B46-3C32
Directory of C:\eclipse34\workspace\TestTerracotta\bin
25/04/2010 21:42 <DIR> .
25/04/2010 21:42 <DIR> ..
25/04/2010 21:22 <DIR> com
25/04/2010 21:42 <DIR> logs-127.0.0.1
25/04/2010 21:23 283 tc-config.xml
C:\eclipse34\workspace\TestTerracotta\bin>
L'état de la variable gérée par le cluster est automatiquement fourni au client une fois créé, ce qui permet une incrémentation sans réinitialisation par les clients.
Ce second exemple va utiliser une application qui stocke sa date/heure d'exécution dans une collection, attend 10 secondes et affiche le contenu de la collection.
Exemple : |
package fr.jmdoudoux.dej.terracotta;
import java.util.*;
public class MainTest1 {
private List<String> donnees = new ArrayList<String>();
public void traiter() {
synchronized (donnees) {
donnees.add("Traitement du " + new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (String donnee : donnees) {
System.out.println(donnee);
}
}
}
public static void main(String[] args) {
System.out.println("Demarrage de l'application");
new MainTest1().traiter();
System.out.println("Arret de l'application");
}
}
A chaque exécution de cette application, la collection ne contient que l'occurrence du traitement courant puisque la collection est recréée dans chaque JVM.
Il est possible avec Terracotta de maintenir l'état de la collection sur le serveur Terracotta et ainsi de partager l'objet entre différentes instances de JVM qui exécutent l'application en concomitance ou non.
Ce qui est intéressant avec Terracotta c'est que le code de l'application n'est pas modifié : aucune API de Terracotta n'est utilisée. Seul l'environnement d'exécution est différent.
Il faut tout d'abord créer un fichier de configuration pour Terracotta nommé tc-config.xml
Exemple : |
<?xml version="1.0" encoding="UTF-8"?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
<application>
<dso>
<roots>
<root>
<field-name>fr.jmdoudoux.dej.terracotta.MainTest1.donnees
</field-name>
</root>
</roots>
<locks>
<autolock>
<method-expression>
* fr.jmdoudoux.dej.terracotta.MainTest1*.*(..)
</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
<instrumented-classes>
<include>
<class-expression>
fr.jmdoudoux.dej.terracotta.MainTest1
</class-expression>
</include>
</instrumented-classes>
</dso>
</application>
</tc:tc-config>
Ce fichier permet de préciser les classes qui sont prises en charge par Terracotta.
Il faut lancer le serveur Terracotta en exécutant la commande start-tc-server
Résultat : |
C:\Documents and Settings\jmd>start-tc-server.bat
2010-02-12 21:00:08,599 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-02-12 21:00:09,193 INFO - Configuration loaded from the Java resource at '/
com/tc/config/schema/setup/default-config.xml', relative to class com.tc.config.
schema.setup.StandardXMLFileConfigurationCreator.
2010-02-12 21:00:09,490 INFO - Log file: 'C:\Documents and Settings\jmd\terracot
ta\server-logs\terracotta-server.log'.
2010-02-12 21:00:10,068 INFO - Available Max Runtime Memory: 504MB
2010-02-12 21:00:11,130 INFO - JMX Server started. Available at URL[service:jmx:
jmxmp://0.0.0.0:9520]
2010-02-12 21:00:12,287 INFO - Terracotta Server instance has started up as ACTI
VE node on 0.0.0.0:9510 successfully, and is now ready for work.
Il faut exécuter l'application en utilisant la commande dso-java
Résultat : |
C:\eclipse34\workspace\TestTerracotta\bin>dso-java fr.jmdoudoux.dej.terracotta.MainTest1
Starting Terracotta client...
2010-02-12 21:04:20,410 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-02-12 21:04:21,019 INFO - Configuration loaded from the file at 'C:\eclipse
34\workspace\TestTerracotta\bin\tc-config.xml'.
2010-02-12 21:04:21,144 INFO - Log file: 'C:\eclipse34\workspace\TestTerracotta\
bin\logs-127.0.0.1\terracotta-client.log'.
2010-02-12 21:04:24,097 INFO - Connection successfully established to server at 127.0.0.1:9510
Demarrage de l'application
Hello, World Fri Feb 12 21:04:24 CET 2010
Arret de l'application
Pour tirer avantages de Terracotta, il faut ouvrir deux boîtes de commandes et exécuter deux fois l'application en simultanée.
Si l'application est exécutée une troisième fois, les données des deux premières exécutions sont toujours présentes tant que le serveur Terracotta n'est pas arrêté.
L'exemple de cette section va exécuter une application sur deux instances d'un serveur Tomcat version 6.0 mises en cluster.
Cette webapp contient une unique servlet stockant sa date/heure d'invocation dans un singleton qui encapsule une collection et affiche le contenu de la collection.
Le code du singleton est plutôt basique.
Exemple : |
package fr.jmdoudoux.dej.terracotta.web;
import java.util.*;
public class MonCache {
private static MonCache instance = new MonCache();
private List<String> maListe;
private MonCache() {
maListe = new ArrayList<String>();
}
public static MonCache getInstance() {
return instance;
}
public List<String> getDonnees() {
return Collections.unmodifiableList(maListe);
}
public synchronized void ajouter(String occurence) {
maListe.add(occurence);
}
public synchronized void effacer() {
maListe.clear();
}
}
Cette classe gère les accès concurrents notamment au niveau des méthodes qui opèrent des mises à jour dans la collection et elle renvoie une version immuable de lcelle-ci. Ces éléments sont importants pour la bonne mise en oeuvre de Terracotta.
Il faut obtenir et installer le TIM dédié à Tomcat 6.0 en utilisant la commande tim-get avec l'option install suivie du module à installer et de sa version.
Résultat : |
C:\Users\Jean Michel>tim-get.bat list
Terracotta 3.2.0, as of 20100107-130122 (Revision 14244 by cruise@su10mo5 from 3
.2)
*** Terracotta Integration Modules for TC 3.2.0 ***
(+) ehcache-terracotta 1.8.0 [net.sf.ehcache]
(+) terracotta-hibernate-cache 1.1.0 [org.terracotta.hibernate]
(-) pojoizer 1.0.4
(-) tim-annotations 1.5.0
(-) tim-apache-collections-3.1 1.2.0
...
(-) tim-tomcat-5.0 2.1.0
(-) tim-tomcat-5.5 2.1.0
(-) tim-tomcat-6.0 2.1.0
(-) tim-vector 2.6.1
(-) tim-wan-collections 1.1.0
(-) tim-weblogic-10 2.1.0
(-) tim-weblogic-9 2.1.0
(-) tim-wicket-1.3 1.4.0
(+) quartz-terracotta 1.0.0 [org.terracotta.quartz]
(-) simulated-api 1.2.0 [org.terracotta]
(+) Installed (-) Not installed (!) Installed but newer version exists
C:\Users\Jean Michel>tim-get install tim-tomcat-6.0 2.1.0
Terracotta 3.2.0, as of 20100107-130122 (Revision 14244 by cruise@su10mo5 from 3
.2)
Installing tim-tomcat-6.0 2.1.0 and dependencies...
INSTALLED: tim-tomcat-6.0 2.1.0 - Ok
INSTALLED: tim-tomcat-5.5 2.1.0 - Ok
INSTALLED: tim-tomcat-common 2.1.0 - Ok
SKIPPED: tim-session-common 2.1.0 - Already installed
Done. (Make sure to update your tc-config.xml with the new/updated version if ne
cessary)
C:\Users\Jean Michel>
Il faut fournir en paramètre de la JVM les informations utiles à l'agent de Terracotta. Le plus simple est d'utiliser le script dso-env fourni par Terracotta :
Exemple sous Linux/Unix :
Résultat : |
...
export TC_INSTALL_DIR=<chemin_rep_Terracotta>
export TC_CONFIG_PATH=<chemin_fichier_tc-config.xml>
. $TC_INSTALL_DIR/bin/dso-env.sh -q
export JAVA_OPTS="$TC_JAVA_OPTS $JAVA_OPTS"
...
Exemple sous Windows :
Résultat : |
...
set TC_INSTALL_DIR=< chemin_rep_Terracotta >
set TC_CONFIG_PATH=<path_to_local_tc-config.xml>
%TC_INSTALL_DIR%\bin\dso-env.bat -q
set JAVA_OPTS=%TC_JAVA_OPTS%;%JAVA_OPTS%
...
Il faut lancer le serveur Terracotta en lançant le script start-tc-server puis les serveurs du cluster.
Cet exemple va permettre à deux applications d'incrémenter une variable commune partagée par Terracotta. Cette variable est encapsulée dans un objet propre à chacune des applications.
Exemple : |
package fr.jmdoudoux.dej.terracotta.appli_a;
public class MonObjetA {
private static int compteur;
public static synchronized int incrementer() {
compteur++;
return compteur;
}
}
La première application incrémente et affiche la variable deux fois.
Exemple : |
package fr.jmdoudoux.dej.terracotta.appli_a;
public class AppliA {
public static void main(String[] args) {
System.out.println("Appli_A compteur="+MonObjetA.incrementer());
System.out.println("Appli_A compteur="+MonObjetA.incrementer());
}
}
Le fichier de configuration de Terracotta pour l'application ne dispose que d'une seule particularité : la racine du compteur partagé est définie avec un nom qui permettra d'y faire référence dans le fichier de configuration de l'autre application. Ce nom permettra à Terracotta d'identifier les deux objets comme étant le même : toutes les modifications dans l'un seront reportées dans l'autre et vice versa.
Exemple : |
<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
<application>
<dso>
<roots>
<root>
<field-name>fr.jmdoudoux.dej.terracotta.appli_a.MonObjetA.compteur</field-name>
<root-name>MonCompteur</root-name>
</root>
</roots>
<locks>
<autolock>
<method-expression>
* fr.jmdoudoux.dej.terracotta.appli_a.MonObjetA.*()
</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
<instrumented-classes>
<include>
<class-expression>
fr.jmdoudoux.dej.terracotta.appli_a.*
</class-expression>
</include>
</instrumented-classes>
</dso>
</application>
</tc:tc-config>
La seconde application encapsule aussi une variable statique de type int dans un objet dédié.
Exemple : |
package fr.jmdoudoux.dej.terracotta.appli_b;
public class AppliB {
public static void main(String[] args) {
System.out.println("Appli_B compteur="+MonObjetB.incrementer());
}
}
La seconde application va incrémenter le compteur et afficher la valeur.
Exemple : |
package fr.jmdoudoux.dej.terracotta.appli_b;
public class MonObjetB {
private static int compteur;
public static synchronized int incrementer() {
compteur++;
return compteur;
}
}
Le fichier de configuration est similaire à celui de la première application en utilisant ses propres objets.
Exemple : |
<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
<application>
<dso>
<roots>
<root>
<field-name>fr.jmdoudoux.dej.terracotta.appli_b.MonObjetB.compteur</field-name>
<root-name>MonCompteur</root-name>
</root>
</roots>
<locks>
<autolock>
<method-expression>
* fr.jmdoudoux.dej.terracotta.appli_b.MonObjetB.*()
</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
<instrumented-classes>
<include>
<class-expression>
fr.jmdoudoux.dej.terracotta.appli_b.*
</class-expression>
</include>
</instrumented-classes>
</dso>
</application>
</tc:tc-config>
Avant de lancer les applications, il faut lancer le serveur Terracotta.
Exemple : |
C:\>start-tc-server.bat
2010-04-25 14:38:03,139 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-04-25 14:38:03,733 INFO - Configuration loaded from the Java resource at '/
com/tc/config/schema/setup/default-config.xml', relative to class com.tc.config.
schema.setup.StandardXMLFileConfigurationCreator.
2010-04-25 14:38:04,154 INFO - Log file: 'C:\Documents and Settings\jmd\terracot
ta\server-logs\terracotta-server.log'.
2010-04-25 14:38:06,701 INFO - Available Max Runtime Memory: 504MB
2010-04-25 14:38:09,279 INFO - JMX Server started. Available at URL[service:jmx:
jmxmp://0.0.0.0:9520]
2010-04-25 14:38:10,264 INFO - Terracotta Server instance has started up as ACTI
VE node on 0.0.0.0:9510 successfully, and is now ready for work.
Pour vérifier le partage de la donnée entre les deux applications, le test suivant est exécuté :
Résultat : |
C:\eclipse34\workspace\TestTerracottaA\bin>dso-java -cp . fr.jmdoudoux.dej.ter
racotta.appli_a.AppliA
Starting Terracotta client...
2010-04-25 14:40:12,821 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-04-25 14:40:13,414 INFO - Configuration loaded from the file at 'C:\eclipse
34\workspace\TestTerracottaA\bin\tc-config.xml'.
2010-04-25 14:40:13,586 INFO - Log file: 'C:\eclipse34\workspace\TestTerracottaA
\bin\logs-172.16.0.50\terracotta-client.log'.
2010-04-25 14:40:15,805 INFO - Connection successfully established to server at
172.16.0.50:9510
Appli_A compteur=1
Appli_A compteur=2
C:\eclipse34\workspace\TestTerracottaB\bin>dso-java -cp . fr.jmdoudoux.dej.ter
racotta.appli_b.AppliB
Starting Terracotta client...
2010-04-25 14:40:28,226 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-04-25 14:40:28,820 INFO - Configuration loaded from the file at 'C:\eclipse
34\workspace\TestTerracottaB\bin\tc-config.xml'.
2010-04-25 14:40:28,960 INFO - Log file: 'C:\eclipse34\workspace\TestTerracottaB
\bin\logs-172.16.0.50\terracotta-client.log'.
2010-04-25 14:40:30,976 INFO - Connection successfully established to server at
172.16.0.50:9510
Appli_B compteur=3
C:\eclipse34\workspace\TestTerracottaB\bin>dso-java -cp . fr.jmdoudoux.dej.ter
racotta.appli_b.AppliB
Starting Terracotta client...
2010-04-25 14:40:35,757 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-04-25 14:40:36,351 INFO - Configuration loaded from the file at 'C:\eclipse
34\workspace\TestTerracottaB\bin\tc-config.xml'.
2010-04-25 14:40:36,491 INFO - Log file: 'C:\eclipse34\workspace\TestTerracottaB
\bin\logs-172.16.0.50\terracotta-client.log'.
2010-04-25 14:40:38,476 INFO - Connection successfully established to server at
172.16.0.50:9510
Appli_B compteur=4
C:\eclipse34\workspace\TestTerracottaA\bin>dso-java -cp . fr.jmdoudoux.dej.ter
racotta.appli_a.AppliA
Starting Terracotta client...
2010-04-25 14:42:27,986 INFO - Terracotta 3.2.0, as of 20100107-130122 (Revision
14244 by cruise@su10mo5 from 3.2)
2010-04-25 14:42:28,580 INFO - Configuration loaded from the file at 'C:\eclipse
34\workspace\TestTerracottaA\bin\tc-config.xml'.
2010-04-25 14:42:28,752 INFO - Log file: 'C:\eclipse34\workspace\TestTerracottaA
\bin\logs-172.16.0.50\terracotta-client.log'.
2010-04-25 14:42:30,768 INFO - Connection successfully established to server at
172.16.0.50:9510
Appli_A compteur=5
Appli_A compteur=6
Le compteur est incrémenté correctement selon les invocations des applications.
Terracotta propose un outil particulièrement utile pour obtenir des informations sur le cluster et le surveiller : la console développeur (Developer Console).
Cet outil permet de voir de nombreuses informations sur l'état d'un cluster, notamment :
A partir de ces informations, il est possible de déterminer des actions pour améliorer les performances notamment sur les verrous.
Comme Terracotta ne propose aucune API, la configuration se fait dans un fichier de configuration qui est utilisé par le serveur et les noeuds du cluster.
Le fichier de configuration de Terracotta est un fichier XML qui permet de décrire les caractéristiques et le comportement du ou des serveurs Terracotta et des clients. Ce fichier explicite :
Au lancement d'un serveur, celui-ci recherche le fichier de configuration qui peut être :
Au lancement d'un client, celui-ci recherche le fichier de configuration qui peut être :
Le contenu du fichier de configuration chargé par un serveur ou un client est inséré dans le fichier de log. Il est aussi consultable grâce à la console du développeur.
Le fichier XML de configuration est composé de plusieurs éléments principaux facultatifs :
Cette partie est définie dans le tag <system> qui possède un tag fils <configuration-model> permettant de préciser un modèle de configuration : les valeurs possibles sont development ou production.
Par défaut, c'est le modèle development qui est utilisé. Il permet à chaque client d'avoir son propre fichier de configuration.
Avec le modèle production, les clients obtiennent le fichier de configuration d'un serveur, ce qui assure une cohérence entre les configurations des éléments du cluster (serveurs et clients). La JVM d'un noeud client doit avoir la propriété tc.config valorisée avec le nom du serveur et son port : -Dtc.config=host:port
Cette partie permet de configurer le ou les serveurs du cluster. Elle est définie dans le tag <servers>.
Si cette section est omise, alors le cluster contiendra un seul noeud avec les valeurs par défaut.
Chaque serveur du cluster est défini dans un tag fils <server>. Ce tag possède plusieurs attributs :
Attribut | Rôle |
host | host du serveur Valeur par défaut : l'adresse IP de la machine locale |
name | nom servant d'identifiant du serveur |
bind | Valeur par défaut : 0.0.0.0 |
Si plusieurs serveurs sont définis dans le fichier de configuration, chaque serveur doit être démarré en précisant son nom.
Le tag fils <data> permet de préciser le répertoire qui va contenir les données du serveur stockées sur le système de fichiers.
Le tag fils <logs> permet de préciser le répertoire qui va contenir les logs du serveur.
Chaque serveur doit avoir un répertoire de logs distinct qui par défaut est le sous-répertoire terracotta/server-logs du répertoire de l'utilisateur.
Le tag fils <statistics> permet de préciser le répertoire qui va contenir les données statistiques du serveur.
Le tag fils <dso-port> permet de préciser le port d'écoute du serveur qui par défaut est le port 9510.
Le tag fils <jmx-port> permet de préciser le port JMX du serveur qui par défaut est le port 9520.
Le tag fils <l2-group-port> permet de préciser le port utilisé pour la communication entre les serveurs en mode actif-passif qui par défaut est le port 9530.
Le tag <dso> permet de configurer le service DSO du serveur.
Le tag fils <client-reconnect-window> permet de définir un temps de reconnexion pour un client exprimé en secondes. La valeur par défaut est 120.
Le tag fils <persistence> permet de préciser si les données gérées par le cluster sont persistantes ou non. Les valeurs possibles sont :
Le tag fils <garbage-collection> permet de configurer le ramasse-miettes distribué. Il possède plusieurs tags fils :
Tag | Rôle |
enabled | Activer ou non le DGC. La désactivation n'est utile que si
aucune des instances gérées par le cluster n'est supprimée
Par défaut : true |
verbose | Activer ou non l'inclusion des informations relatives aux
activités du DGC dans la log
Par défaut : false |
interval | Préciser le temps d'attente, en secondes, entre deux exécutions du DGC Par défaut : 3600 |
Le tag <ha> permet de configurer le mode de fonctionnement des serveurs du cluster : il faut donc qu'il y ait au moins 2 serveurs de configurés.
Le tag fils <mode> peut prendre deux valeurs :
Le tag fils <mirror-groups> permet de définir des groupes de serveurs.
Cette partie permet de configurer le ou les clients du cluster. Elle est définie dans le tag <clients>.
Exemple : |
<clients>
<logs>/home/logs/terracotta/client-logs
</logs>
<statistics>/home/logs/terracotta/client-stats
</statistics>
<modules>
<module name="tim-tomcat-6.0" version="2.1.0" />
<module name="tim-vector" version="2.6.0" />
<module name="tim-hashtable" version="2.6.0" />
</modules>
</clients>
Le tag fils <logs> permet de préciser où seront stockés les fichiers de logs.
Chaque client doit avoir un répertoire de logs distinct qui par défaut est le sous-répertoire terracotta/client-logs du répertoire de l'utilisateur.
Le tag fils <statistics> permet de préciser le répertoire qui va contenir les données statistiques du client.
Le tag fils <modules> permet de définir les TIM qui seront utilisés par les clients. Le tag <modules> contient un tag fils <module> pour chaque TIM utilisé.
Le tag <module> possède plusieurs attributs :
Par défaut, Terracotta recherche un jar correspondant au module à partir des informations fournies dans le sous-répertoire modules du répertoire d'installation de Terracotta. Il est possible de préciser des répertoires supplémentaires en utilisant le tag <repository> fils du tag <modules>. Si un module est stocké dans un de ces répertoires, l'attribut group-id du module doit être précisé.
Cette partie permet de configurer le ou les éléments à gérer par le cluster. Elle est définie dans le tag <dso> du tag fils <application>.
La configuration de ces éléments se fait au travers de trois entités principales :
Les racines permettent de définir l'objet qui sera géré par le cluster en tant qu'objet père d'une hiérarchie d'objets composés de ses dépendances.
Les racines sont précisées dans un tag <roots> fils du tag application/dso.
Chaque objet racine est défini dans un tag <root>.
Exemple : |
<?xml version="1.0" encoding="UTF-8"?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
...
<application>
<dso>
<roots>
<root>
<field-name>fr.jmdoudoux.dej.terracotta.MaClasse.monChamp
</field-name>
</root>
</roots>
...
</dso>
</application>
</tc:tc-config>
Le tag fils <field-name> permet de préciser le champ d'une classe qui sera la racine du graphe d'objets gérés par le cluster. Ce graphe contient les dépendances de l'objet racine.
Le tag fils <root-name> permet de fournir un identifiant à la racine permettant d'y faire référence.
Les verrous sont précisés dans un tag <autolock> fils du tag application/dso/locks.
Il existe deux types de verrous :
Ces verrous définissent les méthodes qui posent des verrous afin de permettre à Terracotta de les propager à tous les noeuds du cluster, assurant ainsi une gestion sécurisée et distribuée des accès concurrents.
Cette propagation est assurée par des traitements ajoutés lors de l'instrumentation : les verrous indiquent à Terracotta qu'il faut instrumenter le code de la méthode pour prendre en charge des verrous distribués permettant ainsi de gérer les accès concurrents sur les objets.
Exemple : |
<?xml version="1.0" encoding="UTF-8"?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
...
<application>
...
<dso>
<locks>
<autolock>
<method-expression>* fr.jmdoudoux.dej.terracotta.MaClasse.get*(..)
</method-expression>
<lock-level>read</lock-level>
</autolock>
<autolock>
<method-expression>* fr.jmdoudoux.dej.terracotta.MaClasse.set*(..)
</method-expression>
<lock-level>write</lock-level>
</autolock>
</locks>
...
</dso>
</application>
</tc:tc-config>
Le tag fils <autolock> permet de définir un autolock.
L'attribut auto-synchronized permet de rajouter dynamiquement, au chargement de la classe, le modificateur synchronized sur la méthode. Ceci est pratique pour éviter de modifier le code source ou si le code source n'est pas modifiable. L'utilisation de cette option est cependant à utiliser avec parcimonie et pertinence.
Un autolock sur une méthode ou un ensemble de méthodes est défini grâce au tag fils <method-expression> en utilisant une syntaxe particulière empruntée à AspectWerkz.
Résultat : |
* fr.jmdoudoux.dej.terracotta.MaClasse.get*(..)
* fr.jmdoudoux.dej.terracotta.MaClasse.set*(..)
void fr.jmdoudoux.dej.terracotta.MaClasse.setChamp(java.lang.String)
void *..*(..)
Important : il faut limiter le nombre de méthodes à instrumenter pour ne pas dégrader les performances au chargement des classes et au runtime.
Le tag fils <lock-level> permet de définir le niveau du verrou. Quatre niveaux sont définis : write, read, concurrent et synchronous-write. Le niveau par défaut est write.
Les classes à instrumenter sont celles :
L'instrumentation des classes est définie dans le tag fils <instrumented-classes>.
Le tag fils <include> permet de définir les classes à instrumenter et le tag fils <exclude> permet de définir les classes à ne pas instrumenter.
Le tag <class-expression> fils du tag <include> permet de définir un ensemble de classes en utilisant une syntaxe particulière empruntée à AspectWerkz.
Résultat : |
fr.jmdoudoux.dej.terracotta.MaClasse
fr.jmdoudoux.dej.terracotta.Ma*
fr.jmdoudoux.dej.terracotta.*
fr.jmdoudoux*.terracotta.*
fr.jmdoudoux.dej..
Le tag <exclude> utilise la même syntaxe.
Le tag fils <transient-fields> permet de définir des champs d'objets gérés par le cluster et à ne pas traiter.
C'est notamment pratique pour des champs qui ne sont pas marqués avec le modificateur transient dans le code source.
Chaque champ est défini grâce à un tag <field-name>.
Le tag fils <distributed-methods> permet de définir des méthodes d'objets gérés par le cluster qui doivent être invoquées dans tous les noeuds du cluster dès qu'elles sont invoquées dans un des noeuds.
Chaque méthode est définie grâce à un tag <method-expression>.
L'attribut run-on-all-nodes permet de préciser dans quel noeud l'invocation sera faite : true (valeur par défaut) demande l'invocation dans tous les noeuds, false demande l'invocation dans tous les noeuds qui possèdent déjà une référence sur l'objet.
Le tag fils <web-applications> permet de définir une ou plusieurs applications de type web dont le contenu de la session sera géré par le cluster
Chaque application web doit être ajoutée avec un tag <web-application> qui contient son nom.
Terracotta propose plusieurs fonctionnalités qui peuvent être combinées selon les besoins pour assurer la fiabilité, la disponibilité et la montée en charge du cluster :
A son lancement, une JVM cliente se connecte au serveur Terracotta actif pour interagir avec lui. Dans un cluster avec un serveur actif, il est possible de définir un ou plusieurs serveurs passifs. Si le serveur actif est arrêté, un des serveurs passifs est promu actif. Les clients du cluster tentent alors de se connecter au serveur actif parmi les serveurs configurés.
Si un serveur passif est configuré, l'arrêt du serveur actif est transparent pour les clients puisque ce serveur prend le relais en tant que serveur actif : les clients tentent de se connecter à ce serveur une fois qu'il est devenu actif. De nouveaux clients peuvent toujours rejoindre le cluster.
Si aucun serveur passif n'est configuré, le cluster est inutilisable tant que le serveur n'est pas redémarré. Les clients connectés avant l'arrêt attendent de pouvoir se reconnecter. Si le serveur était en mode persistant, les données sont restaurées et les clients peuvent se reconnecter. Si le serveur était en mode non persistant, les données sont perdues et les clients ne peuvent pas se reconnecter, ils doivent être arrêtés et démarrés pour se connecter au cluster.
Si un client ne peut se connecter à aucun serveur, il reste en attente tant qu'il n'arrive pas à se reconnecter à un serveur du cluster dans un temps configurable.
Un serveur peut être configuré pour être :
Si plusieurs serveurs sont utilisés en mode persistant, ils doivent être configurés pour écrire leurs données dans un système de fichiers partagés qui soit capable de gérer les accès concurrents en posant des verrous.
Il est possible d'utiliser et de configurer plusieurs serveurs Terracotta afin d'assurer une haute disponibilité du cluster Terracotta. Celle-ci repose sur plusieurs fonctionnalités selon le niveau de sûreté souhaité :
La mise en oeuvre de ces fonctionnalités est effectuée dans le fichier de configuration.
L'état du cluster suite à l'arrêt du serveur actif (de façon volontaire ou non) dépend de deux facteurs :
Serveur actif | Serveur passif |
Etat du cluster |
non persistent | non |
Le cluster est inutilisable jusqu'au rédémarrage du serveur. A ce moment :
|
non persistent | oui |
Le cluster continue de fonctionner car le serveur passif devient actif |
persistent | non |
Le cluster est inutilisable jusqu'au redémarrage du serveur. A ce
moment :
|
persistent | oui |
Le cluster continue de fonctionner car le serveur passif devient actif |
Cette configuration ne propose ni fiabilité, ni haute disponibilité, ni montée en charge.
Cette configuration est pratique dans un environnement de développement mais n'est absolument pas recommandée en production.
La configuration minimale du cluster doit utiliser :
Exemple : |
<?xml version="1.0" encoding="UTF-8" ?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-5.xsd">
<servers>
<server name="noeud1">
...
<dso>
<persistence>
<mode>temporary-swap-only</mode>
</persistence>
</dso>
</server>
...
</servers>
...
</tc:tc-config>
Pour démarrer le serveur, il suffit d'exécuter le script start-tc-server. L'option -f permet de préciser la localisation du fichier de configuration tc-config.xml.
Cette configuration propose la fiabilité mais ne propose ni la haute disponibilité ni la montée en charge.
La configuration du cluster pour la fiabilité doit utiliser :
Le mode persistant des données gérées par le cluster le rend plus fiable dans la mesure où les données sont restaurées si le cluster est redémarré.
Exemple : |
<?xml version="1.0" encoding="UTF-8" ?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-5.xsd">
<servers>
<server name="noeud1">
...
<dso>
<persistence>
<mode>permanent-store</mode>
</persistence>
</dso>
</server>
...
</servers>
...
</tc:tc-config>
Cette configuration n'est généralement pas souhaitable telle quelle ni dans un environnement de développement (la persistance des données n'est pas nécessaire et est même parfois peu pratique car la purge des données est manuelle), ni dans un environnement de production (car elle n'assure pas la haute disponibilité).
En production, il est nécessaire d'assurer la haute disponibilité du cluster notamment en mettant en place un failover sur le serveur. La configuration du cluster pour une haute disponibilité doit utiliser :
Dans cette configuration, un serveur actif communique avec les clients du cluster. Au moins un serveur passif attend de prendre le relai pour devenir le serveur actif en cas de défaillance. Dans cette configuration, un seul serveur peut être actif.
Terracotta synchronise automatiquement les serveurs pour permettre aux serveurs passifs d'être dans le même état que le serveur actif et ainsi de pouvoir prendre sa place en cas de défaillance.
Si les serveurs sont démarrés simultanément, l'un d'entre-eux est choisi pour être le serveur actif, les autres sont passifs. Lors de démarrage d'un serveur, si un serveur actif est en cours d'exécution, son état est synchronisé avec le serveur démarré.
En cas d'arrêt du serveur actif, un des serveurs passifs est promu actif.
Le mode actif-passif est configuré dans le tag <mode> fils du tag servers/ha avec la valeur networked-active-passive qui est celle recommandée car elle assure la synchronisation par le réseau.
Exemple : |
<?xml version="1.0" encoding="UTF-8" ?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-5.xsd">
<servers>
<server host="host2" name="noeud1">
...
</server>
<server host="host1" name="noeud2">
...
</server>
...
<ha>
<mode>networked-active-passive</mode>
<networked-active-passive>
<election-time>5</election-time>
</networked-active-passive>
</ha>
</servers>
...
</tc:tc-config>
Chaque serveur doit être défini dans un tag <server> avec obligatoirement pour chacun un attribut name unique.
Le tag <election-time> permet de préciser une durée en secondes pour déterminer le nouveau serveur actif. La valeur par défaut est 5 secondes.
Dans cette configuration, il est important que les répertoires de données précisés dans le tag <data> de chaque serveur soient différents et de préférence sur la machine locale pour améliorer les performances lorsque la persistance est requise.
Les répertoires des tags <logs> et <statistics> doivent aussi être différents.
Pour démarrer le serveur, il suffit d'exécuter le script start-tc-server avec l'option -n suivie du nom du serveur à démarrer. L'option -f permet de préciser la localisation du fichier de configuration tc-config.xml.
Cette configuration propose la fiabilité et la haute disponibilité. En production, il est nécessaire d'assurer la haute disponibilité et la fiabilité du cluster notamment en mettant en place un failover sur le serveur et la persistance des données.
La configuration du cluster pour une haute disponibilité et la fiabilité doit utiliser :
Cette configuration est une combinaison des configurations haute disponibilité et fiabilité.
Exemple : |
<?xml version="1.0" encoding="UTF-8" ?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-5.xsd">
<servers>
<server host="host2" name="noeud1">
...
<data>repertoire_partage_des_donnees</data>
<dso>
<persistence>
<mode>permanent-store</mode>
</persistence>
</dso>
</server>
<server host="host1" name="noeud2">
...
<data>repertoire_partage_des_donnees</data>
<dso>
<persistence>
<mode>permanent-store</mode>
</persistence>
</dso>
</server>
...
<ha>
<mode>networked-active-passive</mode>
<networked-active-passive>
<election-time>5</election-time>
</networked-active-passive>
</ha>
</servers>
...
</tc:tc-config>
Il est possible d'utiliser plus de deux serveurs, l'un étant le serveur actif qui communique avec les clients, les autres serveurs étant passifs.
Les serveurs passifs peuvent être configurés en mode persistant ou non persistant mais il est préférable de les configurer dans le même mode que le serveur actif.
Si le serveur actif en mode persistant s'arrête et que le nouveau serveur actif est non persistant, il faudra obligatoirement purger les données présentes sur le disque du serveur stoppé avant de le relancer. Le serveur actif ne conservant plus les données, il y aurait un risque d'incohérence entre les données en mémoires et celles sur disque.
En production, il est préférable de n'avoir qu'un seul fichier de configuration pour tous les éléments du cluster. Ceci n'est pas obligatoire mais cela facilite la gestion et la maintenance de placer le fichier de configuration à un seul endroit plutôt que de le répliquer pour chaque élément du cluster.
Le fichier de configuration est accessible aux différents serveurs en étant stocké dans un répertoire partagé. Lorsque le client se connecte à un serveur, il lui demande le contenu du fichier de configuration.
Dans le fichier de configuration, il faut que chaque serveur soit précisément identifié par son host et un nom unique.
Terracotta propose que les clients obtiennent le fichier de configuration du serveur auquel ils sont connectés. Pour cela, la variable d'environnement TC_CONFIG_PATH ne prend pas pour valeur le chemin du fichier de configuration mais contient le host suivi de deux-points et du port du serveur Il est possible de préciser les serveurs du cluster en les séparant par des virgules.
Cette configuration propose la fiabilité, la haute disponibilité et la montée en charge.
La montée en charge est proposée au travers de la fonctionnalité mirror groups qui permet de définir des groupes de serveurs. Chacun de ces groupes possède un serveur actif et un ou plusieurs serveurs passifs.
Cette fonctionnalité n'est pas prise en charge dans la version open source de Terracotta mais elle est disponible dans la version Enterprise.
Il est important pour le développeur de garder à l'esprit que pour que le cluster mis en oeuvre avec Terracotta fonctionne, l'application doit être codée en gérant correctement les accès concurrents.
Il est aussi nécessaire de prendre plusieurs facteurs en compte :
En cas de problème, Terracotta est assez verbeux dans l'exception levée et propose même une ou plusieurs pistes de corrections qui sont assez pertinentes.
Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |