# RabbitMQ

RabbitMQ è un message-oriented middleware (detto anche broker di messaggistica) che implementa il protocollo Advanced Message Queuing Protocol (AMQP). Il server RabbitMQ è scritto in Erlang e si basa sul framework Open Telecom Platform (OTP) per la gestione del clustering e del failover. Le librerie client per interfacciarsi a questo broker sono disponibili per diversi linguaggi.
Un broker di messaggi è un programma intermedio che traduce un messaggio dal protocollo di messaggistica formale del mittente al protocollo di messaggistica formale del ricevitore. I broker di messaggi sono elementi di telecomunicazione o reti di computer in cui le applicazioni software comunicano scambiando messaggi definiti in modo formale.

## Storia

“LShift and CohesiveFT have launched RabbitMQ, a complete open source implementation of Advanced Message Queuing Protocol (AMQP), the emerging standard for high performance enterprise messaging.”
Nel Febbraio 2007 con queste parole la joint venture tra LShift Archiviato il 24 maggio 2018 in Internet Archive. e CohesiveFT lanciò sul mercato il software RabbitMQ, per divenire in poco tempo tra i più popolari strumenti di messaggistica asincrona.
Nell'aprile 2010 è stata acquistata da SpringSource, una divisione di VMware, che ha aggiunto il sistema di messaggistica aperta RabbitMQ nella sua suite di tecnologie che riducono la complessità associata allo sviluppo, all'implementazione e alla gestione delle applicazioni aziendali. Tuttavia i termini dell'accordo non sono stati divulgati.

## Descrizione

Un broker di messaggi è un modello architetturale per la convalida, la trasformazione e il routing dei messaggi. Il broker media la comunicazione tra le applicazioni, riducendo al minimo la consapevolezza reciproca, ovvero quella che le applicazioni dovrebbero avere l'una rispetto all'altra al fine di poter scambiare messaggi, implementando in modo efficace il disaccoppiamento.
Lo scopo principale di un broker è di prendere i messaggi in arrivo dalle applicazioni ed eseguire alcune azioni su di essi. Ad esempio, un broker di messaggi può essere utilizzato per gestire una coda di carico di lavoro o una coda di messaggi per più ricevitori, fornendo memoria affidabile, con una consegna di messaggi garantita.
Nel tipico scenario dello scambio di messaggi, RabbitMQ introduce, oltre la presenza del Publisher, del Consumer e della Queue, un nuovo elemento: l’Exchange. Attraverso questa modifica avviene l’implementazione del protocollo AMQP.
Tale implementazione differisce dal precedente scenario definito dal JMS, nel quale si ha la presenza dei tre elementi sopracitati ed in cui si applica un metodo di transito FIFO in una singola coda.
Con RabbitMQ il Publisher non invia più il messaggio direttamente alla coda, ma passa per l’Exchange, il quale crea la comunicazione con la coda attraverso un legame detto binding.
RabbitMQ offre la possibilità di utilizzare diversi tipi di Exchange al fine di soddisfare i differenti bisogni. Tuttavia si possono delineare tre principali categorie:
* Fanout
* Direct
* Topic
Ognuna di queste categorie definisce un diverso comportamento rispetto a come viene indirizzato il messaggio dall’Exchange alle code. Utilizzando l’Exchange viene a determinarsi un sistema definito Pub/Sub. Infatti nella realtà non è presente un solo consumatore, bensì migliaia. Per questo motivo il messaggio pubblicato dal Publisher viene inviato a tutte le code sottoscritte ad uno specifico Exchange relativo al Publisher mittente.

### Fanout

Lo scambio Fanout trasmette tutti i messaggi ricevuti a tutte le code che conosce.
Una volta che il Publisher pubblica un messaggio, questo viene lavorato dall’Exchange che lo inoltra a tutte le code sottoscritte a lui.

### Direct

Nello scambio diretto, il messaggio viene inoltrato alle code la cui chiave di associazione corrisponde esattamente alla chiave di Routing (etichetta) del messaggio. Nello scambio diretto è possibile associare più code con la stessa chiave di associazione.
Nell'esempio, Q1 è vincolato con la chiave di binding Orange, Q2 con Yellow e Q3 con Orange, Black e Green.
Se l’Exchange riceve il messaggio con la chiave di Routing Orange, invia il messaggio a Q1 e Q3.
Se l’Exchange riceve il messaggio con la chiave di Routing Yellow, invia un messaggio a Q2.
Infine se l’Exchange riceve il messaggio con la chiave di Routing Green lo inoltra a Q3.

#### Fanout VS Direct

Nel Fanout i messaggi vengono inviati a tutte le code che sono legate all'Exchange senza verificare le chiavi di legame (binding key) rispetto alla chiave di instradamento (routing key). Tali chiavi possono essere specificate nel modello, ma nel Fanout non influenzano il comportamento dell’Exchange. Nel modello Direct è fondamentale la presenza delle chiavi e soprattutto il matching fra la chiave di instradamento e la chiave di legame. L’Exchange invia i messaggi alle code per cui è valida la condizione {Routing Key == Binding Key}.

### Topic

L'Exchange Topic è una sintesi dei modelli Fanout e Direct. Viene usato il modello Topic se nello scambio diretto vi è la possibilità di legare la chiave di instradamento con le code, ma senza la possibilità di effettuare associazioni (matching) in base al modello.
Nel modello Topic si instradano i messaggi verso una o più code in base al modello utilizzato per associare una coda all'Exchange. Quest’ultimo tipo di scambio lascia maggiore libertà nello specificare le chiavi di legame permettendo l’utilizzo dei cosiddetti jolly (o wildcards). Il primo è il carattere "*" che sostituisce l’utilizzo di una singola parola mentre “#” rappresenta zero o più parole.
I messaggi del modello Topic inviati a un Exchange non possono avere una Routing key arbitraria, ma deve essere un elenco di parole, delimitato da punti. Esempi validi di chiavi di Routing key sono "stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit".
Le code possono essere collegate con l’Exchange usando modelli come "* .usd. *", ottenendo tutti i messaggi in cui "usd" è una parte centrale del messaggio.
Facendo riferimento all'immagine che riporta un esempio di comportamento del modello Topic, tre differenti code vengono identificate dalla loro Binding key. Se il messaggio che arriva all’Exchange presenta una Routing key come “shape”, esso verrà inviato alla terza coda che rispetta esattamente il matching e alla seconda coda poiché nel proprio pattern è presente sia shape che il carattere "#", ovvero zero o più parole. Al contrario la prima coda non rispetta il matching poiché la struttura della sua chiave di legame richiede la presenza di due parole (di cui una deve essere shape).
Se invece la Routing key fosse “shape.circle” il messaggio è instradato verso la prima e la seconda coda ma non nella terza, poiché quest'ultima richiede la presenza della sola parola shape.
Per ottenere il comportamento di un Exchange Fanout dobbiamo utilizzare come Binding Key il semplice "#", che permette l’instradamento a prescindere dalla Routing Key poiché vi è sempre il matching con zero o più parole.

## RabbitMQ nei Data Center

Un cluster RabbitMQ è un cluster in cui è richiesta la presenza di almeno due nodi Rabbit (preferibilmente 3). Uno di essi è il nodo primario e gli altri sono definiti secondari. Il cluster RabbitMQ viene utilizzato poiché fornisce high availability (HA), ovvero se un nodo si guasta, gli altri possono continuare a svolgere il proprio compito. Tutti i nodi devono trovarsi nella stessa LAN, poiché ogni nodo verifica la presenza degli altri attraverso il controllo degli heartbeat. Nel momento in cui i nodi, appartenenti a due Data Center, registrano una disconnessione, fanno fronte a tale problematica continuando nel funzionamento in modo indipendente; tale scenario non offre più i vantaggi di affidabilità presenti prima della disconnessione.
Nel cluster RabbitMQ, gli scambi e le configurazioni vengono replicati su tutti i nodi al fine di ottenere HA. Ogni nodo può essere dichiarato come nodo Disk-backed o nodo Memory-backed. Vi è bisogno di almeno un nodo Disk-backed che possa, nell'eventualità di un crash sull’intero sistema, mantenere le informazioni e garantire dunque maggiore affidabilità. I nodi Memory-backed sono molto più veloci nella replica dei dati.
Per ottenere un cluster HA, è necessario disporre di Mirrored Queues, che offrono un miglioramento rispetto alle prestazioni delle Regular Queues. Queste ultime risiedono sul nodo dichiarato o creato e hanno una miglior performance in velocità di esecuzione/message throughput, ma sono time sensitive e time limited. Al contrario, le Mirrored Queues permettono la duplicazione dei messaggi su tutti i nodi, al costo di una peggiore performance/message throughput. Ne deriva che il throughput dell’intero sistema dipende dai throughput di tutti i nodi nel sistema.
La soluzione migliore è rappresentata dal trade-off dei due scenari analizzati precedentemente, garantendo una copia del messaggio su tutti i nodi e dunque maggiore affidabilità.

### Esempio

In un cluster RabbitMQ HA con bilanciamento del carico, in cui le code rappresentano strutture singolari, ovvero che non esistono su più di un nodo, si ha lo scenario di seguito descritto. I nodi 1 e 3 vengono replicati, in modo tale che le istantanee di tutte le code compatibili con HA siano sincronizzate su ciascun nodo. Viene creata una nuova coda che, essendo il Load Balancer schedulato in modalità round robin, risiede sul nodo 2 ed è definita “NewQueue” (tuttavia è possibile scegliere esplicitamente il nodo su cui si desidera creare la coda).
La politica HA richiede di replicare la coda su tutti i nodi presenti nel cluster. Nel momento in cui vengono aggiunti messaggi alla coda, essi vengono replicati su ciascun nodo. In sostanza, viene eseguita un'istantanea della coda replicata su ciascun nodo attraverso un'attività in background asincrona, ogni qual volta lo stato della coda cambia. Quindi, collegandosi a RabbitMQ e puntando a "NewQueue", Load Balancer indirizza ad un nodo appropriato. Si può assumere di essere connessi all'istanza di "NewQueue" che risiede su quel nodo.
Tuttavia non è questo il caso. Infatti si deve sempre tenere a mente che una coda RabbitMQ è una struttura singolare, cioè esiste solo sul nodo su cui è stata creata, indipendentemente dal criterio HA. Una coda sarà sempre master ed avrà associati da 0,1,...,N slave. In base all'esempio precedente, "NewQueue" del nodo 2 è la coda master, poiché questo è il nodo su cui è stata creata, mentre i nodi 1 e 3 rappresentano gli slave.
Supponendo che il nodo 2 “muoia”, tecnicamente esso non restituisce un heartbeat ed è considerato disaggregato. In tale scenario la coda master "NewQueue" non è più disponibile, quindi RabbitMQ promuove l'istanza dello slave "NewQueue" sul nodo 1 o 3. Questo comportamento evidenzia i vantaggi di RabbitMQ per quanto concerne gli aspetti dell'affidabilità.
Nel caso in cui tutti e 3 i nodi sono vivi e l'istanza "NewQueue" sul nodo 2 è master, si torna allo scenario di default. Nel momento in cui RabbitMQ si collega, con targeting "NewQueue", il Load Balancer determina un nodo appropriato, attraverso lo scheduling Round-Robin, scegliendo ad esempio il nodo 3. RabbitMQ reindirizza al nodo 2 (master), portando a termine con successo una connessione all'istanza master di "NewQueue".
Nonostante il fatto che le code siano replicate su ciascun nodo, esiste una sola istanza disponibile di ogni coda e risiede sul nodo su cui è stata creata o, in caso di errore, sull'istanza che viene promossa come master. RabbitMQ opera al fine di reindirizzare al nodo master in ogni caso. Questo significa effettuare un hop di rete extra per raggiungere la coda prevista. Nell’esempio con 3 nodi e un Load Balancer bilanciato in modo uniforme, si sostiene un ulteriore salto di rete su circa il 66% delle richieste. Solo una richiesta su tre viene indirizzata al nodo corretto.
Per garantire l’instradamento di ogni richiesta al nodo corretto, ci sono due possibilità:
* Connettersi esplicitamente al nodo su cui si trova la coda di destinazione.
* Distribuire le code in modo uniforme nei nodi.
Nel primo caso, l’applicazione client deve essere a conoscenza di tutti i nodi nel cluster RabbitMQ e deve sapere dove si trova ogni coda master.
La seconda soluzione offre un design in cui le code non sono collegate a nodi singoli. Tornando all’esempio, vengono istanziate 3 code: "NewQueue1", "NewQueue2" e "NewQueue3", ognuna su un nodo separato. L’applicazione client può ora implementare una funzione randomizzante che seleziona una delle tre precedenti code e si connette esplicitamente ad essa.

## Esempi

In questa sezione si trova un esempio di programma scritto in Python per inviare e ricevere messaggi su una coda.

### Invio

Il seguente codice stabilisce una connessione, controlla l'esistenza della coda, invia il messaggio e chiude la connessione:

### Ricezione

Il seguente codice stabilisce una connessione, controlla l'esistenza della coda, riceve il messaggio e lo stampa a schermo: