Services de mobilité et de persistance des applications Java
Transcription
Services de mobilité et de persistance des applications Java
Services de mobilité et de persistance des applications Java Sara Bouchenak and Daniel Hagimont Chapter 6 of Les intergiciels – développemens récents dans CORBA, Java RMI et les agents mobiles , Edited by Hermès, 2002. 149 Chapitre 6 Services de mobilité et de persistance des applications Java 6.1. Introduction Un service de capture/restauration de l’état des flots de contrôle a de nombreux domaines d’utilisation, tels que la mobilité des applications ou la persistance des applications. Ce service, seul ou complété par d’autres, peut aider à la mise en œuvre d’outils comme l’équilibrage dynamique de charge entre plusieurs machines [NIC 87], la diminution du trafic réseau par déplacement des clients vers leur serveur [DOU 92], la reconfiguration dynamique d’applications distribuées [BEL 98, HOF 93], la mise en œuvre de plates-formes à agents mobiles [CHE 95] ou l’administration de machines [OUE 96]. Il peut aussi servir à la tolérance aux pannes [OSM 97] et au débogage des applications. La fourniture de ces outils dépend en partie de la disponibilité d’un service de capture/restauration de l’état des flots de contrôle. Le développement d’applications réparties mettant en œuvre un grand nombre de machines interconnectées par des réseaux généraux, notamment l’Internet, est un domaine de recherche en plein essor.1Dans ce contexte, le langage à objets Java a connu un rapide succès. Le compilateur Java fournit un code intermédiaire (byte code) interprété par une machine virtuelle, la Java Virtual Machine ou JVM. La JVM est implantée sur la plupart des stations de travail et des ordinateurs personnels, Chapitre rédigé par Sara BOUCHENAK et Daniel HAGIMONT. 150 Les intergiciels ce qui fait que le couple langage Java-JVM est considéré comme la plate-forme de référence des applications développées pour l’Internet. Un des intérêts majeurs de la très grande diffusion de cette plate-forme est qu’elle permet de considérer l’ensemble des machines virtuelles Java de l’Internet comme une base homogène pour construire des services répartis, et notamment des services systèmes. La machine virtuelle Java fournit plusieurs services facilitant le développement des applications : - les services de sérialisation et désérialisation permettent respectivement de capturer et de restituer l’état d’un graphe d’objets Java ; ces services peuvent être utilisés pour déplacer des objets entre différentes machines ou pour sauvegarder des objets sur le disque ; - le chargement dynamique des classes permet de déplacer du code Java vers d’autres nœuds. En revanche, Java ne fournit pas de service permettant de capturer et de restituer l’état d’un flot de contrôle (ou thread), car la pile Java d’un flot de contrôle n’est pas accessible. Un tel service permet d’une part d’extraire l’état courant de l’exécution du flot, et d’autre part de restituer cet état pour que l’exécution reprenne au point où elle en était au moment de la capture. Dans ce chapitre, nous décrivons tout d’abord la réalisation d’un service de capture/restauration d’état des flots Java, un service générique qui peut être utilisé aussi bien pour la mobilité des applications Java que pour leur persistance. Nous présentons ensuite les expérimentations effectuées avec ce service et le résultat des mesures de performances. La suite de ce chapitre est organisée comme suit : le paragraphe 6.2 décrit l’état de l’art. Les paragraphes 6.3 et 6.4 présentent respectivement la conception et la réalisation de notre service de capture/restauration d’état des flots Java. Le paragraphe 6.5 présente la mise en œuvre de la mobilité et de la persistance des applications Java grâce à notre service. Quelques expérimentations effectuées avec le service fourni, une discussion et une évaluation de ce service sont décrites aux paragraphes 6.6 et 6.7. Enfin, une conclusion est proposée au paragraphe 6.8. 6.2. Etat de l’art Il existe principalement trois approches pour aborder le problème de capture et de restauration de l’état des flots Java : une approche dite explicite, une approche dite implicite se basant sur un préprocesseur de code et une autre approche implicite qui étend la JVM. Services de mobilité et de persistance des applications Java 151 La première approche, appelée gestion explicite, consiste à laisser entièrement à la charge du programmeur de l’application la gestion de la capture et de la reconstruction de l’état de son application. Le programmeur doit donc ajouter du code qui effectue ce travail, en des points fixes de l’application, dits points de sauvegarde. En un point de sauvegarde, le code ajouté doit enregistrer l’état de l’exécution : il note la dernière instruction exécutée et l’état courant de l’application. Lors de la reprise de l’exécution, le premier traitement effectué est un branchement vers le dernier point de sauvegarde enregistré ; ainsi, l’exécution peut reprendre au point où elle a été interrompue. Cette solution manque de souplesse, car la gestion des points de sauvegarde est entièrement à la charge du programmeur. De plus, l’ajout ou la modification de points de sauvegarde induit une modification de l’application elle-même. Cette gestion explicite est utilisée dans les applications faisant appel à des plates-formes à agents mobiles [CHE 95] fournissant une migration faible tels que les Aglets [IBM 96] ou Mole [BAU 98]. Les deux autres approches, dites implicites, fournissent un service transparent de capture/restauration de l’état des flots de contrôle. Le service fourni est indépendant du code de l’application ; il se présente sous la forme d’une primitive appelée par l’application elle-même ou par une application externe. Ces deux approches diffèrent par leur mise en œuvre : - la première approche implicite consiste à fournir un préprocesseur qui injecte du code soit dans le code source de l’application, soit dans son code intermédiaire Java. Lors de l’exécution de l’application, le code injecté crée un objet Java, que l’on nommera backup, et l’associe à l’application. Au fur et à mesure que l’application s’exécute, le code injecté sauvegarde dans l’objet backup l’état de l’exécution (les appels de méthodes, les valeurs des variables locales des méthodes). Lorsque l’application désire capturer son état, il lui suffit d’utiliser l’objet backup que le préprocesseur lui a associé. Pour restaurer l’état d’une application, les informations sauvegardées dans l’objet backup sont utilisées pour réinitialiser l’état de l’application : une réexécution partielle de l’application est effectuée pour reconstruire la pile d’exécution et réinitialiser les valeurs des variables locales. La principale motivation de cette approche est qu’elle ne modifie pas la JVM. Mais son inconvénient est que d’une part, elle induit un surcoût non négligeable sur les performances de l’application (dû au code injecté par le préprocesseur) [BOU 00b] et que d’autre part, le coût de l’opération de restauration de l’état est important en raison de la réexécution partielle de l’application. Fünfrocken propose une plate-forme à agents mobiles se basant sur un préprocesseur qui instrumente le code source des applications Java [FUN 98] et Truyen, ou encore Sakamoto proposent des implantations de mécanismes de capture d’état de flots de contrôle Java basés sur un préprocesseur qui instrumente le code intermédiaire de l’application [SAK 00, TRU 00] ; 152 Les intergiciels - la seconde approche implicite consiste à étendre la machine virtuelle Java pour en extraire l’état des flots et le rendre accessible par les programmes Java. Cette extension doit permettre de capturer l’état courant d’un flot et de le stocker dans un objet Java. L’extension doit également permettre de créer un nouveau flot et de l’initialiser avec un état préalablement capturé. Un service de capture/restauration construit en suivant cette approche n’est certes utilisable que sur des machines virtuelles étendues. Nous avons tout de même choisi cette approche pour les deux raisons suivantes : - elle réduit le surcoût induit par le service sur les performances de l’application (pas de code injecté) et réduit le coût du service de capture/restauration, car sa mise en œuvre dans la machine virtuelle Java est principalement native (en C), - du fait que ce service ait plusieurs domaines d’applications, nous pensons que c’est un service de base qui doit être intégré à la JVM. Cette approche a été suivie pour la réalisation de la plate-forme à agents mobiles Sumatra [RAN 97]. Contrairement à cette plate-forme qui fournit un mécanisme de mobilité pour ses agents, notre service de capture de flots est plus général : c’est une sorte de sérialisation/désérialisation de flots de contrôle Java. Ce service peut ainsi être spécialisé et utilisé pour la mobilité et pour la persistance des applications [BOU 99]. 6.3. Conception du service de capture/restauration d’état des threads Java Notre service de capture/restauration d’état des flots de contrôle Java a été intégré à la version Java 2 SDK (ou JDK 1.2) de la machine virtuelle Java [SUN 00a]. Nous décrivons tout d’abord la structure de l’état d’un flot dans la machine virtuelle, puis présentons les principes et les choix de conception du service de capture/restauration d’état. 6.3.1. Etat d’un thread Java Une machine virtuelle Java peut supporter l’exécution de plusieurs flots de contrôle ou threads [LIN 96]. L’état d’un thread Java, illustré par la figure 6.1, est constitué de trois structures de données principales : - la pile Java. Elle est associée à chaque flot de contrôle de la machine virtuelle ; cette pile décrit l’état d’avancement de l’exécution du flot de contrôle. La pile Java contient des régions ou frames (figure 6.2) ; une région est créée lors de l’appel d’une méthode Java puis détruite lors de la terminaison de la méthode associée. La région contient, entre autres informations, les variables locales à la méthode et les résultats des calculs intermédiaires ; les valeurs des Services de mobilité et de persistance des applications Java 153 variables locales ou des résultats intermédiaires peuvent être de différents types : entier, réel, référence d’objet, etc. De plus, une région contient des registres tels que le registre de sommet de pile ou le registre compteur ordinal indiquant la prochaine instruction de code intermédiaire (byte code) à exécuter (si la méthode associée à la région est une méthode Java et non une méthode dite « native » dont le code n’est pas interprété) ; - le tas d’objets. Il y a un tas d’objets par machine virtuelle, partagé par tous les flots de contrôle de la machine virtuelle. Il regroupe l’ensemble des objets Java créés et utilisés par les flots de contrôle de la JVM ; - la zone des méthodes. La machine virtuelle Java possède une zone des méthodes partagée par l’ensemble des flots de contrôle de cette machine. Cette zone regroupe l’ensemble des classes Java utilisées par les flots de la JVM. Référence de classe Zone de méthodes Référence d’objet Pile Java Tas d’objets Figure 6.1. Etat d’un thread Java opérande opérande Frame pc variable n Pile Java variable 1 Figure 6.2. Frame Java L’état d’exécution d’un flot de contrôle Java est donc constitué des trois ensembles d’informations suivants : sa pile Java, son tas d’objets constitué de l’ensemble des objets qu’il manipule (objets auxquels la pile Java fait référence) et sa zone de méthodes constituée des classes qu’il utilise. Les références des classes utilisées par le flot de contrôle sont également stockées dans sa pile Java. 154 Les intergiciels 6.3.2. Principe du service de capture/restauration Le service que nous proposons doit fournir deux fonctions : la capture de l’état courant de l’exécution d’un flot de contrôle Java et la restauration de cet état. Ce service permet en premier lieu d’interrompre un flot Java en cours d’exécution et d’extraire son état courant. Cet état peut ensuite être intégré à un nouveau flot Java qui, une fois lancé, démarre son exécution au point même où le précédent a été interrompu. La capture de l’état d’exécution d’un flot Java revient à construire une structure de données qui contient toutes les informations permettant de restituer cet état. Cette structure de données doit donc contenir toutes les informations nécessaires à la reconstruction de la pile Java du flot, de son tas d’objets et de sa zone de méthodes. Pour extraire l’état d’exécution d’un flot Java, il faut tout d’abord capturer la pile qui est associée à ce flot. Il faut ensuite identifier l’ensemble des objets et des classes Java qui sont utilisés par ce flot ; ceci se fait en parcourant la pile Java du flot pour y récupérer les références vers les objets et les classes Java. Finalement, une structure de données Java (un objet) est construite, contenant toutes ces informations. La restauration d’un état d’exécution revient à reconstruire un état d’exécution à partir de la structure de données obtenue lors d’une opération de capture. Cette opération de restauration consiste donc à construire un nouveau flot de contrôle Java, avec une pile Java, un tas d’objets et une zone de méthodes identiques à ceux du flot ayant subi l’opération de capture. Après une opération de capture d’état, un objet Java est construit. Cet objet contient des informations sur la pile, les objets et les classes utilisés par le flot. Pour construire des services de plus haut niveau, tels que la mobilité ou la persistance des flots, notre service de base de capture/restauration d’état des flots doit pouvoir se combiner à d’autres services Java tels que la sérialisation d’objets et le chargement dynamique des classes. Ainsi : - la sérialisation sera utilisée pour le transfert des objets, - le chargement dynamique des classes sera utilisé pour le transfert des classes. 6.3.3. Choix de conception Deux principaux problèmes ont été rencontrés lors de la conception du service de capture/restauration de l’état d’exécution des flots de contrôle Java. Le premier est dû au fait que l’état d’exécution d’un flot n’est pas accessible par les programmes Java. Le second problème est dû au fait que la pile Java associée à un flot n’est pas portable sur des architectures hétérogènes. Services de mobilité et de persistance des applications Java 6.3.3.1. 155 Etat non accessible L’état d’exécution d’un flot de contrôle est interne à la machine virtuelle Java standard. Cet état n’est pas accessible par les programmes Java et ne peut donc être directement capturé. Pour faire face à ce problème, nous avons étendu la machine virtuelle pour en extraire l’état d’exécution des flots de contrôle et permettre ainsi leur capture. 6.3.3.2. Etat non portable L’état d’exécution d’un flot de contrôle est constitué de trois structures de données : la pile Java, le tas d’objets et la zone des méthodes. Le tas d’objets et à la zone des méthodes sont respectivement constitués d’objets Java et de classes Java, donc de structures de données portables sur des architectures hétérogènes. Contrairement à ces deux ensembles, la pile Java est une structure de données native (structure C) ; le codage des informations contenues dans cette pile peut différer d’une architecture de machine à une autre. La structure de pile Java n’est donc pas directement portable sur des architectures hétérogènes. Le service de capture d’état d’un flot doit donc traduire cette structure native en une structure Java qui sera portable sur différentes architectures. Traduire la structure native de pile en une structure Java revient plus précisément à traduire les valeurs natives des variables locales et des résultats intermédiaires (figure 6.2) en des valeurs Java. Pour traduire une valeur native en une valeur Java, il faut connaître le type de cette valeur. En effet, un mot de quatre octets peut aussi bien représenter un entier qu’une référence d’objet. Mais la structure de pile Java ne donne aucune information sur le type des valeurs qu’elle contient. Le service de capture d’état d’un flot doit donc pouvoir retrouver le type des valeurs contenues dans la pile Java associée au flot. Nous proposons deux solutions à ce problème : une première solution qui consiste à étendre l’interprète Java et une seconde solution qui consiste à instrumenter le code exécuté par le flot. Nous avons mis en œuvre chacune de ces deux solutions. Dans ce chapitre, nous n’abordons pas les détails de réalisation de ces deux techniques, une description plus approfondie pouvant être trouvée dans [BOU 00a] et [BOU 00b]. L’évaluation présentée au paragraphe 6.7 concerne la technique qui consiste à étendre l’interprète Java. 6.4. Réalisation du service de capture/restauration Notre service de capture/restauration de l’état des flots Java propose aux programmeurs Java un nouveau package : le package java.lang.threadpack. Ce package fournit plusieurs classes telles que la classe ThreadState, dont les instances représentent l’état d’exécution de flots Java, et la classe ThreadStateManagement 156 Les intergiciels qui fournit les services de base nécessaires à la capture et à la restauration de l’état des flots. La figure 6.3 illustre une partie de l’interface fournie par la classe ThreadStateManagement. La méthode capture permet de capturer l’état courant d’un flot Java et de l’affecter à un objet ThreadState qui sera retourné en résultat de la méthode. Tandis que la méthode restore sert à créer un nouveau flot Java, à initialiser son état avec l’objet ThreadState passé en paramètre puis à lancer son exécution, celle-ci reprendra au point où elle a été interrompue au moment de la capture ; ce nouveau flot sera retourné en résultat de la méthode. java.lang.threadpack Class ThreadStateManagement public final class ThreadStateManagement extends Object The ThreadStateManagement class provides several useful services for the capture and restoration of Java thread states. Method Summary static ThreadState capture(Thread thread) Captures the state of the thread argument and returns it as a ThreadState object. static Thread restore(ThreadState threadState) Creates a new Java thread, initializes it with a previously captured state and starts its execution. static void captureAndSend(Thread thread, SendInterface sndItf, boolean toStop) Captures the state of the thread argument and sends it (to a remote node or to the disk) by calling the sendState method of the SendInterface interface. static Thread receiveAndRestore(ReceiveInterface rcvItf) Receives the state of a Java thread by calling the receiveState method of the ReceiveInterface interface, creates a new Java thread, initializes it with the received state and starts its execution. Figure 6.3. Interface du service de capture/restauration Les méthodes captureAndSend et receiveAndRestore permettent de spécialiser le service de capture/restauration en fonction des besoins de l’application qui l’utilise. En effet, la méthode captureAndSend permet, en plus de la capture de l’état d’un flot, de spécifier le traitement à effectuer à l’état capturé : celui-ci peut par exemple être envoyé à une machine distante pour des besoins de mobilité ou écrit sur le disque dans le cas de la persistance. La spécialisation du traitement à effectuer sur l’état capturé est possible grâce au paramètre de type SendInterface. En effet, un objet qui implante notre interface SendInterface fournit une méthode sendState, dans laquelle sera spécifié le traitement relatif à l’envoi de l’état capturé. Le troisième paramètre de la méthode captureAndSend permet de spécifier soit l’arrêt du flot dont l’état a été capturé, soit la reprise de l’exécution de ce flot. Ce paramètre sera par Services de mobilité et de persistance des applications Java 157 exemple mis à vrai pour de la migration de flot et il sera mis à faux pour du clonage de flot à distance. De façon symétrique, la méthode receiveAndRestore permet de spécifier le traitement à effectuer pour recevoir l’état d’un flot : celui-ci peut par exemple être lu à partir d’un fichier ou reçu à partir d’une machine distante. La spécialisation de ce traitement est possible grâce au paramètre de type ReceiveInterface. Un objet qui implante notre interface ReceiveInterface fournit une méthode receiveState, dans laquelle sera spécifié le traitement relatif à la réception de l’état à restaurer. Une fois l’état obtenu, celui-ci sera restauré dans un nouveau flot Java. 6.5. Services de mobilité et de persistance des flots Java En plus des services de bas niveau fournis par la classe ThreadStateManagement, nous proposons des services spécifiques à la mobilité des flots Java et des services spécifiques à la persistance des flots. La classe MobileThreadManagement appartenant au package threadpack fournit les services nécessaires à la mobilité des flots de contrôle Java. Une partie de son interface est illustrée par la figure 6.4. La méthode go permet de transférer l’exécution d’un flot vers une machine distante identifiée par une adresse IP et un numéro de port, et la méthode arrive permet de recevoir un nouveau flot à partir d’une machine dont l’adresse IP et le numéro de port sont passés en arguments de la méthode. java.lang.threadpack Class MobileThreadManagement public final class MobileThreadManagement extends Object The MobileThreadManagement class provides several useful services for making Java threads mobile. Method Summary static void go(Thread thread, String targetHost, int targetPort) Moves the execution of the thread argument to the machine specified by the host name and the port number arguments. static Thread arrive(String localHost, int localPort) Receives a thread on the machine specified by the host name and the port number arguments. Figure 6.4. Service de mobilité des threads Java La figure 6.5 illustre la réalisation des primitives go et arrive. Ces deux primitives ont été mises en œuvre en faisant appel aux méthodes génériques de la classe ThreadStateManagement qui sont captureAndSend et receiveAndRestore. Ces 158 Les intergiciels deux dernières méthodes ont été appelées en utilisant respectivement une instance de la classe MySender et une instance de la classe MyReceiver. La classe MySender, décrite par la figure 6.6, implante notre interface SendInterface : elle fournit une méthode sendState qui transfère l’état du flot vers la machine spécifiée. Et la classe MyReceiver, décrite également par la figure 6.6, implante notre interface ReceiveInterface : elle fournit une méthode receiveState qui reçoit l’état du flot de la machine spécifiée. public static void go(Thread thread, String targetHost, int targetPort) { MySender sndItf = new MySender(targetHost, targetPort) ; ThreadStateManagement.captureAndSend(thread, sndItf) ; } public static Thread arrive(String localHost, int localPort) { MyReceiver rcvItf = new MyReceiver(localHost, localPort) ; return ThreadStateManagement.receiveAndRestore(rcvItf) ; } Figure 6.5. Réalisation du service de mobilité class MySender class MyReveiver implements SendInterface { implements ReceiveInterface String host ; { int port ; String host ; int port ; MySender(String host, int port) { MyReceiver(String host, this.host = host ; int port) { this.port = port ; this.host = host ; } this.port = port ; public void } sendState(ThreadState state) { public ThreadState // Envoi de state à la machine receiveState( ThreadState state) { // <host, port> // Réception de l’état … // arrivant sur la machine } // <host, port> et retour de } // cet état en résultat … } } Figure 6.6. Réalisation du service de mobilité Services de mobilité et de persistance des applications Java 159 La méthode sendState établit une connexion vers la machine distante puis envoie l’objet ThreadState en utilisant la sérialisation Java. L’objet ThreadState fait luimême référence aux objets Java utilisés par le flot ; ces objets feront donc partie du graphe d’objets sérialisé. La méthode receiveState accepte une connexion à partir de la machine source puis reçoit l’objet ThreadState en utilisant la désérialisation Java. Les objets utilisés par le flot feront également partie du graphe d’objets reçus. Les classes utilisées par le flot et auxquelles l’objet ThreadState fait référence sont reçues en utilisant le chargement dynamique des classes. Pour la mise en œuvre de ces deux méthodes, les services Java standard de sérialisation et de chargement dynamique des classes ont été utilisés. Mais une spécialisation de ces services permettra d’adapter le transfert aux besoins de l’application. Un exemple de spécialisation sera présenté au paragraphe 6.6.2. De façon similaire, la classe PersistentThreadManagement a été réalisée pour fournir les services nécessaires à la persistance des flots Java. Une partie de son interface est illustrée par la figure 6.7. La méthode store permet de sauvegarder l’état d’exécution d’un flot dans un fichier identifié par un nom donné, et la méthode load permet de restaurer un flot Java à partir d’un état sauvegardé dans le fichier dont le nom est passé en argument de la méthode. Ces deux méthodes ont également été mises en œuvre en adaptant les méthodes génériques captureAndSend et receiveAndRestore. Les classes MobileThreadManagement et PersistentThreadManagement sont donc deux adaptations possibles de notre service générique de capture/restauration d’état des flots Java. De la même façon et pour des besoins spécifiques à une application, le programmeur de l’application peut adapter notre service générique et construire le service répondant au mieux aux attentes de son application. java.lang.threadpack Class PersistentThreadManagement public final class PersistentThreadManagement extends Object The PersistentThreadManagement class provides several useful services for making Java threads persistent. Method Summary static void store(Thread thread, String fileName) Saves the state of the thread argument in the file specified by the name argument. static Thread load(String fileName) Restores the execution of a Java thread from the state stored in the file specified by the name argument. Figure 6.7. Service de persistance des threads Java 160 Les intergiciels 6.6. Expérimentations Dans ce paragraphe, nous présentons tout d’abord deux expérimentations utilisant notre service de mobilité des applications Java. La première expérimentation sert à mettre en évidence l’utilité de la mobilité forte des flots de contrôle et donc, l’utilité de notre service de mobilité des flots Java. La seconde expérimentation montre les limitations de notre service de mobilité et tente d’y remédier en le combinant à d’autres services Java qui seront adaptés aux besoins de l’application. Finalement, une discussion décrira certains problèmes rencontrés et les solutions proposées. 6.6.1. Application récursive mobile : courbe fractale Nous pouvons considérer deux niveaux de mobilité des applications : la mobilité dite faible et la celle dite forte. La première ne prend en compte que l’état des données manipulées par l’application ; l’application mobile verra ainsi ses données modifiées mais devra, une fois arrivée sur sa nouvelle localisation, reprendre son exécution depuis le début. En revanche, la mobilité forte prend en compte, en plus de l’état des données, l’état de l’exécution de l’application (l’état de son flot de contrôle). Ainsi, une application « fortement mobile » poursuivra son exécution sur le site destination au point où elle a été interrompue sur le site source. Choisir la mobilité faible ou la mobilité forte dépend des besoins et des spécificités de l’application considérée. Prenons par exemple le cas d’une application Java récursive que l’on veut rendre mobile. Les appels récursifs se traduisent par une succession de régions (frames) sur la pile associée au flot. La mobilité faible, qui n’a pas accès à l’état du flot de contrôle, ne permettra pas de garder une trace des différentes régions (frames) stockées sur la pile : l’exécution, après migration faible, reprendra depuis le début. Nous avons considéré le cas d’une application graphique récursive Java : la courbe fractale du Dragon où l’on voit apparaître un petit dragon à un certain niveau de récursivité [TRI 93]. Nous avons mis en œuvre cette application et avons utilisé notre service de mobilité pour déplacer l’application récursive, au cours de son exécution, d’une machine à une autre. La figure 6.8 illustre cette expérimentation. L’application lancée sur une première machine est déplacée, au cours de son exécution, vers une seconde machine où elle poursuit son exécution, puis vers une troisième machine. Ces ordres de déplacement sont donnés par un flot extérieur au flot calculant la fractale, en faisant appel à la primitive go de la classe MobileThreadManagement sur le flot qui calcule la fractale. Cette application récursive a donc bénéficié de la mobilité forte fournie par notre service et n’a pas eu à reprendre son exécution sur les machines d’arrivée. Services de mobilité et de persistance des applications Java Migration Migration Machine 1 161 Machine 2 Machine 3 Figure 6.8. Fractale mobile 6.6.2. Adaptation de la mobilité : Talk L’objectif de cette seconde expérimentation est de montrer comment notre service de mobilité peut être combiné à d’autres services Java pour mieux s’adapter aux besoins de l’application qui l’utilise. Nous considérons ici une application qui se déplace entre des terminaux aux caractéristiques physiques différentes (ordinateur personnel, téléphone mobile, etc.). Notre service de mobilité des flots d’exécution Java est combiné à d’autres services Java tels que la sérialisation d’objets et le chargement dynamique des classes : - la sérialisation est utilisée pour transférer les objets Java entre les deux sites ; - le chargement dynamique des classes est utilisé pour transférer le code Java entre les deux sites. L’adaptabilité peut de ce fait intervenir à différents niveaux : au niveau de la sérialisation d’objets et au niveau du chargement dynamique des classes. Considérons l’application de Talk, illustrée par la figure 6.9, où deux utilisateurs échangent des messages. Chaque utilisateur possède deux canaux de communication : un canal entrant lui permettant de recevoir les messages de son interlocuteur, et un canal sortant lui permettant d’envoyer des messages à son interlocuteur. Les deux utilisateurs lancent initialement leur application de Talk sur leurs ordinateurs personnels, avec une interface utilisateur graphique. Au cours de l’exécution de l’application, un des utilisateurs décide de transférer son application vers une machine aux caractéristiques physiques minimales (un PDA par exemple) et d’y poursuivre l’exécution. 162 Les intergiciels Utilisateur 1 Utilisateur 2 Sérialisation Site 1 Site 2 Migration Désérialisation Chargement dynamique de classes Site 3 Figure 6.9. Application de Talk mobile L’appel à notre service de capture de l’état des flots Java permet de construire un objet Java contenant, entre autres informations, des renseignements sur les objets et les classes utilisés par l’application. Deux problèmes restent à résoudre : - le traitement des canaux de communication reliant les deux utilisateurs ; - la machine physique minimale ne peut supporter l’exécution d’une interface utilisateur graphique. En ce qui concerne les canaux de communication, une spécialisation de la sérialisation Java avant le transfert permet d’envoyer un message particulier à l’interlocuteur, le prévenant du prochain déplacement, puis à fermer les connexions. Une spécialisation de la désérialisation Java permet, après le transfert, de recréer les canaux de communication et de réinitialiser la connexion. Pour ce qui est de l’interface utilisateur, le service de chargement dynamique des classes peut être spécialisé pour utiliser une interface minimale textuelle sur le nouveau site d’accueil. Finalement, notre service de mobilité fournit une migration forte aux flots Java, et les services Java de sérialisation et de chargement des classes peuvent être spécialisés. La combinaison de ces trois services permet de construire une mobilité forte, adaptée aux besoins de l’application. Services de mobilité et de persistance des applications Java 163 6.6.3. Discussion L’application de Talk mobile, présentée ci-dessus, mettait en évidence certains problèmes rencontrés lors de la mise en œuvre de la mobilité de l’application. Dans ce paragraphe, nous développons les principales difficultés liées à la mise en place de la mobilité et de la persistance des applications. Prenons par exemple le cas de la mobilité d’un flot : - que se passe-t-il lorsqu’un flot se déplace d’un site source vers un site destination alors qu’il accédait à un objet utilisé également par d’autres flots du site source ?; - comment sont gérées les communications entre flots de contrôle lorsqu’un des flots mis en œuvre se déplace d’un site à un autre ?; - que se passe-t-il lorsqu’un flot est transféré sur un site destination alors qu’il faisait partie d’une application constituée de plusieurs flots sur le site source ? Nous abordons, une à une, chacune de ces questions et proposons certaines solutions. Que se passe-t-il lorsqu’un flot se déplace d’un site source vers un site destination alors qu’il accédait à un objet utilisé également par d’autres flots du site source ? Une première solution consiste à dupliquer l’objet partagé et à en transférer une copie avec le flot mobile [GAR 86]. Dans ce cas, il faut gérer la cohérence entre les différentes copies d’un même objet et la synchronisation d’accès à un objet. Une autre solution au problème de partage d’objets consiste à utiliser un lien de poursuite entre le site destination et le site source pour que le flot, sur le site destination, puisse accéder à l’objet sur le site source. Dans ce cas, un problème de disponibilité se pose lorsqu’une panne survient sur le site source [CHO 83, LU 87]. Comment sont gérées les communications entre flots de contrôle lorsqu’un des flots mis en œuvre se déplace d’un site à un autre ? Une première solution consiste à utiliser un lien de poursuite entre le site destination et le site source : les messages envoyés au flot déplacé transiteront par le site source. Dans ce cas, un problème de disponibilité en cas de panne du site source se pose. Une autre solution au problème de communication entre flots consiste à fermer les canaux de communication sur le site source et à en recréer de nouveaux sur le site destination. Dans ce cas, un problème d’acheminement des messages en transit sur les anciens canaux se pose, ainsi qu’un problème de désignation des nouveaux canaux. Que se passe-t-il lorsqu’un flot est transféré sur un site destination alors qu’il faisait partie d’une application constituée de plusieurs flots sur le site source ? Le flot peut être déplacé seul et communiquer avec les autres flots à distance. Il peut 164 Les intergiciels aussi être déplacé avec l’ensemble des flots ou une partie des flots intervenant dans l’application. Finalement, quel que soit le problème abordé, le choix de la solution est fortement dépendant des besoins de l’application considérée. Nous avons donc délibérément choisi de fournir un comportement par défaut pour nos services de mobilité/persistance, un comportement qui considère la machine virtuelle Java comme une machine centralisée (ce qu’elle est !). Quant aux aspects relatifs à la distribution et au partage, leur gestion est laissée à la charge du programmeur de l’application qui optera pour la solution la plus appropriée à son problème. 6.7. Evaluation Ce paragraphe présente tout d’abord les coûts de base de notre service : le coût d’une opération de capture d’état de flot Java et celui d’une opération de restauration d’état. Des coûts de plus haut niveau sont ensuite présentés, tels que celui d’une migration de flot Java entre deux machines et celui des opérations de sauvegarde et de reprise d’une exécution à partir d’une image disque. Les mesures de performances de nos services ont été effectuées sur : - la version JDK 1.2.2 de la machine virtuelle Java, - installée sur un processeur Sparc d’une fréquence de 167 Mhz, doté de 64 Mo de mémoire et d’un système Solaris 2.6, - sur un réseau Ethernet à 100 Mb/s. 6.7.1. Coûts de base La durée du traitement effectué par notre service de capture/restauration dépend de la taille de l’état du flot de contrôle Java lorsque celui-ci fait appel au service. La taille d’un flot dépend du nombre et de la taille des régions (frames) stockées sur la pile Java associée à ce flot. Nous ne présentons ici que les résultats des mesures de performances concernant l’influence du nombre de régions sur les coûts de notre service de capture/restauration. Pour faire varier le nombre de régions, nous nous sommes basés sur un calcul récursif (la fonction factorielle). La figure 6.10 présente la variation du coût d’une opération de capture d’état d’un flot Java en fonction du nombre de régions contenues dans la pile Java du flot au moment de la capture. La courbe montre que le coût d’une opération de capture d’état reste inférieur à 1 ms pour un nombre de régions inférieur à 10. Ce coût atteint ensuite 2 ms pour 20 régions et 9 ms pour 80 régions. Services de mobilité et de persistance des applications Java 165 12 Temps (ms) 10 8 6 4 2 0 0 20 40 60 80 100 Nombre de frames Capture Figure 6.10. Capture de l’état d’un thread La figure 6.11 présente la variation du coût d’une opération de restauration d’état d’un flot Java en fonction du nombre de régions contenues dans la pile Java du flot au moment de la capture. La courbe montre que le coût d’une opération de restauration ne varie pas beaucoup. Il est compris entre 0,6 et 0,9 ms pour un nombre de régions allant jusqu’à 75, puis atteint la milliseconde à partir de 80 régions. Finalement, les coûts de notre service de base de capture et de restauration sont très raisonnables, surtout si l’on considère des flots n’ayant pas un nombre important de régions sur leur pile Java au moment de la capture. 1,2 Temps (ms) 1 0,8 0,6 0,4 0,2 0 0 20 40 60 80 100 Nombre de frames Restauration Figure 6.11. Restauration de l’état d’un thread 6.7.2. Coûts de la migration La courbe en trait plein de la figure 6.12 représente la variation du coût d’une opération de migration de flot Java en fonction du nombre de régions contenues dans la pile Java du flot au moment de la migration. Cette figure montre que le coût d’une opération de migration est autour de 100 ms lorsque le nombre de régions est inférieur à 5. Ce coût atteint ensuite 185 ms pour 20 régions, 287 ms pour 40 régions 166 Les intergiciels et 500 ms pour 80 régions. Ce coût peut paraître important, mais il est en fait principalement dû au coût de transfert de l’état capturé. En effet, rappelons qu’une migration de flot d’une machine source à une machine destination consiste à capturer l’état courant du flot, transférer cet état de la machine source vers la machine destination puis restaurer l’état sur la machine destination. 60 0 Temps (ms) 50 0 40 0 30 0 20 0 10 0 0 0 20 40 60 80 10 0 N o m b re d e fram es M igra tio n T ra nsfert é tat Figure 6.12. Migration de thread La courbe en pointillés de la figure 6.12 présente le coût du transfert de l’état du flot entre deux machines. Nous constatons que cette courbe recouvre quasiment la courbe du coût de la migration. Le coût du transfert de l’état est de 100 ms lorsque le nombre de régions est inférieur à 5. Ce coût atteint ensuite 180 ms pour 20 régions, 279 ms pour 40 régions et 495 ms pour 80 régions. Le coût du transfert de l’état représente finalement 98 % du coût total d’une migration de flot Java. Le temps de transfert est en partie dû au temps de sérialisation du graphe d’objets. Ce temps de sérialisation peut être amélioré en utilisant l’externalisation Java [SUN 98]. L’externalisation permet d’écrire sa propre politique de transfert et de ne considérer que les informations nécessaires à la restitution du graphe d’objets. Elle peut être jusqu’à 40 % plus rapide que la sérialisation. 6.7.3. Coûts de la persistance Rendre un flot de contrôle Java persistant revient d’une part à pouvoir sauvegarder son état sur un support non volatil tel que le disque, et d’autre part à pouvoir reprendre l’exécution de l’état sauvegardé sur le disque. Nous avons donc mesuré le coût de la sauvegarde d’un flot Java sur le disque et celui de la reprise de l’exécution d’un flot à partir d’une image disque. Services de mobilité et de persistance des applications Java 167 300 Temps (ms) 250 200 150 100 50 0 0 20 40 60 80 100 Nombre de frames Sauvegarde fichier Ecriture fichier Figure 6.13. Sauvegarde de thread sur disque Sauvegarder un flot sur le disque revient à capturer son état puis à écrire cet état sur le disque. La courbe en trait plein de la figure 6.13 représente la variation du coût d’une opération de sauvegarde d’état de flot Java en fonction du nombre de régions contenues dans la pile Java du flot au moment de la sauvegarde. La courbe en pointillés de la figure 6.13 représente le coût de l’écriture de l’état sur le disque. Nous remarquons que les coûts d’une sauvegarde et d’une écriture sur le disque sont respectivement de 35 ms et 34 ms pour 5 régions, 46 ms et 45 ms pour 10 régions, 71 ms et 70 ms pour 20 régions et 124 ms et 115 ms pour 40 régions. Le coût d’une sauvegarde de flot sur le disque est principalement dû au coût de l’écriture de l’état capturé sur le disque : 97 % du temps total de la sauvegarde est passé dans l’écriture sur le disque. Ce temps d’écriture peut être amélioré d’une part en utilisant l’externalisation au lieu d’employer la sérialisation, et autre part en effectuant des écritures asynchrones sur le disque. 350 Temps (ms) 300 250 200 150 100 50 0 0 20 40 60 80 100 Nombre de frames Reprise d'un fichier Lecture d'état à partir d'un fichier Figure 6.14. Reprise d’un thread du disque De façon symétrique, la courbe en trait plein de la figure 6.14 représente la variation du coût d’une opération de reprise d’exécution à partir d’une image disque 168 Les intergiciels en fonction du nombre de régions contenues dans la pile Java du flot au moment de la capture. La courbe en pointillés représente le temps de lecture de l’état à partir du disque. Cette figure montre que le coût d’une opération de reprise est principalement dû à la lecture de l’état du flot à partir du disque : 99 % du temps total de reprise à partir d’une image disque est passé dans la lecture disque. 6.7.4. Evaluation de la JVM En plus de la mesure des performances de nos services, nous nous sommes intéressés à la mesure de l’éventuel surcoût induit par l’intégration de nos services à la machine virtuelle Java (JVM). Nous avons, en effet, étendu la JVM pour y intégrer nos services de capture/restauration de l’état des flots Java. Cette intégration s’est faite : - sans modification du langage Java, - sans modification du compilateur Java, - sans modification des API Java existantes, - sans modification de la mise en œuvre des API Java existantes. Notre extension de la JVM propose de nouvelles API Java et une mise en œuvre modulaire de ces API. Les nouvelles API fournissent des services de mobilité et de persistance des flots et des services de capture et de restauration de l’état d’exécution des flots. L’objectif de ce paragraphe est de montrer que les performances de notre JVM étendue restent inchangées par rapport aux performances de la JVM standard. Autrement dit, nous voulons montrer que l’ajout de nos services à la JVM n’a pas modifié les performances des autres services de la JVM. A cet effet, nous avons utilisé un benchmark de machine virtuelle Java appelé SciMark 2.0 [POZ 00]. SciMark 2.0 est un benchmark de calcul numérique scientifique Java. Il est constitué de cinq calculs : la transformée de Fourier rapide (FFT), la résolution de systèmes linéaires par la méthode Jacobi Successive OverRelaxation (SOR), l’intégration Monte Carlo, la factorisation de matrice LU et la multiplication de matrices (sparse matmut). Ce benchmark présente également un résultat global (composite score). Tous les résultats sont donnés en Mflops (millions d’opérations de flottants par seconde). Autrement dit, plus le score résultant d’un calcul est important, plus la JVM sous-jacente est performante pour ce type de calcul. Les calculs effectués par ce benchmark sont de petite taille pour éviter les problèmes engendrés par le manque d’espace mémoire et se concentrer sur les Services de mobilité et de persistance des applications Java 169 performances de calcul de la JVM. De plus, le benchmark a été exécuté en désactivant le compilateur Java JIT pour mesurer les performances réelles de la JVM. La figure 6.15 présente les résultats du benchmark SciMark 2.0, dans deux cas : - le cas de la JVM standard (rectangle blanc), - le cas de la JVM étendue de nos services (rectangle blanc à points noirs). Performances 2,0 1,5 1,0 0,5 (1 00 x1 00 ) N =1 00 0, ... Ca r lo Sp ar se m at m ul t( LU M on te (1 02 4) R SO FF T Sc or e e C om po sit (1 00 x1 00 ) 0,0 Tests JVM standard JVM étendue Figure 6.15. Evaluation de la JVM Dans ces deux cas, les calculs du benchmark sont exécutés par le thread principal de la JVM (thread qui exécute la méthode principale main). Les résultats des calculs montrent que notre JVM étendue présente les mêmes performances que la JVM standard. L’extension de la JVM avec nos services n’induit donc pas de perte de performances pour les autres services de la JVM (services utilisés par les calculs du benchmark). Ce résultat est dû au fait que notre extension de la JVM n’ait pas exigé la modification de la mise en œuvre des autres services de la JVM. 6.8. Conclusion et perspectives Un service de capture et de restauration de l’état des flots de contrôle (threads) a été ajouté à l’environnement Java. Ce service est générique et peut être instancié pour différents usages. Il a, par exemple, été utilisé comme base pour la mise en œuvre de services permettant la mobilité et la persistance des flots Java. Par ailleurs, ces services de mobilité et de persistance des flots peuvent être combinés à d’autres services Java, tels que la sérialisation d’objets et le chargement dynamique des classes. Cette combinaison permet de construire des services plus complets et qui répondent au mieux aux besoins de l’application qui les utilise. 170 Les intergiciels Des expérimentations qui mettent en évidence d’une part l’utilité de notre service de mobilité, et d’autre part la construction d’un service de mobilité adaptable, ont été effectuées et présentées dans ce chapitre. Une évaluation des performances des services fournis a également été menée et a montré que les coûts obtenus étaient acceptables. Nos services peuvent être utilisés comme base pour la mise en œuvre de la répartition dynamique de charge dans un environnement distribué, pour la mise en œuvre d’une machine Java persistante [JOR 96] ou d’une machine virtuelle Java répartie. Ce sont ces utilisations que nous abordons actuellement. Nous nous intéressons également au portage de nos services sur des machines virtuelles embarquées pour pouvoir bénéficier de la mobilité entre des machines rudimentaires telles que les téléphones ou les PDA [SUN 00b]. Notre extension de la machine virtuelle étant modulaire, le portage de nos services sur une autre implantation de JVM en est ainsi simplifié. 6.9. Bibliographie [BAU 98] BAUMANN J., HOHL F., STRABER M., ROTHERMEL K., « Mole - Concepts of Mobile Agent System », WWW Journal, Special issue on Applications and Techniques of Web Agents, vol. 1, n. 3, 1998. http://mole.informatik.uni-stuttgart.de/ [BEL 98] BELLISSARD L., DE PALMA N, RIVEILL M., « Dynamic Reconfiguration of Agent-Based Applications », 8th ACM SIGOPS European Workshop, Sintra, Portugal, septembre 1998, http://sirac.inrialpes.fr/~riveill [BOU 99] BOUCHENAK S., HAGIMONT D., « Capture et restauration du contexte d’exécution d’un thread dans l’environnement Java », 1ère Conférence Française sur les Systèmes d’Exploitation, Rennes, France, juin 1999. http://sirac.inrialpes.fr/~bouchena [BOU 00a] BOUCHENAK S., HAGIMONT D., « Approaches to Capturing Java Thread State », Middleware’2000, session de posters, New York, Etats-Unis, avril 2000. http://sirac.inrialpes.fr/~bouchena [BOU 00b] BOUCHENAK S., HAGIMONT D., « Pickling threads state in the Java system », Proceedings of 33rd International Conference on Technology of Object-Oriented Languages (TOOLS Europe’2000), Mont-Saint-Michel, France, juin 2000. http://sirac.inrialpes.fr/~bouchena [CHE 95] CHESS D., HARRISON C., KERSHENBAUM A., « Mobile Agents: Are They a Good Idea? », T.J. Watson Research Center, IBM Research Division, mars 1995. http://www.research.ibm.com/iagents/ publications.html Services de mobilité et de persistance des applications Java 171 [CHO 83] CHOU T.C.K., ABRAHAM J. A., « Load Redistribution under Failure in Distributed Systems », IEEE Transactions on Computers, vol. 32, n. 9, septembre 1983. [DOU 92] DOUGLIS F., MARSH B., « The Workstation as a Waysttion: Integrating Mobility into Computing Environment », The Third Workshop on Workstation Operating System (IEEE), avril 1992. http://www.douglis.org/fred [FUN 98] FÜNFROCKEN S., « Transparent Migration of Java-based Mobile Agents(Capturing and Reestablishing the State of Java Programs) », Proceedings of Second International Workshop Mobile Agents 98 (MA’98), Stuttgart, Allemagne, septembre 1998. http://www.informatik.tu-darmstadt.de/~fuenf [GAR 86] GARCIA-MOLINA H., « The Future of Data Replication », Symposium on Reliability in Distributed Software and Database Systems, 1986. [HOF 93] HOFMEISTER C., PURTILO J. M., « Dynamic Reconfiguration in Distributed Systems: Adapting Software Modules for replacement », Proc. of the 13th International Conference on Distributed Computing Systems, Pittsburgh, Etats-Unis, mai 1993. [IBM 96] IBM Tokyo Research Labs, Aglets Workbench: Programming Mobile Agents in Java, 1996. http://www.trl.ibm.co.jp/aglets [JOR 96] JORDAN M. J., « Early Experiences with Persistent Java », The First International Workshop on Persistence and Java (PJW’1), Drymen, Ecosse, septembre 1996. http://www.dcs.gla.ac.uk/pjama [LIN 96] 1996. LINDHOLM T., YELLIN F., Java Virtual Machine Specification, Addison Wesley, [LU 87] LU C., CHEN A., et LIU J., « Protocols for Reliable Process Migration, INFOCOM 1987, The 6th Annual Joint Conference of IEEE Computer and Communication Societies, mars 1987. [NIC 87] NICHOLS D. A., « Using Idle Workstations in a Shared Computing Environment », Proceedings of the Eleventh ACM Symposium on Operating Systems Principles, novembre 1987. [OSM 97] OSMAN T., BARGIELA A., « Process Checkpointing in an Open Distributed Environment », Proceedings of European Simulation Multiconference (ESM’97), juin 1997. [OUE 96] OUEICHEK I., Conception et réalisation d’un noyau d’administration pour un système réparti à objets persistants, Thèse de doctorat, institut national polytechnique de Grenoble, France, octobre 1996. [POZ 00] POZO R., MILLER B., SciMark 2.0 Documentation, 2000. http://math.nist.gov/scimark2