SeamFramework.orgCommunity Documentation

Capitolo 35. Clustering e passivazione EJB

35.1. Clustering
35.1.1. Programmare il clustering
35.1.2. Deploy di un'applicazione Seam in un cluster JBoss AS con replica di sessione
35.1.3. Validazione dei servizi distribuiti di un'applicazione su un cluster JBoss AS
35.2. Passivazione EJB e ManagedEntityInterceptor
35.2.1. Attrito fra passivazione e persistenza
35.2.2. Caso #1: Sopravvivere alla passivazione EJB
35.2.3. Caso #2: Sopravvivere alla replica della sessione HTTP
35.2.4. ManagedEntityInterceptor wrap-up

Si noti che questo capitolo è ancora sotto revisione. Maneggiare con cura.

Questo capitolo copre due distinti argomenti che condividono un soluzione comune in Seam, (web) clustering e passivazione EJB. Quindi, sono trattati assieme in questo manuale. Sebbene anche la performance tende ad essere raggruppata in questa categoria, essa viene mantenuta separata poiché il focus di questo capitolo è sul modello di programmazione e su come questo è influenzato dall'uso delle funzionalità sopracitate.

In questo capitolo si apprenderà come Seam gestisce la passivazione dei componenti Seam e degli entity, come attivare questa funzionalità e come questa funzionalità è collegata al clustering. Inoltre si apprenderà come eseguire il deploy di un'applicazione Seam in un cluster e verificare che la replica della sessione HTTP funzioni correttamente. Iniziamo con un pò di background sul clustering e vediamo come fare il deploy di un'applicazione Seam in un cluster JBoss AS.

Il clustering (più formalmente web clustering) consente ad un'applicazione di girare su due o più server paralleli (cioè, nodi) mentre viene fornita ai client una vista uniforme dell'applicazione. Il carico è distribuito sui server in modo tale che se uno o più server fallisce, l'applicazione continua ad essere accessibile tramite gli altri nodi. Questa tipologia è cruciale per la costruzione di applicazioni enterprise scalabili in performance e la disponibilità può essere migliorata semplicemente aggiungendo nodi. Ma si giunge ad una domanda importante. Cosa succede allo stato presente su un server che ha fallito?

Sin dal primo giorno Seam ha sempre fornito il supporto alle applicazioni stateful in azione dentro un cluster. Fino ad ora si è appreso che Seam fornisce la gestione dello stato aggiungendo degli scope e governando il ciclo di vita dei componenti stateful (con scope). Ma la gestione dello stato in Seam va oltre alla creazione, memorizzazione e distruzione di istanze. Seam traccia i cambiamenti ai componenti JavaBean e memorizza i cambiamenti in punti strategici durante la richiesta, affinché i cambiamenti vengano ripristinati quando la richiesta passa su un nodo secondario del cluster. Fortunatamente il monitoraggio e la replica di componenti EJB stateful viene già gestita dal server EJB, quindi tale funzionalità di Seam serve per mettere i JavaBean a fianco dei suoi compagni EJB.

Ma attenzione, c'è di più! Seam offre anche un'incredibile ed unica caratteristica per le applicazioni cluster. In aggiunta al monitoraggio dei componenti JavaBean, Seam assicura che le istanze entity gestite (cioè entity JPA e Hibernate) non divengano detached durante la replica. Seam mantiene un record di entity caricati e li carica automaticamente nel nodo secondario. Occorre comunque usare un contesto di persistenza gestito da Seam per avere questa funzionalità. Maggiori e più dettagliate informazioni su questa funzionalità vengono fornite nella seconda parte del capitolo.

Ora che si è capito quali funzionalità offre Seam per supportare l'ambiente clustered, guardiamo come si programma un cluster.

Un componente JavaBean mutabile con scope sessione o conversazione che verrà usato in un ambiente clustered deve implementare l'interfaccia org.jboss.seam.core.Mutable dell'API Seam. Come parte del contratto, il contratto deve mantenere un dirty flag che viene segnato e resettato dal metodo clearDirty(). Seam chiama questo metodo per determinare se è necessario replicare il componente. Questo evita di dover usare la più problematica API dei Servlet per aggiungere e rimuovere l'attributo sessione ad ogni cambiamento dell'oggetto.

Bisogna assicurarsi che tutti i componenti JavaBean con scope sessione e conversazione siano Serializable. Inoltre tutti i campi di un componente stateful (EJB o JavaBean) devono essere Serializable amenoché siano marcati come transient o impostati a null in un metodo @PrePassivate. Si può ripristinare il valore di un campo transient o null in un metodo @PostActivate.

Un'area in cui spesso le persone hanno problemi è con l'uso di List.subList per creare una lista. La lista risultante non è Serializable. Quindi attenzione a queste situazioni. Se ci si imbatte in una java.io.NotSerializableException e non si riescead individuare subito il colpevole, si può mettere un breakpoint su quest'eccezione, avviare l'application server in modalità debug ed attaccare il debugger (come in Eclipse) per vedere quale deserializzazione causa il problema.

Questa procedura descritta nel tutorial è stata validata con un'applicazione seam-gen e nell'esempio Prenotazione.

In questo tutorial si assume che gli indirizzi IP dei server master e slave siano rispettivamente 192.168.1.2 e 192.168.1.3. Intenzionalmente non viene usato il bilanciatore di carico mod_jk, quindi è più facile validate che entrambi i nodi stiano rispondendo alle richieste e possano condividere le sessioni.

In queste istruzioni si sta usando il metodo di sviluppo farm, sebbene si possa fare normalmente il deploy dell'applicazione e consentire ai due server di negoziare una relazione master/slave basata sull'ordine di avvio.

  • Creare due istanze di JBoss AS (estrarre lo zip due volte)

  • Fare il deploy del driver JDBC in server/all/lib/ su entrambe le istanze se non si usa HSQLDB

  • Aggiungere <distributable/> come primo elemento figlio in WEB-INF/web.xml

  • Impostare la proprietà distributable su org.jboss.seam.core.init a true per abilitare il ManagedEntityInterceptor (cioè, <core:init distributable="true"/>)

  • Assicurarsi di avere disponibili due indirizzi IP (due computer, due schede di rete oppure due indirizzi IP sulla stessa interfaccia). Si assume che i due indirizzi IP siano 192.168.1.2 e 192.168.1.3

  • Avviare l'istanza master di JBoss AS sul primo IP

    ./bin/run.sh -c all -b 192.168.1.2

    Il log dovrebbe riportare che c'è 1 membro cluster e 0 altri membri.

  • Verificare che la directory server/all/farm sia vuota nell'istanza slave di JBoss AS

  • Avviare l'istanza slave di JBoss AS sul secondo IP

    ./bin/run.sh -c all -b 192.168.1.3

    Il log dovrebbe riportare che ci sono 2 membri cluster e 1 altro membro. Dovrebbe anche mostrare che lo stato viene recuperato dal master.

  • Fare il deploy di -ds.xml in server/all/farm dell'istanza master

    Nel log del master si dovrebbe vedere l'acknowledgement del deploy. Nel log dello slave si dovrebbe vedere il corrispondente messaggio di acknowledgement del deploy per lo slave.

  • Fare il deploy dell'applicazione nella directory server/all/farm

    Nel log del master si dovrebbe vedere l'acknowledgement del deploy. Nel log dello slave si dovrebbe vedere il corrispondente messaggio di acknowledgement del deploy per lo slave. Si noti che si potrebbe dover attendere fino a 3 minuti perché venga trasferito l'archivio deployato.

La vostra applicazione sta girando in un cluster con replica della sessione HTTP! Ma certamente si vuole validate il fatto che il clustering sta funzionando.

E' sempre bello vedere che l'applicazione si avvia con successo su due diversi server JBoss AS, ma vedere è credere! Probabilmente si vuole validare che le due istanze si stiano scambiando le sessioni HTTP per consentire allo slave di entrare in azione quando l'istanza master si ferma.

Avviare e visitare sul browser l'applicazione che gira sull'istanza master. Questo produrrà la prima sessione HTTP. Ora si apra la console JMX di JBoss AS su tale istanza e si vada al seguente MBean:

Invocare il metodo printDetails(). Si vedrà un albero con le sessioni HTTP attive. Verificare che la sessione del browser in uso corrisponda ad una delle sessioni in questo albero.

Ora si passi all'istanza slave e si invochi lo stesso metodo nella console JMX. Si dovrebbe vedere la stessa lista (almeno sotto il context path dell'applicazione)

Quindi si può vedere che entrambi i server hanno le stesse identiche sessioni. Ora occorre testare che i dati vengano serializzati e deserializzati correttamente.

Fare il login usando l'URL dell'istanza master. Poi scrivere l'URL per la seconda istanza mettendo ;jsessionid=XXXX immediatamente dopo il path del servlet e cambiando l'indirizzo IP. Si dovrebbe vedere che la sessione viene trasportata nell'altra istanza. Ora uccidere il processo dell'istanza master e verificare che si possa continuare ad usare l'applicazione dall'istanza slave. Rimuovere i deploy dalla directory server/all/farm ed avviare di nuovo l'istanza. Cambiare nell'URL l'IP a quello dell'istanza master e visitare l'URL. Si vedrà che viene usata ancora la sessione originale.

Un modo per vedere passivare ed attivare gli oggetti è create un componente Seam con scope conversazione o sessione ed implementare i metodi del ciclo di vita in modo appropriato. Si possono usare i metodi dell'interfaccia HttpSessionActivationListener (Seam automaticamente registra quest'interfaccia per tutti i componenti non-EJB):

public void sessionWillPassivate(HttpSessionEvent e);

public void sessionDidActivate(HttpSessionEvent e);

O semplicemente si possono marcare due metodi senza argomenti public void rispettivamente con @PrePassivate e @PostActivate. Si noti che il passo di passivazione avviene alla fine di ogni richiesta, mentre quello di attivazione avviene quando viene chiamato un nodo.

Ora che si ha il quadro generale del funzionamento di un cluster con Seam, è tempo di indirizzarsi verso il misterioso ManagedEntityInterceptor.

Il ManagedEntityInterceptor (MEI) è un interceptor opzionale di Seam che viene applicato, se abilitato, ai componenti con scope conversazione. L'abilitazione è semplice. Occorre impostare a true la proprietà distributable nel componente org.jboss.seam.init.core. Più semplicemente aggiungere (o aggiornare) la seguente dichiarazione di componente in components.xml:


<core:init distributable="true"/>

Si noti che questo non abilita la replica delle sessioni HTTP, ma prepara Seam a poter gestire la passivazione dei componenti EJB o dei componenti nelle sessioni HTTP.

MEI serve per due scenari distinti (passivazione EJB e passivazione della sessione HTTP), sebbene raggiunge lo stesso obiettivo generale. Assicura che lungo la vita di una conversazione con l'uso di almeno un contesto di persistenza esteso, le istanze dell'entity caricate dal contesto di persistenza rimangano gestite (non divengano detached in modo prematuro da un evento passivation). In breve, assicura l'integrità del contesto di persistenza esteso (e quindi delle sue garanzie).

La precedente affermazione implica che ci sia una minaccia al contratto. Infatti ce ne sono due. Un caso è quando lo stateful session bean (SFSB) che ospita un contesto di persistenza esteso viene passivato (per risparmiare memoria o migrarlo in un altro nodo del cluster) ed il secondo quando la sessione HTTP viene passivata (per prepararla alla migrazione in un altro nodo del cluster).

Vogliamo discutere per primo il problema generale della passivazione e poi guardare singolarmente le due minacce.

Il contesto di persistenza è il posto in cui il gestore di persistenza (cioè EntityManager JPA o Hibernate Session) memorizza le istanze degli entity (cioè gli oggetti) che ha caricato dal database (tramite mappature relazionali degli oggetti). Dentro un contesto di persistenza, c'è alpiù un oggetto per record di database. Il contesto di persistenza è spesso considerato il primo livello di cache, poiché se l'applicazione chiede un record attraverso il suo identificatore unico che è già stato caricato nel contesto di persistenza, viene evitata una chiamata al database. Ma questo è più che una cache.

Gli oggetti mantenuti nel contesto di persistenza possono essere modificati, cosa di cui il gestore di persistenza tiene traccia. Quando un oggetto viene modificato, viene considerato "dirty". Il gestore di persistenza migrerà questi cambiamenti al database usando una tecnica conosciuta come write-behind (che significa solo quando necessario). Quindi il contesto di persistenza mantiene un set di cambiamenti pendenti sul database.

Le applicazioni orientate ai database fanno molto di più che leggere e scrivere nel database. Esse catturano bit transazionali di informazione che devono essere trasferiti nel database in modo atomico (in una solo volta). Non è sempre possibile catturare queste informazioni in una sola volta. In aggiunta l'utente potrebbe decidere se approvare o disapprovare le modifiche pendenti.

Ciò che vogliamo dire è che l'idea di transazione dal punto di vista dell'utente deve essere estesa. E questo è il motivo per cui il contesto di persistenza esteso risponde perfettamente a questo requisito. Può mantenere i cambiamenti tanto a lungo quanto viene mantenuta aperta l'applicazione e poi usare le capacità predefinite del gestore di persistenza per apportare nel database questi cambiamenti senza richiedere allo sviluppatore di preoccuparsi dei dettagli di basso livello (una semplice chiamata a EntityManager#flush() fa tutto il lavoro).

Il collegamento tra il gestore della persistenza e le istanze degli entity viene mantenuto usando i riferimenti oggetto. Le istanze entity sono serializzabili, ma il gestore della persistenza non lo è (e quindi il suo contesto di persistenza). Quindi il processo di serializzazione funziona contro questo design. La serializzazione avviene anche quando un SFSB o sessione HTTP sono passivati. Per sostenere l'attività nell'applicazione, il gestore della persistenza e le istanze entity che gestisce devono esporre la serializzazione senza perdere la loro relazione. E' questo l'aiuto che viene fornito da MEI.

Le conversazioni sono state inizialmente progettate avendo in mente gli stateful session bean (SFSB), in primo luogo poiché la specifica EJB3 indica gli SFSB come host del contesto di persistenza esteso. Seam introduce un complemento al contesto di persistenza esteso, conosciuto col nome di contesto di persistenza gestito da Seam, il quale risolve un certo numero di limitazioni presenti nella specifica (le regole complesse di propagazione e la mancanza di flush manuale). Entrambi possono essere usati con i SFSB.

Un SFSB si affida al client per mantenere un riferimento ad esso e per mantenerlo attivo. Seam ha fornito un posto ideal per questo riferimento al contesto di conversazione. Quindi fintantoché il contesto di conversazione rimane attivo, il SFSB è attivo. Se un EntityManager viene iniettato in questo SFSB usando l'annotazione @PersistenceContext(EXTENDED), allora tale EntityManager verrà associato al SFSB e rimarrà aperto lungo tutto il ciclo di vita della conversazione. Se un EntityManager viene iniettato usano @In, allora tale EntityManager verrà mantenuto da Seam e memorizzato direttamente nel contesto conversazione, quindi vivrà per tutto il ciclo di vita della conversazione indipendente dal ciclo di vita del SFSB.

Con tutto ciò detto, il container Java EE può passivare un SFSB, il che significa che verrà serializzato l'oggetto in un area di memorizzazione esterna alla JVM. Quando questo avviene dipende dalle impostazione del singolo SFSB. Questo processo può anche essere disabilitato. Comunque il contesto di persistenza non è serializzato (questo è vero solo per SMPC?). Infatti cosa succede dipende fortemente dal container Java EE. La specifica non è molto chiara in proposito. Molti vendor dicono di non fare avvenire questo se servono le garanzie del contesto di persistenza esteso. L'approccio di Seam più conservativo. Seam non si fida del SFSB con il contesto di persistenza o delle istanze entity. Dopo ogni invocazione del SFSB, Seam sposta il riferimento all'istanza entity mantenuta dal SFSB dentro l'attuale conversazione (e quindi nella sessione HTTP). mettendo a null questi campi nel SFSB. Poi ripristina questi riferimenri all'inizio di ogni invocazione. Certamente Seam sta già memorizzando il gestore della persistenza nella conversazione. Perciò quando il SFSB passiva e poi si riattiva, non c'è alcun effetto negativo sull'applicazione.

E' possibile disabilitare la passivazione su un SFSB. Si veda la pagina Ejb3DisableSfsbPassivation su JBoss Wiki.