HazelCast: In-Memory Data Grid Prêt-à-porter

HazelCast è un data grid opensource, che permette di memorizzare dati ed effettuare operazioni direttamente tramite la memoria volatile (RAM). Alcune delle principali feature di questo progetto sono le seguenti

  • Distributed java.util.{Queue, Set, List, Map}
  • Distributed java.util.concurrency.locks.Lock
  • Distributed java.util.concurrent.ExecutorService
  • Distributed Topic for publish/subscribe messaging
  • Write-Through and Write-Behind persistence for maps
  • Java Client for accessing the cluster remotely
  • Dynamic HTTP session clustering
  • Support for cluster info and membership events
  • Dynamic discovery
  • Dynamic scaling
  • Dynamic partitioning with backups
  • Dynamic fail-over

 

Il fatto di poter gestire oggetti distribuiti, unito alla semplicità di poter creare dei cluster, fanno di questo prodotto un progetto molto interessante e soprattutto versatile per diverse situazioni. In questi anni sono stati creati molti progetti simili a HazelCast come Oracle Coherence, MongoDB, TerraCotta e Cassandra. Da uno studio indipendente che è stato fatto ultimamente HazelCast sembrerebbe avere delle performance davvero interessanti. Tralasciando questi test, HazelCast è comunque un progetto interessante che per alcune sue peculiarità può tornare molto utile nei nostri progetti.

 

Hello World

Vediamo quindi come poter creare un Map condiviso da un server, agganciare un client e leggere questo Map. Possiamo creare un semplice progetto Maven includendo le seguenti dipendenze

La classe che crea il Map e fa partire un’istanza di HazelCast è HelloServer, che richiamando il metodo statico newHazelcastInstance di com.hazelcast.core.Hazelcast crea un nuovo server e memorizza al suo interno un Map.

Se eseguite questa classe vedrete prima di tutto questa informazione nel log

che praticamente vi dice che attualmente il cluster HazelCast è composto da un solo nodo, in ascolto sulla porta 5701. Poi come avrete notato avviando HelloServer, la sua esecuzione non termina, visto che se non viene fatto lo shutdown del server questo rimane in ascolto. Passiamo ora al client che si connette e legge la Map che abbiamo condiviso.

In questo caso viene condiviso un Map, ma HazelCast ci permette di rendere distribuiti anche altre tipologie di oggetti. Di seguito viene riportata una lista degli oggetti che è possibile distribuire con il link alla relativa documentazione ufficiale:

 

Publish & Subscribe

Come avete visto dalla precedente lista di oggetti, è possibile anche distribuire un Topic, che è qualcosa di simile al Topic presente nella specifica JMS. Infatti HazelCast, pur non implementando la specifica JMS, può essere utilizzato anche per lo scambio di messaggi, facendo riferimento al modello publish/subscribe (pub/sub). Vediamo quindi l’esempio di una classe che pubblica ogni 5 secondi diverse stringhe

Utilizzando l’oggetto HazelcastInstance prendiamo il riferimento al topic su cui pubblicare la notizia per l’allerta meteo (scusate ma non trovavo un esempio significativo 😛 ) e successivamente richiamiamo il metodo publish di ITopic. In questo caso viene inserita nel topic una semplice stringa ma sarebbe stato possibile anche distribuire un qualsiasi oggetto definito da noi. Passiamo ora al codice della classe che legge dal topic

In questo caso dobbiamo implementare l’interfaccia com.hazelcast.core.MessageListener che prevede la definizione del metodo onMessage (molto simile alla controparte JMS). Nel costruttore di TopicSubscriber ci agganciamo ad HazelCast, recuperiamo il topic d’interesse e aggiungiamo la stessa classe come listener.

 

Configurazione

La configurazione dei nodi e delle informazioni distribuite può essere fatta in due diverse modalità: file di configurazione o in maniera programmatica. Il file di configurazione XML viene prima ricercato nel file specificato dalla proprietà di sistema hazelcast.config. Se non è presente questa proprietà allora viene ricercato nel classpath il file hazelcast.xml e se nemmeno questo è presente viene caricato il file di default hazelcast-default.xml presente nel file hazelcast.jar. Per configurarlo in maniera programmatica invece possiamo agire direttamente sull’oggetto Config come riportato nel seguente esempio

Per quanto riguarda la scalabilità di HazelCast c’è da evidenziare prima di tutto le seguenti feature comuni a tutti i tipi di dati distribuiti su questo data grid:

  • I dati presenti sono partizionati su tutti i nodi del cluster.
  • Se un membro del cluster va giù, il nodo designato come sua replica di backup inizia a ridistribuire i suoi dati sugli altri nodi.
  • Quando un nuovo nodo si aggiunge al cluster, gli vengono assegnati una frazione dei dati presenti nel cluster dei quali è responsabile (alleggerendo il carico degli altri nodi) ed eventualmente ne aggiunge altri al cluster.
  • Non esiste un singolo nodo/cluster master che può causare quello che viene chiamato un single point of failure. Ogni nodo ha eguali responsabilità.

 

Oltre a queste caratteristiche in HazelCast possiamo effettuare una configurazione davvero dettagliata a livello di nodi del cluster, partizionamento della rete, listener di migrazione fra diversi nodi etc. etc. Per un analisi dettagliata di queste feature vi rimando alla documentazione ufficiale su questo argomento

 

MapLoader

HazelCast prevede la possibilità di caricare i map distribuiti da un’altra fonte dati. Uno scenario tipico potrebbe essere quello in cui vogliamo rendere disponibili in sola lettura delle informazioni che vengono richieste frequentemente, senza dover accedere direttamente al database. In questo caso potremmo popolare HazelCast con i valori prendendoli dal database quando vengono richiesti. Proprio per questo esiste l’interfaccia MapLoader che prevede i seguenti metodi

  • load : Ritorna il valore di una specifica chiave
  • loadAll: Ritorna un map contenente le coppie chiave-valore per certe chiavi richieste
  • loadAllKeys: Ritorna tutte le chiavi presenti

 

Ipotizziamo quindi di voler trasferire su un map distribuito tutte le coppie chiave-valore presenti in un nostro file di properties. Ecco come dovrebbe essere implementata la classe che effettua questa gestione

Praticamente la classe si carica in memoria il properties e poi in base al metodo che viene richiamato ci permette di leggere i valori. Quando HazelCast vede che viene richiesto un valore non presente per un determinato map, se è stato configurato un MapLoader cerca di caricarlo utilizzando questa classe. Ovviamente una volta lette le coppie chiave-valore, queste saranno memorizzati nel map distribuito di HazelCast, quindi non verrà più richiamato nessun metodo load (a meno che non venga settato a null questo valore). Per il client che effettua la chiamata questa operazione di caricamento è trasparente, nel nostro esempio la classe potrebbe essere la seguente

Per configurare il MapLoader possiamo procedere, come abbiamo visto in precedenza, attraverso il file di configurazione o in maniera programmatica. Per farlo attraverso lo start di nodo possiamo utilizzare la seguente classe

Praticamente nell’oggetto di tipo Config che utilizziamo per avviare il nodo andiamo a specificare che ci sarà un map di nome “dati” e che avrà associato un MapStore di tipo MyStore. Volendo utilizzare la configurazione xml ecco cosa dobbiamo riportare

 

MapStore

Abbiamo visto come poter popolare un map con dei valori presi da un’altra fonte dati, ora vediamo come riversare quello che c’è nel map altrove. In maniera similare a MapLoader, è stata definita l’interfaccia MapStore che prevede i metodi per salvare e rimuovere le chiavi da un’altra eventuale fonte dati. Per utilizzare questa feature dobbiamo utilizzare la stessa configurazione usata per MapLoader e quindi possiamo far implementare l’interfaccia MapStore alla nostra classe MyStore. Provando ad inserire dei metodi che effettuano un semplice log vi renderete conto che, quando un client richiede l’aggiornamento di un valore del map o la cancellazione, verranno richiamati questi metodi.

 

Alternativa a Memcached

Memcached è un famoso sistema di cache distribuita ed esistono molte librerie e client che permettono di configurare il nostro applicativo per il suo utilizzo. HazelCast può essere utilizzato in alternativa a Memcached senza nessun cambiamento in quanto fornisce la stessa interfaccia per la connessione. Di seguito un esempio riportato dalla documentazione ufficiale dove c’è un classico esempio di connessione da parte di un applicativo PHP, che può essere utilizzato anche nel caso in cui lo facciamo connettere al nostro server HazelCast

 

Web Clustering

Nelle applicazioni web dove sono presenti diversi server in cluster c’è spesso l’esigenza di avere la sessione replicata all’interno del cluster. In questo modo se l’utente con una sessione attiva sta dialogando con un server che cade, abbiamo la possibilità di continuare a lavorare su un altro server del cluster senza nessun problema. HazelCast può essere utilizzato anche in questo modo, effettuando una semplice configurazione sul web.xml della nostra applicazione. Dopo aver aggiunto le seguenti librerie di HazelCast al nostro progetto

possiamo aggiungere la seguente configurazione al web.xml

Abbiamo definito un nuovo filtro nella nostra applicazione, istanza della classe com.hazelcast.web.WebFilter. Questo filtro è stato inizializzato con i seguenti parametri:

  • map-name: il nome del map distribuito che conterrà le nostre sessioni http
  • sticky-session: specifica se la vostra applicazione ha attiva la configurazione sticky-session sul load balancer
  • debug: abilita le informazioni di debug

 

Oltre a questo è stato anche definito un listener di tipo com.hazelcast.web.SessionListener. Ed ecco le sessioni clusterizzate senza troppa fatica.

 

Conclusioni

In questo articolo abbiamo visto una serie di feature di HazelCast, che sicuramente ci fanno capire le potenzialità di questo prodotto opensource. Senza entrare nella discussione riguardante le performance di questo prodotto, sicuramente possiamo notare quanto sia estremamente flessibile e versatile per diverse situazioni. Qui di seguito trovate il link al progetto GitHub contenente i sorgenti dell’articolo.
 

 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *