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
|