MokaByte 91- Dicembre 2004 

Clustering di applicazioni J2EE
Lo strato web: load balancing e session replication

di
Giovanni Puliti

Nelle precedenti puntate di questa miniserie dedicata al clustering di applicazioni J2EE ci siamo preoccupati di rendere affidabile la parte server side di business logic remota (clustering del container EJB), dimenticando che come recita un famoso spot pubblicitario la potenza è nulla senza il controllo.
Nel caso in questione il controllo non significa tanto avere buoni freni, ma piuttosto disporre in modo affidabile e continuativo di uno strato di comunicazione verso l'utente.

Introduzione
Da diverso tempo ormai il modello che si è andato affermando sempre più è il modello della applicazione web, con tutti i pro ed i contro del caso. L'interfaccia HTML dinamica (gestita in Java tramite applicazioni basate su servlet e JSP) è in questo momento il sistema più utilizzato per realizzare applicazioni pervasive: tutti i PC hanno un browser e quindi potenzialmente l'applicazione che si scrive può avere un parco di utenza planetaria.
Non è detto che questo scenario resti invariato ancora per molto tempo ed anzi presto le cose potrebbero cambiare. Molti sono i problemi derivanti dallo sviluppo di applicazioni web pure: un maggior tempo necessario per sviluppare i vari menù, minor potenza espressiva di una pagina HTML e ridotte funzionalità interattive del modello CGI hanno da tempo mosso la comunità IT verso diverse evoluzioni del modello web.
Personalmente trovo il modello web effettivamente piuttosto rudimentale, ma con alcuni vantaggi talmente importanti da far dimenticare in un attimo ogni altra limitazione. Penso alla facilità con cui sia possibile disaccoppiare la parte di presentazione dalla business logic e magari dare i template HTML/JSP in gestione ad un buon grafico mentre il programmatore J2EE spende il suo tempo a progettare e modellare session ed entity CMP 2.0.
Per non pensare alla potenza di poter inserire varie applicazioni nell'ambito di un unico portale "amalgamando" il tutto grazie a poche e semplici istruzioni HTML: il single sign-on non è mai stato così semplice, dato che vi è un browser che permette di centralizzare in sessione tutte le informazioni utente.
Ovviamente il motivo principale per cui il modello ha avuto successo è quello della indipendenza dalla piattaforma: avere un browser il cui compito è limitato al parsing di HTML (e non dovrebbe fare niente di più) elimina la fase di installazione e manutenzione della piattaforma client.
Si potrebbe proseguire ancora molto sui costi e benefici di questo modello di programmazione, ma non è questo il luogo adatto: le web application sono di fatto attualmente lo strumento più utilizzato nell'ambito delle applicazioni distribuite.
Per questo motivo è importante, quando si parla di alta affidabilità di applicazioni J2EE, preoccuparsi anche di predisporre le adeguate misure per rendere lo strato web affidabile.
Sistema affidabile vuol dire preoccuparsi essenzialmente di due aspetti: load balancing delle chiamate e replica della sessione HTTP.
Load balancing significa avere diversi web container in funzione per la stessa applicazione web in modo da garantire la ripartizione del carico fra i web container minimizzando per quanto possibile i tempi di risposta del sistema.
La replica della sessione invece permette di sincronizzare le sessioni utente sui vari container rendendoli di fatto del tutto equivalenti agli occhi del client. In questo modo il cosiddetto conversational state verrebbe reso indipendente dal particolare container, rendendo possibile ad un client di passare da un web container ad un altro senza che nessun dato di sessione sia perso.
Se il proprio obiettivo è rendere la architettura il più affidabile possibile e minimizzare i tempi di indisponibilità del sistema, allora implementare il load balancing è sicuramente il primo passo da compiere.
La replica delle sessioni (benché non particolarmente difficile da implementare) richiede uno sforzo concettuale maggiore ed implica un carico di lavoro maggiore per il sistema: per questo motivo si procede in questa direzione solo se strettamente necessario, ovvero se si desidera realizzare un sistema che oltre ad essere sempre disponibile, non perda nemmeno una sessione utente e quindi non perda nessuna informazione o non invalidi nessun work-flow applicativo.
Se nel sistema in questione il blocco di un container è un evento che si verifica raramente e se l'applicazione non maneggia dati sensibili (per esempio denaro o dati personali), si può pensare di evitare la replica della sessione, mettendo in conto il fatto che ogni tanto un utente debba ricominciare la sua procedura di acquisto su un sito di ecommerce, il login in un forum e così via.

 

Nota sui prodotti e tecnologie utilizzate
Come si è avuto modo di accennare nei precedenti articoli, per brevità concentreremo la trattazione limitando l'analisi della configurazione e del comportamento ai prodotti open source attualmente più utilizzati, ovvero Apache-Jakarta Tomcat (per brevità Tomcat) per la parte web e JBoss per la parte EJB.
I due server verranno qui analizzati come componenti separati, anche se si consiglia di utilizzarli in modalità embedded per il supporto completo dello stack J2EE. Come server HTTP verrà preso in esame Apache HTTP Server (per brevità Apache).

 

Load balancing
Ripartizione del carico significa associare l'URL a cui risponde una applicazione ad un numero n arbitrario di server HTTP e web container.


Figura 1
- Una tipica architettura load balanced: il server HTTP maschera la presenza di n web container

Per semplicità cerchiamo di scomporre il problema analizzando singolarmente i vari soggetti che partecipano in uno scenario del genere: il server HTTP, il connettore lato server HTTP, il connettore lato web container ed il web container.
Il server HTTP è quel componente che gestisce l'accesso del client remoto (browser) ad un determinato URL o genericamente a delle risorse.
Ad esempio nel caso di applicazioni web è compito del server HTTP mappare un indirizzo del tipo http://www2.mokabyte.it/community indirizzando le chiamate verso un web container dove sia stata deployata la web application Community (che nel caso specifico permette di gestire gli utenti della comunità di MokaByte).
Se il server HTTP è unico il processo di mappatura è relativamente semplice e viene fatto con poche istruzioni inserite nel file di configurazione (come verrà mostrato successivamente).
Ma se il server HTTP si blocca o si rende indisponibile per un motivo qualsiasi, tutto il sistema rimarrà irrimediabilmente irraggiungibile. Questo significa che un sistema altamente affidabile deve prevedere anche la replica del server HTTP, cosa che implica l'introduzione di un ulteriore strumento detto director (dal nome del più popolare ed affidabile strumento commerciale prodotto da CISCO). Questo strumento è infatti in grado di nascondere i diversi server HTTP dietro un unico nome di dominio virtuale.
Collegandosi all'indirizzo www.mokabyte.it l'utente verrà quindi rediretto su uno dei server HTTP disponibili.


F
igura 2 - Per aumentare la affidabilità del sistema si può pensare di replicare il server HTTP, ponendo di fronte un Director che unifichi tutti i server sotto un unico dominio web

Questa configurazione richiede maggiori investimenti di energie, conoscenze e denaro: la sua analisi e trattazione esula dagli scopi di questo articolo, essendo la sua installazione e gestione una attività tipicamente a carico del reparto sistemistico.
Per questo motivo e per rendere le cose semplici, limiteremo l'attenzione al caso di un solo server HTTP collegato ad una batteria di web container: in questo scenario tutte le chiamate per la web application in questione potranno essere indirizzate verso un URL in gestione al server HTTP il quale poi le redirigerà verso uno dei container web collegati.
Varianti e modifiche a questa semplice architettura potranno essere realizzate in modo semplice operando sulla procedura di configurazione che è riportata di seguito.

 

La connessione Apache-Tomcat in round robin bilanciato
La connessione Apache-Tomcat viene realizzata tramite un apposito connettore (il più usato è il mod_jk) che può essere configurato in modo da realizzare un instradamento delle connessioni in modalità round-robin pesato: ogni volta che un nuovo client (browser) richiede l'esecuzione della web application, verrà associato ad uno dei container web in esecuzione. Tale associazione perdura per tutto il tempo di vita della sessione dando vita a quella che si definisce "sticky session", in modo che sia mantenuto il conversational state.
La procedura di configurazione di una connessione load balanced fra Apache e Tomcat richiede la definizione di alcune semplici regole all'interno del server HTTP, mentre poco o nulla deve essere fatto in Tomcat.
Il connettore che verrà usato per questi esempi è il Coyote che utilizza come protocollo di comunicazione l'AJP 1.3. Altri connettori e protocolli possono essere utilizzati come ampiamente mostrato in [MOD_JK].
Per concludere si possono riassumere i punti fondamentali del load balancing:

  • E' un sistema che permette di connettere un server HTTP con una batteria di container web.
  • Permette la ripartizione del carico fra tutti i container web, garantendo migliori prestazioni
  • Consente la realizzazione di sistemi con un elevato livello di HA: se anche uno o più server dovessero bloccarsi, i client potranno continuare ad usufruire del sistema dato che verranno rediretti su un'altra applicazione deployata su un altro container
  • Tutti i container (e quindi anche le applicazioni) sono equivalenti fra loro: quando un client inizia la comunicazione con uno dei server, proseguirà sempre con lo stesso
  • Il sistema non garantisce il mantenimento della sessione: se in un determinato momento il container si blocca o diviene indisponibile, i dati di sessione andranno persi e l'utente dovrà reiniziare da capo la procedura.


Figura 3
- Il load balancing permette di ripartire il carico delle chiamate in modo circolare

Load balancing: la configurazione di Apache
La prima cosa che è necessario fare è decidere quali URL pattern dovranno essere rediretti da Apache verso Tomcat: si potrebbe pensare di instradare tutte le chiamate verso file del tipo *.jsp oppure tutte le request relative ad indirizzi della forma www.mokabyte.it/servlet/*.
La cosa che si consiglia in genere è realizzare la soluzione più pulita ed omogenea possibile, ad esempio definendo un virtual host opportuno, in modo da non "sporcare" il resto della configurazione del server e non avere sovrapposizioni fra URL pattern diversi.
Ad esempio i server di mokabyte.it sono configurati per redirigere tutte le chiamate del tipo www2.mokabyte.it/* verso applicazioni web in esecuzione su Tomcat.
La definizione di un virtual host è una operazione che si compie in modo piuttosto semplice tramite il seguente script da inserire nel file di configurazione di Apache httpd.conf:


<VirtualHost www2.mokabyte.it>
ServerAdmin someone@mokabyte.it
DocumentRoot /somewhere/here/there/docroot
ServerName nomeserver.mokabyte.it

# definisce il mapping URL-pattern e connessioni virtuali
JkMount /community/* balanced
JkMount /community* balanced
</VirtualHost>

in questo caso si specifica che all'interno del dominio virtuale www2.mokabyte.it è definito un URL pattern "community" per il quale tutte le invocazioni saranno redirette verso una batteria di Tomcat tramite la connessione definita dal nome simbolico "balanced".
In questo modo l'invocazione verso la pagina di login della Community di MokaByte

www2.mokabyte.it/community/login.jsp

sarà rediretta verso i Tomcat. Per definire una connessione è necessario inserire alcune regole all'interno di un file di configurazione di apache detto workers.properties. All'interno di questo file si definiscono tutte le connessioni verso altrettanti server Tomcat. Ad esempio supponendo di avere due Tomcat denominati tom1 e tom2, in workers.properties troveremo:

worker.tom1.port=8009
worker.tom1.host=server1.mokabyte.it
worker.tom1.type=ajp13
worker.tom1.cachesize=1
worker.tom1.lbfactor=10

worker.tom2.port=8009
worker.tom2.host=server2.mokabyte.it
worker.tom2.type=ajp13
worker.tom2.cachesize=1
worker.tom2.lbfactor=10

L'attributo port specifica la porta (8009 è il valore convenzionale) sulla quale resta in ascolto il connettore lato Tomcat (quello che riceve le richieste inoltrate da Apache); l'attributo type serve invece per specificare il tipo di protocollo utilizzato per la comunicazione: in questo caso AJP13 indica il protocollo AJP 1.3.
Ad ogni connessione è poi possibile assegnare un carico o peso: il parametro lbfactor (il cui valore è un numero relativo non assoluto) specifica il peso che deve e essere assegnato ad un particolare server. Si potrebbe ad esempio volere che il 70% delle chiamate siano redirette verso server1, in modo da affaticare meno il Tomcat su server2 che potrebbe a questo punto dedicare risorse di sistema per gestire altri processi (ad esempio un database server).

Infine una volta definite tom1 e tom2, è necessario riunirle per definire il pool di connessioni sulle quali verrà effettuato il round-robin pesato. Tale operazione di accorpamento viene effettuata definendo un'altra connessione virtuale (il cui nome spesso è convenzionalmente "loadbalancer") specificando come attributo type il valore "lb", ad indicare una connessione orientata al bilanciamento del carico:

worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=tom1,tom2
worker.loadbalancer.sticky_session=1
worker.loadbalancer.local_worker_only=1
worker.list=loadbalancer

Tramite l'attributo balanced_workers si definisce la lista delle connessioni che verranno associate al loadbalancer. L'attributo sticky_session è particolarmente interessante: se impostato al valore 0 la connessione apache-tomcat avverrà realmente in modalità round robin, ovvero ogni diversa chiamata proveniente dallo stesso client viene dirottata su un server diverso. Questa procedura aumenta il carico di lavoro del servizio di sincronizzazione della sessione, per cui si raccomanda di impostare sticky_session a 1 in modo da mantenere le connessioni di un client sempre verso il medesimo Tomcat. Il round robin in questo caso interviene solo in caso di blocco della istanza di Tomcat alla quale ara agganciata la sticky session.
Discorso analogo per local_worker_only: se l'attributo è presente la connessione offre funzionalità di round robin ma solo di failover (ovvero lavorerà sempre e solamente una connessione, mentre la seconda entrerà in caso di blocco della prima).
Per poter utilizzare con successo il connettore è necessario attivarne il funzionamento caricando in memoria il modulo Apache corrispondente. Tale operazione viene effettuata con questa semplice istruzione che dovrà essere inserita prima degli script mostrati in precedenza e che facevano uso del connettore.

LoadModule jk_module modules/mod_jk.so

In alternativa se invece si volesse utilizzare il connettore JK2

LoadModule jk2_module modules/mod_jk2.so

E' inoltre necessario specificare dove si trova il file con la definizione dei workers definendo al contempo livello di log e locazione del file di log

JkWorkersFile /etc/apache/workers.properties
JkLogFile /var/apache2/logs/mod_jk.log
JkLogLevel error

La configurazione di Tomcat: il connettore Coyote
Terminata la parte di configurazione di Apache è necessario abilitare il connettore lato Tomcat: il più utilizzato ed aggiornato dal gruppo di Jakarta è il Coyote, che fra le altre cose è in grado di "parlare" vari protocolli.
In questo caso si abilita il Coyote per il protocollo AJP 1.3 in ascolto sulla porta 8009

<!-- An AJP 1.3 Connector on port 8009. Used by Apache mod_jk -->
<Connector className="org.apache.coyote.tomcat4.CoyoteConnector"
port="8009" minProcessors="5"
maxProcessors="75"
enableLookups="true"
redirectPort="8443"
acceptCount="10"
debug="0"
connectionTimeout="20000"
useURIValidationHack="false"
protocolHandlerClassName="org.apache.jk.server.JkCoyoteHandler"/>

Il pezzo di script appena presentato deve essere inserito nel file di configurazione di Tomcat. Nel caso in cui si usi una versione JBoss-embedded del server si dovrà invece lavorare sul file di configurazione tomcat41-service.xml presente nella directory di deploy.
Nella versione Tomcat stand alone invece il file da modificare è il server.xml che si trova nella directory di configurazione (conf) di Tomcat.

 

Session replication
Replicare le sessioni HTTP vuol dire sincronizzare i dati relativi alla sessione utente su tutti i web container che partecipano al cluster.
La prima cosa che si deve valutare, prima di procedere alla installazione di un qualsiasi sistema di replica della sessione è se realmente questa è una funzionalità necessaria al sistema.


Figura 4
- Il servizio di replica delle sessioni permette di replicare i dati in modo istantaneo

Session replication infatti implica, rispetto al semplice round robin, un salto di qualità non indifferente sia dal punto di vista concettuale che per quello che concerne la complessità del sistema (anche se poi all'atto pratico la gestione del sistema non si complica enormemente).
Adottare la replica delle sessioni infine può significare e intraprendere una strada che potrebbe non avere fine e che potrebbe far venire la tentazione di spingersi verso territori insidiosi e complessi: "già che ci siamo perché non rendere persistente la sessione distribuita e perché non ridondare anche il database con un sistema virtual data server…".


Figura 5
- La session replication entra in funzione se un server web si blocca

E sempre tenere presente che le prestazioni possono risentirne visto che per ogni modifica ai dati in sessione il sistema dovrà occuparsi di replicare la sessione su tutto il cluster.

 

La replica delle sessioni in Tomcat embedded JBoss
Sulla base della piattaforma scelta come riferimento per questa trattazione la parte web verrà servita da Tomcat nella versione embedded in JBoss. In questo scenario si deve subito dire che il web container di per sé non fornisce nessun supporto per la replica della sessione ma si deve far uso del supporto offerto da JBoss. Tutte le componenti necessarie per la replica della sessione sono incluse in una serie di moduli (fra cui il principale è jbossha-httpsession.sar) sono già opportunamente configurati e deployati nella all-configuration che si attiva, su piattaforma unix, tramite il comando

jboss.sh -c all

o equivalentemente su sistema Windows

jboss.bat -c all

Nel file deploy/jbossha-httpsession.sar sono implementate tutte le funzionalità di replica della sessione, che sono indipendenti dal particolare web container utilizzato. Per questo motivo con le opportune modifiche si potrebbe senza problemi implementare le replica della sessione per altri container come ad esempio Jetty o Resin.
Dopo aver attivato la replica delle sessioni è necessario definire la politica di replica della sessione: immediata o dilazionata ad intervalli di tempo. Tali impostazioni si devono definire all'interno del file deploy/tomcat41-service.xml di cui si riporta di seguito la porzione relativa alla replica della sessione

<server>

<mbean code="org.jboss.web.catalina.EmbeddedCatalinaService41"
name="jboss.web:service=WebServer">
<attribute name="CatalinaHome">&catalina.home;</attribute>
<attribute name="SnapshotMode">instant</attribute>
<!-- you may switch to "interval" -->
<attribute name="SnapshotInterval">2000</attribute>

Il punto chiave è rappresentato dall'attributo SnapshotMode: il valore istant abilita la replica immediata delle modifiche sulla sessione, mentre interval ha l'effetto opposto. In questo caso si potrà specificare il tempo di intervallo fra una sincronizzazione ed un'altra tramite l'attributo SnapshotInterval.
Infine per sfruttare le funzionalità di replica della sessione all'interno di una applicazione web è necessario abilitarne il funzionamento cluster-wide, cosa che può essere ottenuta inserendo il tag <distributable/> all'interno del descrittore di deploy WEB-INF/web.xml.
Ovviamente affinché la sessione sia replicabile è necessario seguire le indicazioni J2EE in tal senso. La cosa più importante è far uso di oggetti a scope session che siano serializzabili.

 

Conclusione
Load balancing e session replication sono due feautures molto importanti per garantire il massimo di affidabilità e disponibilità di una applicazione web.
Per chi si avvicinasse a questi concetti per la prima volta suggerisco di iniziare con la attivazione di una connessione Apache-Tomcat con un connettore che metta in contatto un solo web container per poi passare alla versione balanced.
Infine per coloro che volessero garantire un elevato livello di disponibilità del servizio si potrà passare alla realizzazione della replica della sessione.

 

Bibliografia e riferimenti
[MOD_JK] - "Apache & Tomcat: come connettere i due server per realizzare architetture scalabili, sicure performanti. I parte: introduzione allo scenario e primi con nettori" e seguenti, di Giovani Puliti MokaByte 82 - Febbraio 2004. www.mokabyte.it/2004/02
[JK-JB]"UsingMod_jk1.2WithJBoss" - wiki web site su JBoss. http://www.jboss.org/wiki/Wiki.jsp?page=UsingMod_jk1.2WithJBoss
[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


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