mercredi 20 juin 2012

L’objectif de CommonJ est de proposer une interface de développement pour effectuer des traitements en parallèle dans un contexte JEE. Traditionnellement, pour réaliser des traitements en parallèle, il faut créer des Threads distincts pour exécuter chaque traitement hors cette chose est strictement interdite sur un serveur d’application, car trop dangereuse et dévolue au moteur de l’instance.

Un pattern de contournement était d’utiliser un pool MDB et une file JMS pour déclarer des traitements en parallèle (la taille du pool étant le nombre de traitements en parallèle possibles), mais cela impose de déclarer et administrer une file JMS.

Le principe de CommonJ et de s’appuyer sur les WorkManager pour déclarer les Thread et de proposer une interface de développement pour lancer les traitements à déployer sur ces WorkManager.

Il faut donc développer spécifiquement avec l’interface de CommonJ afin de déclarer les traitements à effectuer en parallèle et de récupérer une référence d’un Workmanager que l’on déclarer dans la configuration Weblogic via la console ou via les descripteurs JEE.

Cette API est utilisée en interne de beaucoup de framework éditeur comme Oracle Service Bus mais peut rependu dans les développements classique JEE car peut connu.

DEVELOPPEMENT



Nous allons reprendre un excellent article sur le sujet comme exemple de développement. Cet article montre la façon d’implémenter une méthode de simulation de traduction de mot via une Servlet qui fonctionne soit en série soit en parallèle.


J’ai donc repris ce code exemple dans une Web Application que j’ai appelé CommonjWAR, j’ai créé deux Servlet ServletSequential et ServletParallel issue de AbstractServlet. J’ai modifié ce code pour déclarer non pas 5 actions, mais 100 traitements.

AbstractServlet
private List getClientInput() {
             
   ArrayList arrList = new ArrayList();
   for( int i = 0; i < 100; i++ ) { arrList.add( Integer.toString(i)); }
             
   return arrList;
   // return Arrays.asList(new String[] { "one", "two", "three", "four", "five" } );
}

Le traitement séquentiel est réalise par la Servlet ServletSequential avec une bête boucle.

for (Iterator iter = input.iterator(); iter.hasNext();) {
             
   String source = (String) iter.next();
   Translator translator = new DummyTranslator(source, DELAY);
   translator.translate();
   result.add(translator.getTranslation());
}

Le traitement en parallèle est déclare dans la Servlet ServletParallel qui récupérer via un lookup JNDI une référence à un WorkManager de nom wm/MyWorkManager.


try {
                    
   InitialContext ctx = new InitialContext();
   this.workManager = (WorkManager) ctx.lookup("java:comp/env/wm/MyWorkManager");
                    
} catch (Exception e) { throw new ServletException(e); }

On injecte ensuite les traitements à réaliser sur le WorkManager, on attend la fin de l’ensemble des traitements que l’on re agrège

for (Iterator iter = input.iterator(); iter.hasNext();) {
                    
   String source = (String) iter.next();
   Translator translator = new DummyTranslator(source, 10 * 1000);

   // schedule
   Work work = new WorkTranslatorWrapper(translator);
   WorkItem workItem = this.workManager.schedule(work);
   jobs.add(workItem);
}

System.out.println( System.currentTimeMillis()  + " : All jobs scheduled at : " );
// wait for all jobs to complete
this.workManager.waitForAll(jobs, WorkManager.INDEFINITE);

// extract results
for (Iterator iter = jobs.iterator(); iter.hasNext();) {
                    
   WorkItem workItem = (WorkItem) iter.next();
   Translator translator = (Translator) workItem.getResult();
   result.add(translator.getTranslation());
}

J’ai déclaré dans le web.xml le nom du WorkManager que j’utilise dans mon code.


 <resource-ref>  
   <res-ref-name>wm/MyWorkManager</res-ref-name>  
   <res-type>commonj.work.WorkManager</res-type>  
   <res-auth>Container</res-auth>  
   <res-sharing-scope>Shareable</res-sharing-scope>  
 </resource-ref>  

ENVIRONNEMENT



Pour déployer cette  application, j’ai créé un domaine mono instance en mode production. J’ai déclaré un WorkManager du nom que j’ai déclaré dans le web.xml (wm/MyWorkManager). J’ai ajouté à ce WorkManager une contrainte minimum en concordance avec le nombre de traitements déclaré en parallèle dans mon code, c’est à dire 100.




Je déploie mon application sur cette instance et j’exécute les deux Servet en observant la sortie standard.

Avant activation, nous avons 6 Threads déclaré dans le pool de l’instance.


Activons le traitement en séquentiel.

http://localhost:7001/CommonjWAR/ServletSequential

L’affichage nous montre que les traitements s’effectuent en séquentiel et le temps pour réaliser tous les traitements est de 100*10s. On peut observer que seul le Thread 1 est utilisé pour le traitement.

begin
1339495038734 JOB[0] in 10000 ms on [[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [default]
1339495048735 JOB[1] in 10000 ms on [[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [default]
1339495058735 JOB[2] in 10000 ms on [[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [default]
1339495068735 JOB[3] in 10000 ms on [[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [default]
1339495078735 JOB[4] in 10001 ms on [[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [default]
1339495088736 JOB[5] in 10000 ms on [[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [default]
1339495098736 JOB[6] in 10000 ms on [[ACTIVE] ExecuteThread: '1' for queue:

….

1339495038734 JOB[99] in 10000 ms on [[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [default]
done in 1000045

Nombre de threads après traitement.


(Au bout de 10 minutes, le thread est considéré comme bloqué par Weblogic)

1339495698764 JOB[66] in 10000 ms on [[STUCK] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [default]

Exécutons maintenant ces mêmes traitements, mais via le WorkManager.

http://localhost:7001/CommonjWAR/ServletParallel

begin
1339501775295 : All jobs scheduled at :
1339501775283 JOB[17] in 10000 ms on [[ACTIVE] ExecuteThread: '4' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775283 JOB[19] in 10000 ms on [[STANDBY] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775283 JOB[14] in 10001 ms on [[ACTIVE] ExecuteThread: '17' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775283 JOB[12] in 10001 ms on [[ACTIVE] ExecuteThread: '21' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775283 JOB[1] in 10001 ms on [[ACTIVE] ExecuteThread: '9' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775283 JOB[10] in 10001 ms on [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775283 JOB[3] in 10001 ms on [[ACTIVE] ExecuteThread: '18' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775283 JOB[6] in 10001 ms on [[ACTIVE] ExecuteThread: '13' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]


1339501775295 JOB[94] in 10000 ms on [[STANDBY] ExecuteThread: '96' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775295 JOB[95] in 10000 ms on [[STANDBY] ExecuteThread: '97' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775295 JOB[99] in 10001 ms on [[STANDBY] ExecuteThread: '101' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775295 JOB[97] in 10001 ms on [[STANDBY] ExecuteThread: '99' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339501775295 JOB[98] in 10001 ms on [[STANDBY] ExecuteThread: '100' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
done in 10017

L’ensemble des traitements sont exécuté en même temps sur le WorkManager déclaré, et le retour apparaît au bout de 10s qui est le temps unitaire de traitement d’un job. On remarque que chaque traitement a un No de Thread différent.

En observant la taille du pool, on peut voir que la contrainte minimum du WorkManager de 100 Thread a été respectée et qu’il y a plus de 100 threads dans le pool global.


WORKMANAGER EMBEDED



On peut également déclarer le WorkManager dans les descripteurs JEE, ce qui évite une tâche administrative, mais le masque aux administrateurs. Supprimer l’entrée du WorkManager via la console est ajouter le weblogic.xml ci-dessous.

weblogic.xml

 <?xml version="1.0" encoding="UTF-8"?>  
 <wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.3/weblogic-web-app.xsd">  
   <wls:weblogic-version>10.3.6</wls:weblogic-version>  
   <wls:context-root>CommonjWAR</wls:context-root>  
   <wls:work-manager>  
     <wls:name>wm/MyWorkManager</wls:name>  
     <wls:min-threads-constraint>  
       <wls:name>myMinThreadsConstraint</wls:name>  
       <wls:count>100</wls:count>  
     </wls:min-threads-constraint>      
   </wls:work-manager>  
 </wls:weblogic-web-app>  

Re déployer l’application et redémarrer l’instance pour re exécuter la Servlet  ServletParallel

MIN THREADS CONSTRAINT



Regardons maintenant l’impact du paramètre MinThreadsConstraint en diminuant sa valeur de 100 à 10.


   <wls:work-manager>  
     <wls:name>wm/MyWorkManager</wls:name>  
     <wls:min-threads-constraint>  
       <wls:name>myMinThreadsConstraint</wls:name>  
       <wls:count>10</wls:count>  
     </wls:min-threads-constraint>      
   </wls:work-manager>  

En redémarrant et redéployant l’application et activant la Servlet ServletParallel, nous constatons que tous les traitements ne se font pas tous en parallèle et que le temps total d’exécution est supérieur à 10s.

La principale raison est que le WorkManager se base sur un historique pour prendre la décision d’utiliser un Thread du pool global pour exécuter la tâche demandée par l’application associée (éviter trop de traitement en parallèle qui pourrait écroulé la machine). Le fait de définir un minimum permet d’allouer tout de suite des Threads pour desservir la charge voulue.

begin
1339502663859 : All jobs scheduled at :
1339502663857 JOB[0] in 10000 ms on [[ACTIVE] ExecuteThread: '9' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339502663857 JOB[2] in 10000 ms on [[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339502663857 JOB[1] in 10000 ms on [[ACTIVE] ExecuteThread: '8' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]


1339502705721 JOB[97] in 10000 ms on [[ACTIVE] ExecuteThread: '21' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339502705720 JOB[96] in 10002 ms on [[ACTIVE] ExecuteThread: '19' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339502705720 JOB[95] in 10002 ms on [[ACTIVE] ExecuteThread: '15' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339502705721 JOB[98] in 10002 ms on [[ACTIVE] ExecuteThread: '20' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
1339502705722 JOB[99] in 10001 ms on [[ACTIVE] ExecuteThread: '23' for queue: 'weblogic.kernel.Default (self-tuning)'] WorkManager [wm/MyWorkManager]
done in 51879

Afin de populer le pool de Threads dès le départ avec une valeur égale à la contrainte minimum, on peut également ajouter le paramètre suivant aux scriptes de démarrage des instances

-Dweblogic.threadpool.MinPoolSize=100

0 commentaires:

AUTEUR

Ma photo
Carrières Sur Sein, Yvelines, France
Consultant Oracle (Ancien consultant BEA depuis 2001), je m’occupe des expertises sur les produits Oracle : SOCLE (Weblogic, Coherence, JRockit) SOA (Service Bus, SOA Suite, BPM)
MON CV

LABEL 3D

Blogumulus by Roy Tanck and Amanda Fazani

LABEL CLOUD

MAP

Locations of visitors to this page

AUTRES BLOG

LIVRES

MEMBRES