Primi Passi con Vert.x (parte 2)

Dopo aver introdotto il framework Vert.x nell’articolo Primi Passi con Vert.x (parte 1) descrivendone l’architettura e presentando un semplice esempio di produttore-consumatore, vediamo alcuni concetti avanzati del framework.

Istanze Verticle

Quando un verticle viene deployato è possibile indicare il numero di istanze che si intende avviare. Questo è molto utile per scalare in modo semplice l’applicazione su tutti i core di cui dispone la CPU. Per farlo è sufficiente specificare l’opzione instances della classe DeploymentOptions. Ad esempio possiamo avviare più istanze del produttore di numeri casuali modificando il codice nel seguente modo:

Supponendo di valorizzare il parametro instances del metodo startProducer() con 2, l’output prodotto per ogni secondo di esecuzione dell’applicazione diviene del tipo:

in cui si evince che esistono 2 istanze del produttore e che il consumatore riceve quindi due numeri casuale, uno per ogni istanza.

Configurazione di un Verticle

Al momento del deploy di un verticle è possibile indicare alcuni parametri di configurazione che saranno recuperati dall’istanza ed utilizzati internamente. Per farlo è possibile utilizzare una istanza dell’oggetto JsonObject e passarlo al metodo setConfig() dell’oggetto DeploymentOptions. Unico requisito è l’inserimento nel pom.xml della dipendenza a  Jackson Core Databind:

Proviamo ad esempio a modificare la classe RandomNumberProducerVerticle del nostro esempio, in modo da poter indicare come configurazione il valore massimo del numero intero generato. Modifichiamo quindi il metodo startProducer() inserendo un nuovo parametro intero max da passare all’istanza verticle:

La classe RandomNumberProducerVerticle può leggere i parametri in configurazione utilizzando il metodo config() ereditato dalla classe astratta AbstractVerticle.

Esecuzione di Codice Bloccante

Come detto nell’articolo precedente, Vert.x è stato progettato in modo da lavorare in modo asincrono, introducendo una serie di API non bloccanti. Questo però non impedisce al programmatore di generare codice bloccante, anche in modo involontario. Esempi di codici bloccanti sono:

  • utilizzo della direttiva Thread.sleep();
  • utilizzo della direttiva di sincronizzazione synchronized;
  • operazioni di I/O o su DB molto onerose;
  • algoritmi di calcolo complessi;

Il risultato dell’esecuzione di un codice bloccante è il blocco dell’event loop e l’impossibilità del framework di rispondere ad un qualsiasi altro evento. Si tratta quindi di una situazione da evitare assolutamente ed il framework stesso monitorizza continuamente i suoi thread generando una eccezione nel caso in cui uno di essi non risponda per un “lungo periodo di tempo”.

Poiché in generale può accadere che ci sia la necessità di eseguire un codice bloccante, il framework mette a disposizione il metodo executeBlocking() della classe Vertx. Tale metodo riceve in input due handler, il primo è il codice bloccante mentre il secondo è il codice eseguito al termine dell’esecuzione del primo. Un esempio banale dell’utilizzo di tale metodo è il seguente:

Importante per triggerare l’esecuzione del secondo handler è l’invocazione del metodo complete() sull’istanza future restituita dal metodo  executeBlocking() all’handler che gestisce il codice bloccante.

Di fatto questo metodo avvia un worker thread per eseguire il codice che gli viene assegnato. Tale thread è recuperato da un pool di worker thread che è associato ad ogni event loop. E’ possibile configurare tale pool al momento del deploy del verticle utilizzando l’opzione setWorkerPoolSize() in DeploymentOptions. Pool di thread aggiuntivi possono essere generati ed utilizzati per l’esecuzione del codice bloccante:

Quando diversi worker executor vengono creati con lo stesso nome, condivideranno lo stesso pool. Il pool di worker sarà distrutto quando tutti gli executor worker che lo utilizzano vengono chiusi. Quando un executor è creato in un verticle, Vert.x lo chiuderà automaticamente quando il verticle viene un-deployato.

Event Loop Thread vs Worker Thread

Concludendo Vert.x gestisce due tipologie di thread: Event Loop Thread e Worker Thread. Di default il framework crea tanti Event Loop Thread quanti sono i core della CPU ed un pool di 20 Worker Thread. Gli Event Loop Thread sono utilizzati per gestire eventi asincroni (lettura dal buffer di uno stream, messaggio ricevuto da una coda, ecc.). I Worker Thread sono invece utilizzati per eseguire il codice bloccante dell’applicazione.

I verticle possono essere di tre tipi in funzione del tipo di thread a cui è associata la sua esecuzione. In particolare quelli associati alle due tipologie di thread sopra menzionate sono:

  • Standard Verticle: sono i più comuni e sono sempre assegnati ad un Event Loop Thread. Il framework garantisce che tutto il codice dichiarato nella istanza di verticle è sempre eseguito sullo stesso Event Loop (purché non venga esplicitamente creato ed invocato un nuovo thread!).
  • Worker Verticle: sono verticle eseguiti utilizzando un thread prelevato dal Worker Thread Pool e specificatamente utilizzati per l’esecuzione di codice bloccante. Diverse istanze di Worker Verticle non vengono mai eseguite contemporaneamente da più di un thread, ma possono essere eseguite da thread diversi in momenti diversi. Per generare un Worker Verticle è sufficiente impostare a TRUE il parametro worker  della classe  DeploymentOptions:

Codice Sorgente

Il codice sorgente con tutti gli esempi mostrati è scaricabile qui vertx

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!