La question posée dans ce article est de savoir s’il est opportun d’utiliser JavaJ2EE et donc un serveur d’application pour des traitements longs. Je pense notamment à des traitements batch ou à des traitements lourds provoqués par une demande d’un utilisateur sur une page web. Pour répondre à cette question il faut comprendre le fonctionnement d’un serveur d’application.
Principe de fonctionnement
Lorsqu’un serveur d’application reçoit à une demande cliente (HTTP, RMI, etc.), il utilise une thread pour y répondre puis la libère à la fin du traitement. Vous pouvez vérifier ce comportement avec une requête HTTP ou bien un MBean. Pour visualiser les threads dans les log il faut paramétrer le PatternLayout Log4J avec %t. Lorsque toutes les threads sont occupées, le serveur ne peut plus répondre aux demandes clientes.
Pour information, sur un serveur d’application, le nombre de thread est limité mais configurable. Par exemple, le nombre maximum de threads sur JBoss est de 100 par défaut.
Traitements longs
Dans un serveur d’application, certaines ressources ont une durée de vie ou d’activité limitée. Par exemple, une transaction sur une base de données ne peut pas excéder 5 minutes par défaut sur une serveur JBoss sous peine de provoquer l’exception suivante :
Transaction is not active: tx=TransactionImple < ac, BasicAction: -3f57f10b:911:49fb4a4b:3e status: ActionStatus.ABORT_ONLY >; - nested throwable: (javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: -3f57f10b:911:49fb4a4b:3e status: ActionStatus.ABORT_ONLY >)
La seule solution est de découper son traitement en plusieurs traitements plus petit ou de modifier le timeout. Dans ce dernier cas, il faut faire attention car un timeout top grand peut avoir des effets néfastes sur les performances de l’application. Voici comment gérer les démarcations de transaction pour découper un traitement long en plusieurs petits traitements :
Client (sans transaction) -> EJB (transaction supportée : TransactionAttributeType.SUPPORTS) -> EJBs (nouvelle transaction pour chaque appel EJB : TransactionAttributeType.REQUIRED ou TransactionAttributeType.REQUIRES_NEW)
Dissociation entre le web et les traitements lourds
Après avoir vu le principe de fonctionnement d’un serveur d’application et comment développer un traitement long sans provoquer d’exception de timeout, il ne faut pas oublier qu’une application web peut être utilisée par un grand nombre d’utilisateurs. Si les requêtes HTTP provoquent des traitements longs cela peut rapidement provoquer une indisponibilité du serveur.
Une des solutions possibles est d’utiliser deux serveurs d’applications différents : un serveur d’application pour les traitements lourds et un pour l’IHM. Les échanges entre ces deux serveurs doivent se faire en asynchrone pour ne pas pénaliser la partie web de l’application. Ainsi une augmentation du nombre d’utilisateur n’impactera pas votre application, à moins de mal configurer le serveur s’occupant de l’IHM.
Au final, tout n’est qu’une question de paramétrage des serveurs. Il ne faut pas oublier qu’avec un serveur d’application le clustering est toujours possible, et dans ce cas, le nombre de threads disponibles pour traiter les demandes augment lui aussi.
Vous pouvez utiliser le serveur d’application pour exécuter seulement des batchs. il faut alors vous assurer que le nombre de threads disponibles sur le serveur soit assez grand et que vos traitements successifs n’engendrent pas de problèmes de mémoire ou de réservation de ressources, car votre serveur ne sera pas réinitialisé au début de chaque traitement.
Cas du conteneur embarqué
Si vous préférez avoir une JVM par traitement et donc une architecture plus proche d’un batch classique, des conteneurs embarqués sont désormais disponibles et ils fonctionnent très bien. J’ai déjà testé openEJB et JBossEmbedded, glassfish, etc. je n’ai jamais remarqué de problème sur leur utilisation. De plus, dans ce cas, comme il y a une JVM non partagée par traitement, les problèmes de disponibilités lié à l’utilisation de thread n’existe plus. J’ajouterai que l’utilisation d’un conteneur embarqué est très simple, voici un exemple avec openEJB :
Créer un project maven avec l’archetype quickstart et ajouter openEJB dans les dépendances. Voici l’allure du fichier POM :
<dependencies>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>openejb-core</artifactId>
<version>3.1.2</version>
<exclusions>
<exclusion>
<groupId>org.apache.activemq</groupId>
<artifactId>activeio-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
L’exclusion d’activeMq est nécessaire car l’artefact n’existe pas sur le repository maven. L’étape suivante est de rajouter deux fichiers de configuration : jndi.properties avec la ligne
java.naming.factory.initial = org.apache.openejb.client.LocalInitialContextFactory
et un fichier “vide” META-INF/ejb-jar.xml qui ne contient que la ligne :
<ejb-jar/>
Ce fichier XML vide indique au conteneur de rechercher les EJBs présents dans le classpath. Le serveur embarqué se démarre et déploie les services EJB tout seul et vous n’avez plus qu’a les appeler :
public class App {
public static void main( String[] args ) {
try {
Context context = new InitialContext();
HelloWorldService helloWorldService =
(HelloWorldService) context.lookup("HelloWorldServiceHandlerLocal");
System.out.println(helloWorldService.sayHello("Smith"));
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Pour plus de précisions, voici l’URL où vous pouvez retrouver mon exemple : https://subversion.assembla.com/svn/everythingiswrong/tutorial-embedded-openejb/
Conclusion
Pour conclure cet article, je dirais qu’utiliser un serveur d’application pour ses batchs ou traitements longs est très intéressant car cela vous permet de disposer de la stack Java J2EE. Cela peut éventuellement vous permettre de mutualiser du code entre traitement batch et IHM, si vous avez une application qui nécessite les deux. Si l’utilisation d’un “vrai” serveur d’application vous fait peur, il est toujours possible d’utiliser un conteneur embarqué pour bénéficier, là aussi, de la stack logiciel fournie par Java J2EE.
Les traitements longs sur les serveurs d’applications J2EE ne sont donc qu’une question de paramétrage. Ils ne sont pas à proscrire, surtout si vous utilisez un conteneur embarqué.
Pour ceux qui ne sont toujours pas convaincus, voici 4 articles intéressants sur les batchs J2EE et le multithreading dans un serveur J2EE :