MokaByte 90- 9mbre 2004 
Clustering di applicazioni J2EE
EJB Clustering con JBoss
II parte: il clustering di entity e session beans
di
Giovanni Puliti

Dopo la panoramica introduttiva del mese scorso dedicata alla teoria delle architetture cluster EJB con JBoss, questo mese si proseguirà in questi argomenti affrontando il tema dal punto di vista applicativo, mostrando cosa sia necessario fare per realizzare una applicazione EJB in modalità cluster.

Se quanto visto in precedenza analizzava i concetti di clustering EJB da un punto di vista sistemistico (ovvero come progettare e configurare una architettura EJB cluster con JBoss), questo mese si vedrà quali sono i compiti del programmatore che deve realizzare applicazioni EJB in grado di essere eseguite in ottica cluster. Le nozioni teoriche in tale ambito sono poche, essendo di primaria importanza tenere sempre a mente quanto detto relativamente alla progettazione della struttura a nodi-cluster.
L'obiettivo primario che si deve sempre tenere in mente quando si sviluppano applicazioni cluster-oriented è la semplicità: per quanto la tecnologia metta a disposizione strumenti potenti e flessibili (l'autodiscovery di nodi in una partizione, le tecniche di sincronizzazione della sessione ed il round robin sulle lookup), è di primaria importanza realizzare geometrie e configurazioni il più semplici possibili. Così ad esempio è fortemente consigliabile deployare i componenti delle applicazioni cluster su ogni nodo del cluster che si vuole utilizzare, al fine di ridurre enormemente i tempi di risposta delle invocazioni remote e l'overhead complessivo del sistema.
Inoltre un sistema più semplice è certamente migliore di un sistema complesso. Per questo motivo si cerchi sempre di ridurre al minimo la complessità dell'architettura cluster, la compartecipazione in partizioni differenti di nodi specie se abbinata a una non uniforme dislocazione geografica delle applicazioni rispetto ai nodi.
Avendo ben in mente queste raccomandazioni piuttosto semplici, si può passare ora ad analizzare cosa voglia dire scrivere applicazioni cluster per ogni tipo di componente EJB: stateless session bean, stateful session bean, entity bean, message driver bean (per i quali in realtà in questo momento non è prevista nessun supporto per il clustering almeno in JBoss).

NOTA: per rendere più chiari i vari concetti qui esposti verranno mostrati alcuni pezzi di codice XML che fanno riferimento alla applicazione MokaByte Community, utilizzata sul sito web di MokaByte per la gestione degli utenti registrati. Tale applicazione verrà presto rilasciata in modalità open source all'interno del progetto MokaLab (per maggiori approfondimenti si veda [MBLAB]).


Clustering di Session bean stateless
Il clustering di questo tipo di componenti è certamente il più semplice da realizzare: non essendoci uno stato conversazionale fra il client ed il componente remoto, tutte le istanze di session bean si equivalgono fra loro. Ogni session deployato su un nodo qualsiasi potrà essere utilizzato indifferentemente. Gli smart proxies (di cui si è parlato il mese scorso) potranno quindi far uso dell'oggetto remoto di risulta più comoda l'operazione di lookup (secondo una politica di round robin simmetrico, bilanciato o in base alla logica "elected member" di cui si parlerà più avanti).
Supponendo di voler rendere cluster oriented l'applicazione MokaByte Community ed in particolare il session bean stateless CommunityManagerSF (un session che svolge per il client il compito di Session Façade dello strato applicativo sottostante) sarà necessario modificare il file jboss.xml in modo da introdurre alcuni tag relativi al clustering (in grassetto nel codice seguente):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 3.2//EN" "http://www.jboss.org/j2ee/dtd/jboss_3_2.dtd">
<jboss>
<enterprise-beans>
<session>
<ejb-name>CommunityManagerSF</ejb-name>
<jndi-name>CommunityManagerSF</jndi-name>
<clustered>True</clustered>
<cluster-config>
<partition-name>DefaultPartition</partition-name>
<home-load-balance-policy>
org.jboss.ha.framework.interfaces.RoundRobin
</home-load-balance-policy>
<bean-load-balance-policy>
org.jboss.ha.framework.interfaces.RoundRobin
</bean-load-balance-policy>
</cluster-config>

</session>
. . .
<jboss>

Come si può notare le modifiche da fare sono piuttosto semplici ed intuitive. Solo il tag <clustered> è obbligatorio mentre gli altri se non specificati sono impostati automaticamente al momento del deploy con i valori qui riportati.
Il tag <partition-name> indica quale partizione verrà utilizzata per il clustering del session (il valore di default è DefaultPartition che è anche il nome comunemente utilizzato per la creazione di una partizione di default alla installazione di JBoss).
I tag <home-load-balance-policy> e <bean-load-balance-policy>, non obbligatori, permettono di specificare la politica da utilizzare per ricavare i reference remoti (home e remote): per default viene utilizzata una logica di round robin. Si possono utilizzare altre politiche implementando classi ad hoc oppure utilizzarne alcune fornite con l'application server, come ad esempio la org.jboss.ha.framework.interfaces.FirstAvailable. Al termine dell'articolo verranno approfonditi ulteriormente i concetti relativi alle politiche di lookup.

 

Clustering di session bean stateful
In questo caso le cose sono un po' più complesse dato che è necessario mantenere lo stato conversazionale fra client e server. Mantenere uno stato e sincronizzare le sessioni implica un notevole lavoro aggiuntivo che per fortuna è a carico dell'application server e di tutta l'architettura cluster, mentre per il programmatore ci sono poche differenze rispetto al caso precedente. Ovviamente essendo necessario un lavoro maggiore di coordinamento delle chiamate e di replica delle sessioni, a risentirne sono in genere le prestazioni. In tal senso è bene ricordare che ogni singola applicazione sarà in termini assoluti leggermente meno performante; per valutare il livello del degrado si dovrebbe certamente prendere in considerazione alcuni aspetti come le caratteristiche della rete e dell'hardware utilizzato. In ogni caso quando si fa clustering non vuol dire necessariamente porsi come obiettivo primario quello delle prestazioni.
Nella implementazione attuale di JBoss non è previsto nessun sistema di persistenza per permettere la replica e la condivisione delle informazioni di sessione: invece viene utilizzato un meccanismo di replica in memoria in modo che ogni variazione viene immediatamente propagata su tutti i nodi della rete.
JBoss fa uso di un servizio distribuito cluster-wide proprio per gestire la sessione distribuita detto HASessionState definito dal seguente mbean così descritto

<mbean code="org.jboss.ha.hasessionstate.server.HASessionStateService"
       name="jboss:service=HASessionState"/>

I valori disponibili per questo mbean sono i seguenti:

  • Attributo JndiName (attributo opzionale): Il nome JNDI con il quale il servizio verrà registrato. Il valore di default è /HAPartition/Default
  • Attributo PartitionName (attributo opzionale): Il nome della partizione all'interno della quale il servizio HASessionState lavora. Il valore di default è /HAPartition/Default
  • Attributo BeanCleaningDelay (attributo opzionale): rappresenta il tempo massimo (espresso in millisecondi) dopo il quale il servizio HASessionState può procedere ad una pulizia dello stato di una sessione che non sia stata modificata. Il motivo per cui questa operazione si rende necessaria è legato a come i bean sono gestiti fra nodi differenti. In particolare nel caso in cui un nodo si blocchi, il bean verrà preso in gestione da un altro nodo. Esso inizierà una nuova vita nel nuovo container. Dato che la sessione tiene traccia anche di questi aspetti, dovrà essere ripulita dopo una passaggio di nodo. Il valore di default è 30*60*1000 (ovvero 30 minuti).

Per quanto riguarda la configurazione dei bean, analogamente al caso precedente anche per gli stateful è necessario modificare opportunamente il file jboss.xml in modo da rendere il session cluster-aware. In questo caso, oltre ai tag relativi alla definizione del clustering, si deve inserire la parte relativa alla definizione del servizio HASessionState (riportato in grassetto):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 3.2//EN" "http://www.jboss.org/j2ee/dtd/jboss_3_2.dtd">
<jboss>
<enterprise-beans>
<session>
<ejb-name>CommunityManagerSF</ejb-name>
<jndi-name>CommunityManagerSF</jndi-name>
<clustered>True</clustered>
<cluster-config>
<partition-name>DefaultPartition</partition-name>
<home-load-balance-policy>
org.jboss.ha.framework.interfaces.RoundRobin
</home-load-balance-policy>
<session-state-manager-jndi-name>
/HASessionState/Default
</session-state-manager-jndi-name>
</cluster-config>

</session>
. . .
<jboss>

Si noterà in questo caso la mancanza del tag <bean-load-balance-policy>: questa carenza non è un fatto casuale ed anzi è conseguenza di un aspetto molto importante. Se da un lato è possibile continuare ad implementare politiche di lookup bilanciato (esempio in round robin) di tutte le istanze della home interface di un session stateful, la stessa cosa non è possibile per la interfaccia remote.
Una volta che il client ottiene il reference della remote, inizia a colloquiare con il bean corrispondente iniziando a creare lo stato conversazionale.
Per tutto il periodo di esecuzione quel client "parlerà" sempre con lo stesso bean e quindi utilizzando sempre la stessa interfaccia. Si crea quindi quella che in ambito HTTP viene comunemente detta sticky session.
Dato che le operazioni di sincronizzazione della sessione sono costose, per evitare che ad ogni modifica del bean vengano propagate le modifiche agli altri bean deployati in altri nodi, il programmatore potrà implementare il metodo

public boolean isModified();

metodo invocato dall'application server per controllare se la sessione debba essere replicata oppure no. Questo meccanismo offre al programmatore la possibilità di controllare il livello di replica: ad esempio modificando il valore restituito solo quando ritenuto necessario sarà possibile forzare la replica della sessione solo in concomitanza di modifiche sostanziali o comunque importanti dello stato del session bean.
Clustering di entity beans
La configurazione del sistema per consentire la modalità clustered di un entity bean è identica a quanto visto fino ad adesso. E' necessario modificare il file jboss.xml introducendo i tag relativi al clustering. Ad esempio si potrebbe scrivere

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 3.2//EN" "http://www.jboss.org/j2ee/dtd/jboss_3_2.dtd">
<jboss>
<enterprise-beans>
<entity>
<ejb-name>User</ejb-name>
<local-jndi-name>User</local-jndi-name>
<clustered>True</clustered>
<cluster-config>
<partition-name>DefaultPartition</partition-name>
<home-load-balance-policy>
org.jboss.ha.framework.interfaces.RoundRobin
</home-load-balance-policy>
<bean-load-balance-policy>
org.jboss.ha.framework.interfaces.FirstAvailable
</bean-load-balance-policy>
</cluster-config>

</entity>
. . .
</enterprise-beans>
</jboss>


Al solito tranne il tag <clustered> gli altri sono opzionali con valori di default corrispondenti ai valori sopra mostrati.
A questo punto è molto importante considerare quale sia il funzionamento di un entity bean clustered: in questo caso non è disponibile nessun meccanismo di lock distribuito così come non è presente una cache dei dati distribuita.
L'unico meccanismo di sincronizzazione può essere implementato tramite un lock di riga a livello di database oppure impostando il livello di isolamento della transazione nel driver JDBC al valore TRANSACTION_SERIALIZABLE.
Nel caso in cui si stia utilizzando un BMP entity bean, è compito del programmatore implementare il livello di isolamento.
MVCSoft il framework di JBoss che implementa CMP 2.0, offre vari livelli di isolamento. Anche il framework JAWS (CMP 1.0) consente di configurare il comportamento ed il livello di lock. Facendo riferimento alla specifica EJB è comunque consigliabile di fare riferimento sempre al livello di isolamento "Commit Option B".
Si consiglia di far riferimento alla documentazione ufficiale per maggiori approfondimenti.

 

Bilanciare le invocazioni remote fra i nodi
Per ogni bean definito come clustered, è possibile specificare la modalità tramite la quale il client (o meglio lo smart proxy) ricava le interfacce remote. In JBoss 3.0.x due sono le politiche di accesso: round robin e first avalaible.
La prima viene utilizzata se il parametro corrispondente al tag <home-load-balance-policy>, di cui si è parlato in precedenza, viene impostato al valore org.jboss.ha.framework.interfaces.RoundRobin. In questo caso ogni chiamata è deviata su un nodo differente.
Nel secondo caso (<home-load-balance-policy> impostato a org.jboss.ha.framework.interfaces.FirstAvalaible) le chiamate verranno sempre indirizzate verso quello che viene eletto come il destinatario principale. Tale scelta viene fatta una prima volta in maniera casuale fra tutti i nodi disponibili. Nel caso in cui il nodo eletto sia indisponibile, le chiamate verranno allora dirottate verso il nuovo nodo eletto come rappresentate principale. In questa politica, ogni proxy elegge il suo rappresentante indipendentemente dagli altri.
Caching dei proxies e gestione delle liste di nodi
Tornando al caso di JBoss 3.0.x, il calcolo dei nodi disponibili si basa sul fatto che ogni proxy ha il suo set di nodi cosa che porta ad alcune interessanti conseguenze. Ad esempio una strategia comunemente utilizzata dai programmatori EJB è quella di salvare, nello strato client, il reference alla home interface e di ricreare (con una ejbCreate(), ejbFind() o altro sulla home) la remote ogni volta che sia necessario invocare un metodo remoto. In questo caso per ogni nuovo reference remoto verrà scaricato un nuovo proxy (quello relativo alla interfaccia remota): questo fatto implica che ad ogni chiamata verrà sempre rilevato il primo elemento dalla lista dei nodi disponibili, dato che ogni volta verrà scaricato sul client un nuovo proxy, annullando di fatto il contatore sulla lista dei nodi. In questo scenario quindi non si sfruttano le funzionalità di balancing della rete di nodi.
Se invece, oltre alla interfaccia home, si salva anche il reference alla remote, allora il round robin potrà essere implementato grazie al fatto che la lista dei nodi disponibili rimane sempre la stessa, ed il contatore sull'ultimo nodo utilizzato potrà essere variato.
Per risolvere questo problema, a partire dalla versione 3.2 dell'application server è stato introdotto il concetto di proxies family: una famiglia di proxies rappresenta un insieme di proxies che fanno riferimento allo stesso EJB deploiato all'interno dello stesso cluster (si tenga presente che home e remote proxies che fanno riferimento allo stesso EJB formano due famiglie diverse).
Tutti i proxies appartenenti alla stessa famiglia condividono la stessa lista di nodi memorizzata all'interno di una struttura dati denominata FamilyClusterInfo: questo oggetto è in grado di memorizzare anche altre tipologie di dati comuni a tutti i proxies.
Se qualcosa cambia a livello di partizione (ad esempio un nodo non risulta più disponibile) tutti i proxies della stessa famiglia potranno ricevere in un solo colpo la nuova lista dei nodi o un aggiornamento sulla topologia dei nodi.
Grazie a questo meccanismo si può risolvere il problema dell'azzeramento della history nel caso in cui si memorizzi nel client la home ma non la remote.
Per utilizzare il meccanismo delle proxy family è necessario impostare <home-load-balance-policy> al valore FirstAvalaibleIdenticalAllProxies.

 

Note conclusive
Non è stato fatto cenno dei meccanismi di clustering e sincronizzazione della sessione fra i vari MDB, questo perché attualmente non è fornito nessun supporto per il clustering di message driven beans.
Il mese prossimo parleremo di come si implementi la sincronizzazione di sessioni HTTP, ovvero come sia possibile clusterizzare anche la parte delle web applications all'interno di un set di nodi.

 

Bibliografia e riferimenti
[JBCLUSTER] - JBoss/Documentation - http://www.jboss.com/docs/index
[JGROUPS2] - JGroups - A Toolkit for Reliable Multicast Communication http://www.jgroups.org/javagroupsnew/docs/index.html
[MBLAB] - Progetto MokaLab: www.mokabyte.it/mokalab


MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it