Marcin
Lewandowski
Marcin
Lewandowski
Marcin
Lewandowski
Marcin
Lewandowski
09:00 do 09:15 - Rozpoczęcie dnia, sprawy organizacyjne
09:15 do 10:45 - Blok szkoleniowy
10:45 do 11.00 - Przerwa kawowa ( 15 min. )
11:00 do 13:00 - Blok szkoleniowy
13:00 do 13:45 - Przerwa obiadowa ( 45 min. )
13:45 do 14:45 - Blok szkoleniowy
14:45 do 15:00 - Przerwa kawowa ( 15 min. )
15:00 do 16:00 - Blok szkoleniowy
Marcin
Lewandowski
apt-get install rabbitmq-server
Instalacja sprowadza się do wydania jednego polecenia:
Po instalacji sprawdzamy stan serwera
/etc/init.d/rabbitmq-server status
Instalacja serwera
apt-get update
Przed instalacją aktualizujemy repozytoria
Zalogowanie na administratora
sudo su
Możemy także sprawdzić stan serwera za pomocą polecenia
rabbitmqctl status
Polecenie to zwraca dużo więcej informacji o serwerze i jego stanie. Z wyświetlonych informacji możemy dowiedzieć się:
Instalacja serwera
Do instalacji pluginów mamy specjalne polecenie:
rabbitmq-plugins
Instalacja pluginów
rabbitmq-plugins --help
Dodając flagę help dowiemy się jakie to polecenie ma możliwości
Nas interesuje sekcja Plugin Management
disable Disables one or more plugins
enable Enables one or more plugins
list Lists plugins and their state
set Enables one or more plugins, disables the rest
Sprawdźmy jakie pluginy możemy zainstalować
rabbitmq-plugins list
Instalacja pluginów
Jest ich sporo
[ ] rabbitmq_amqp1_0 3.8.2
[ ] rabbitmq_auth_backend_cache 3.8.2
[ ] rabbitmq_auth_backend_http 3.8.2
[ ] rabbitmq_auth_backend_ldap 3.8.2
[ ] rabbitmq_auth_backend_oauth2 3.8.2
[ ] rabbitmq_auth_mechanism_ssl 3.8.2
[ ] rabbitmq_consistent_hash_exchange 3.8.2
[ ] rabbitmq_event_exchange 3.8.2
[ ] rabbitmq_federation 3.8.2
[ ] rabbitmq_federation_management 3.8.2
...
Nas interesuje plugin rabbitmq_management i to go włączamy
rabbitmq-plugins enable rabbitmq_management
Instalacja pluginów
Co w rezultacie spowodowało automatyczne włączenie dwóch innych pluginów: rabbitmq_management_agent, rabbitmq_web_dispatch.
Enabling plugins on node rabbit1
rabbitmq_management
The following plugins have been configured:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit1
The following plugins have been enabled:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
started 3 plugins.
Aby nowo zainstalowany plugin działał konieczny jest restart serwera
/etc/init.d/rabbitmq-server restart
Instalacja pluginów
Teraz w przeglądarce otwieramy adres http://localhost:15672 pod którym powinniśmy zobaczyć ekran logowania do panelu administracyjnego.
Login: guest
Hasło: guest
Instalacja Docker
apt-get install docker.io
Instalacja Docker Compose
apt-get install docker-compose
Wymagania:
Plik z konfiguracją środowiska testowego
version: '3'
services:
rabbit:
image: rabbitmq:3.8-management
ports:
- "5673:5672"
- "15673:15672"
Uruchomienie środowiska
docker-compose up
Uruchomienie środowiska w tle
docker-compose up -d
Sprawdzenie uruchomionych kontenerów
docker ps
Zatrzymanie środowiska
ctrl + c
Zatrzymanie środowiska
docker-compose down
Wprowadzenie do RabbitMQ
Aplikacja ustanawia jedno połączenie z RabbitMQ
Połączenie powinno trwać tak długo jak aplikacja żyje
Połączenie TCP
duży narzut związany z nawiązaniem połączenia,
przesyłanie nadmiarowych informacji dotyczących samego połączenia, a nie danych
Wprowadzenie do RabbitMQ
Aplikacja po nawiązaniu połączenia ustanawia kanały komunikacji,
Jedna aplikacja może ustanowić wiele kanałów komunikacji,
W ramach kanałów komunikacji możliwe jest:
deklarowanie central, kolejek, połączeń pomiędzy nimi itp.
publikowanie wiadomości,
pobieranie wiadomości
...
Połączenie TPC
Wprowadzenie do RabbitMQ
Wprowadzenie do RabbitMQ
lekki protokół opracowany przez IBM
broker pełni rolę serwera
aynchroniczny
Wprowadzenie do RabbitMQ
Wprowadzenie do RabbitMQ
Włączenie obsługi MQTT w RabbitMQ
rabbitmq-plugins enable rabbitmq_mqtt
# restart RabbitMQ
/etc/init.d/rabbitmq-server restart
Instalacja klienta MQTT
apt install mosquitto-clients
Subskrypcja tematu czujnik_1
mosquitto_sub -h 127.0.0.1 -p 1883 -t "czujnik_1"
Publikowanie wiadomości w temacie czujnik_1
mosquitto_pub -h 127.0.0.1 -p 1883 -t "czujnik_1" -m "Odczyt 1"
Wprowadzenie do RabbitMQ
Wprowadzenie do RabbitMQ
protokół podobny do HTTP, TCP
aynchroniczny
Wprowadzenie do RabbitMQ
Wprowadzenie do RabbitMQ
Wprowadzenie do RabbitMQ
Zarządzanie - Panel administracyjny
Zarządzanie - Panel administracyjny
Lista Virtual Host
Dodawanie Virtual Host
Zarządzanie - Panel administracyjny
Szczegóły dostępne po kliknięciu w nazwę
W podglądzie dostępnych jest klika bardzo istotnych opcji:
Zarządzanie - Panel administracyjny
Zarządzanie - CLI
rabbitmqctl list_vhosts
Lista
Dodawanie
rabbitmqctl add_vhost app_1
Usuwanie
rabbitmqctl delete_vhost app_1
Zarządzanie - CLI
rabbitmqctl set_permissions -p app_1 guest ".*" ".*" ".*"
Przypisywanie uprawnień
rabbitmqctl set_topic_permissions -p app_1 guest amq.topic ".*" ".*"
Przypisywanie uprawnień do Topic
rabbitmqctl clear_permissions -p app_1 guest
rabbitmqctl clear_topic_permissions -p app_1 guest amq.topic
Uwierzytelnianie
Tagi - definiują uprawnienia dla panelu administracyjnego
Uprawnienia - definiują możliwości użytkownika w zakresie wykonywanych operacji konfiguracyjnych, odczytu i zapisu wiadomości
Tagi - Panel administracyjny
W przypadku, gdy mówimy o uprawnieniach do panelu administracyjnego myślimy o Tagach. Dzięki nim mamy różne poziomy dostępu do panelu administracyjnego.
Tagi - Panel administracyjny
Tagi - Panel administracyjny
Nasz użytkownik guest na, którym obecnie pracujemy ma tag administrator. Dzięki czemu ma uprawnienia do wszystko w panelu administracyjnym.
Dodawanie użytkownika - PA
Dodawanie użytkownika - CLI
rabbitmqctl add_user app_1 password
Dodawanie użytkownika - CLI
# przypisujemy tag administrator
rabbitmqctl set_user_tags app_1 administrator
Uprawnienia
Mamy trzy rodzaje uprawnień:
Przypisywanie uprawnień, jest oparte o wyrażenia regularne.
Uprawnienia - PA
Uprawnienia - CLI
rabbitmqctl list_user_permissions app_1
Lista uprawnień użytkownika
Usuwanie uprawnień
rabbitmqctl clear_permissions -p app_1 app_1
Dodawanie uprawnień
PRODUCENT
KONSUMENT
RabbitMQ
var amqp = require('amqplib/callback_api');
amqp.connect('amqp://localhost', function(err1, connection) {
if (err1)
{
return console.error(err1);
}
console.info('Connected');
connection.close();
});
connect.js
Połączenie z RabbitMQ
Połączenie z RabbitMQ
Scenariusze testowe:
amqp://username:password@localhost/vhost
Format wiadomości
RabbitMQ akceptuje dowolny ciąg znaków, co niesie ze sobą konsekwencje.
PRODUCENT 1
KONSUMENT
RabbitMQ
XML
XML
XML
Format wiadomości
RabbitMQ akceptuje dowolny ciąg znaków, co niesie ze sobą konsekwencje.
PRODUCENT 1
KONSUMENT
RabbitMQ
XML
XML JSON
PRODUCENT 2
JSON
Format wiadomości
RabbitMQ akceptuje dowolny ciąg znaków, co niesie ze sobą konsekwencje.
PRODUCENT 1
KONSUMENT
RabbitMQ
XML
XML JSON TXT
PRODUCENT 3
TXT
PRODUCENT 2
JSON
Format wiadomości
Wybierz format, który jest standardem jak JSON
Tworzenie wiadomości
var amqp = require('amqplib/callback_api');
amqp.connect('amqp://app_full:app_full@localhost/app_1', function(err1, connection) {
if (err1)
{
return console.error(err1);
}
console.info('Connected');
connection.createChannel(function(err2, channel) {
if (err2)
{
throw err2;
}
console.info('Channel created');
var queue = 'q_1';
var msg = JSON.stringify({
'text': 'Hello world'
});
channel.sendToQueue(queue, Buffer.from(msg));
console.log("[x] Sent %s", msg);
});
});
Tworzenie wiadomości
W panelu administracyjnym widzimy trwające połączenie i otwarty kanał komunikacji.
Tworzenie wiadomości
Nie widać, aby jakakolwiek wiadomość dotarła.
Tworzenie wiadomości
connection.createChannel(function(err2, channel) {
if (err2)
{
throw err2;
}
console.info('Channel created');
var queue = 'q_1';
var msg = JSON.stringify({
'text': 'Hello world'
});
channel.sendToQueue(queue, Buffer.from(msg));
console.log("[x] Sent %s", msg);
});
Tworzenie wiadomości
connection.createChannel(function(err2, channel) {
if (err2)
{
throw err2;
}
console.info('Channel created');
var queue = 'q_1';
var msg = JSON.stringify({
'text': 'Hello world'
});
// tworzy kolejkę jeśli nie istnieje
channel.assertQueue(queue, {
durable: false
});
channel.sendToQueue(queue, Buffer.from(msg));
console.log(" [x] Sent %s", msg);
});
Tworzymy kolejkę z poziomu kodu, jeśli nie istnieje
create_first_message_full.js
Tworzenie wiadomości
Tworzenie wiadomości
Tworzenie wiadomości
Tworzenie wiadomości
TTL Wiadomości
channel.sendToQueue(queue, Buffer.from(msg), {
expiration: 15000
});
TTL czyli Time To Live to określenie czasu życia wiadomości, które może być zdefiniowane na poziomie pojedynczej wiadomości.
Aby zobaczyć jak mechanizm zadziała otwieramy plik create_first_message_ttl.js, który zawiera dodatkowy parametr określający czas życia wiadomości na 15 sek.
TTL Wiadomości
Weryfikacja czy wiadomość została doręczona
Możliwe problemy z doręczeniem wiadomości:
Weryfikacja czy wiadomość została doręczona
Sprawdzimy scenariusz w którym osiągniemy limit wiadomości. W tym celu usuwamy kolejkę q_1 i tworzymy nową z dwoma parametrami.
Weryfikacja czy wiadomość została doręczona
Przechodzimy do pliku message_delivery_guaranteed.js
connection.createConfirmChannel(function(err2, channel) {
Pierwsza zmiana jaka zaszła to zmiana tworzonego kanału na kanał wymagający potwierdzeń createConfirmChannel
Dodany został kod generujący 10 wiadomości.
for (var i = 0; i < 10; i++)
{
var msg = JSON.stringify({
'text': 'Message ' + i
});
console.log(" [x] Sent %s", msg);
channel.sendToQueue(queue, Buffer.from(msg), {}, errorCallback(msg));
}
Weryfikacja czy wiadomość została doręczona
function errorCallback(msg) {
return function(err) {
if (err)
{
return console.log('Message: ' + msg + ' - NOT confirm');
}
console.log(msg + ' - confirm');
};
}
Przy każdym wysłaniu wiadomości do kolejki mamy funkcję, która jest wywoływana. Dozwala nam to obsłużyć błędy.
Weryfikacja czy wiadomość została doręczona
channel.waitForConfirms(function(err) {
if (err)
{
return console.log('Not confirm all message');
}
console.log('All messages confirm');
});
Ostatni element jaki został dodany to wywołanie specjalnej metody, która czeka aż wszystkie wiadomości się wykonają lub zwrócą błędy.
Weryfikacja czy wiadomość została doręczona
Mając tak przygotowaną aplikację możemy przeprowadzić testy, które pokażą czy rzeczywiście dostaniemy informacje o braku dostarczenia wiadomości.
Weryfikacja czy wiadomość została doręczona
Kolejne uruchomienie aplikacji zwróci nam same błędy.
Weryfikacja czy wiadomość została doręczona
Usuńmy 2 wiadomości z kolejki, aby sprawdzić czy rzeczywiście tylko 2 wiadomości trafią po ponownym uruchomieniu aplikacji.
Weryfikacja czy wiadomość została doręczona
Uruchamiamy jeszcze raz aplikację i sprawdzamy czy zostaną dodane dwie nowe wiadomości, a dla pozostałych otrzymamy błąd.
Narzędzie do wizualizacji przepływu wiadomości w RabbitMQ
Fanout
Wszystkie wiadomości przesłane do centrali będą przekazywane do powiązanych kolejek.
Fanout
Wszystkie wiadomości przesłane do centrali będą przekazywane do powiązanych kolejek.
Fanout
Wszystkie wiadomości przesłane do centrali będą przekazywane do powiązanych kolejek.
Fanout
Definiujemy centralę wiadomości
fanout_ex
Fanout
Definiujemy dwie kolejki
fanout_q1 i fanout_q2
Fanout
Łączymy centralę z kolejkami binding
(możliwe z poziomu kolejki i centrali )
Fanout
Po połączeniu powinniśmy zobaczyć
wizualizację połączeń
Fanout
Wysyłamy wiadomość do centrali wiadomości
Fanout
Po wysłaniu wiadomości powinniśmy otrzymać wiadomość w obu kolejkach
Fanout
Treść możemy podejrzeć wchodząc do jednej z kolejek, a następnie w sekcji Get Messages mamy możliwość pobrania wiadomości z kolejki.
Fanout
connection.createChannel(function(error1, channel) {
if (error1)
{
throw error1;
}
var exchange = 'fanout_ex_app';
var msg = JSON.stringify({
'text': 'Hello world'
});
// tworzy centralę wiadomości
// channel.assertExchange(exchange, 'fanout', {
// durable: false
// });
channel.publish(exchange, '', Buffer.from(msg));
console.log("[x] Sent %s", msg);
});
producer_exchange_fanout.js
Fanout
connection.createChannel(function(error1, channel) {
if (error1){
throw error1;
}
var exchange = 'fanout_ex_app';
var queue1 = 'fanout_q1_app';
var msg = JSON.stringify({
'id': '145'
});
channel.assertExchange(exchange, 'fanout', {
durable: true
});
channel.assertQueue(queue1, {durable: true}, function(err2) {
if (err2){
throw err2;
}
channel.bindQueue(queue1, exchange);
producer_exchange_fanout_bind.js.js
Direct
Wiadomości przesłane do centrali muszą posiadać parametr routingKey, aby zostały przesłane do kolejki o takim samym parametrze.
Direct
Centrala w działaniu
Po co centrala ? Kolejka nie wystarczy ?
Direct
Funkcjonalność pobierz wszystkie
Direct
Odwzorozujemy strukturę w panelu administracyjnym
Direct
connection.createChannel(function(error1, channel) {
if (error1)
{
throw error1;
}
var exchange = 'direct_ex';
var msg = JSON.stringify({
'id': '145'
});
channel.publish(exchange, 'epub', Buffer.from(msg));
console.log("[x] Sent %s", msg);
});
Direct
connection.createChannel(function(error1, channel) {
if (error1) {
throw error1;
}
var exchange = 'direct_ex_app';
var queue1 = 'direct_epub_app';
var queue2 = 'direct_mobi_app';
var queue3 = 'direct_pdf_app';
var msg = JSON.stringify({
'id': '145'
});
channel.assertExchange(exchange, 'direct', {
durable: true
});
channel.assertQueue(queue1, {durable: true}, function(err2) {
if (err2){
throw err2;
}
channel.bindQueue(queue1, exchange, 'download_epub');
channel.assertQueue(queue2, {durable: true}, function(err3) {
if (err3){
throw err3;
}
channel.bindQueue(queue2, exchange, 'download_mobi');
channel.assertQueue(queue3, {durable: true}, function(err4) {
if (err4){
throw err4;
}
channel.bindQueue(queue3, exchange, 'download_pdf');
channel.publish(exchange, 'download_epub', Buffer.from(msg));
console.log("[x] Sent %s", msg);
});
});
});
});
Topic
info.log
error.log
all.log
#.log
info.*
error.*
all.*
Topic
Zastosowanie w praktyce bindowania z uwzględnieniem wyrażeń regularnych
Topic
Tworzymy nową centralę wiadomości
topic_ex
Topic
Tworzymy kolejki:
topic_info, topic_error, topic_all
Topic
Tworzymy powiązanie
topic_info <--- info.* -----> topic_ex
Topic
Tworzymy powiązanie
topic_error <--- error.* -----> topic_ex
Topic
Tworzymy powiązanie
topic_all <--- *.log -----> topic_ex
Topic
Wysyłamy wiadomość o routingu info.log
Topic
Wysyłamy wiadomość o routingu error.log
Topic
connection.createChannel(function(error1, channel) {
if (error1)
{
throw error1;
}
var exchange = 'topic_ex_app';
var queueInfo = 'topic_info_app';
var queueError = 'topic_error_app';
var queueAll = 'topic_all_app';
var msg = JSON.stringify({
'id': '145'
});
channel.assertExchange(exchange, 'topic', {
durable: true
});
channel.assertQueue(queueInfo, {durable: true}, function(err2) {
if (err2)
{
throw err2;
}
channel.bindQueue(queueInfo, exchange, 'info.*');
channel.assertQueue(queueError, {durable: true}, function(err2) {
if (err2)
{
throw err2;
}
channel.bindQueue(queueError, exchange, 'error.*');
channel.assertQueue(queueAll, {durable: true}, function(err2) {
if (err2)
{
throw err2;
}
channel.bindQueue(queueAll, exchange, '*.log');
console.log("Ready");
// channel.publish(exchange, 'info.log', Buffer.from(msg));
// console.log("[x] Sent %s", msg);
});
});
});
});
Headers
Headers
RabbitMQ Simulator niestety nie wspiera tego typu centrali wiadomości. Dlatego zaprojektujemy od razu w panelu poniższą strukturę.
Headers
Dodajemy centralę
Headers
Dodajemy centralę
Headers
Dodajemy kolejki
Headers
Definiujemy połączenia pomiędzy centralą, a kolejkami.
headers_ex <-> header_info
Headers
Definiujemy połączenia pomiędzy centralą, a kolejkami.
headers_ex <-> header_error
Headers
Definiujemy połączenia pomiędzy centralą, a kolejkami.
headers_ex <-> header_all
Headers
Po wszystkim powinniśmy w centrali headers_ex zobaczyć następujące połączenia
Możemy je teraz przetestować ;)
Headers
Publikujemy wiadomości z poziomu centrali
Headers
Publikujemy wiadomości z poziomu centrali
Headers
Publikujemy wiadomości z poziomu centrali
Headers
Aby zmienić zachowanie połączenia z kolejką header_all konieczne jest usunięcie bieżącego połączenia.
Headers
Na miejsce usuniętego połączenia dodajemy nowe z uwzględnieniem parametru x-match
Headers
Przed testami czyścimy zawartość kolejek, aby łatwiejsza była interpretacja wyników.
Headers
Publikujemy wiadomość w centrali
headers_ex
Headers
Publikujemy wiadomość w centrali
headers_ex
Headers
connection.createChannel(function(error1, channel) {
// ... pominięto kod
var exchange = 'headers_ex_app';
var queueError = 'headers_log_error_app';
var queueInfo = 'headers_log_info_app';
var queueAll = 'headers_log_all_app';
var msg = JSON.stringify({
'id': '145'
});
channel.assertExchange(exchange, 'headers', {
durable: true
});
channel.assertQueue(queueError, {durable: true}, function(err2) {
// ... pominięto kod
channel.bindQueue(queueError, exchange, '', {
'log_error': '1'
});
channel.assertQueue(queueInfo, {durable: true}, function(err2) {
// ... pominięto kod
channel.assertQueue(queueAll, {durable: true}, function(err2) {
// ... pominięto kod
channel.bindQueue(queueAll, exchange, '', {
'log_info': '1',
'log_error': '1'
});
channel.publish(exchange, '', Buffer.from(msg), {
'headers': {
'log_error': '1'
}
});
console.log("[x] Sent %s", msg);
});
});
});
});
Durable
/etc/init.d/rabbitmq-server restart
Auto delete
Internal
Centrala wewnętrzna nie pozwala na bezpośrednie publikowanie wiadomości przez producentów.
Internal
Centrala wewnętrzna nie pozwala na bezpośrednie publikowanie wiadomości przez producentów.
connection.createChannel(function(err1, channel) {
if (err1) {
throw err1;
}
var exchange = 'internal_ex_app';
var msg = JSON.stringify({
'id': '145'
});
channel.assertExchange(exchange, 'fanout', {
internal: true
});
channel.assertQueue('tmp_app', {durable: true}, function(err2) {
if (err2) {
throw err2;
}
channel.bindQueue('tmp_app', exchange, 'test');
channel.publish(exchange, 'test', Buffer.from(msg));
console.log("[x] Sent %s", msg);
});
});
Alternate exchange
Jeśli wiadomość nie jest rutowalna to zostanie przesłana do innej centrali
Zarządzanie
Zarządzanie - CLI
rabbitmqctl list_queues -p app_1
Lista kolejek
Jeśli chcemy wyświetlić konkretne kolumny to podajemy je na końcu polecenia.
rabbitmqctl list_queues -p app_1 name durable
rabbitmqadmin -V app_1 declare queue name=test
Dodawanie kolejek
Durable - przeżyć restart serwera
connection.createChannel(function (err2, channel) {
if (err2) {
throw err2;
}
console.info('Channel created');
var queue = 'q_durable_fale_app';
var msg = JSON.stringify({
'text': 'Czy wiadomość przetrwa restart serwera ??'
});
channel.assertQueue(queue, {
durable: false
});
channel.sendToQueue(queue, Buffer.from(msg));
console.log("OK");
});
Producent tworzy kolejkę z ustawionym parametrem durable na false. Co spowoduje, że kolejka zostanie usunięta po restarcie serwera.
/etc/init.d/rabbitmq-server restart
durable_false_producer.js
TTL
Wcześniej definiowaliśmy TTL na poziomie wiadomości,
ale czas życia wiadomości może być zdefiniowany w kolejce.
TTL
Do tak zdefiniowanej kolejki możemy dodać wiadomość i zobaczyć czy rzeczywiście zostanie usunięta po 15 sekundach.
TTL
connection.createChannel(function (err2, channel) {
if (err2) {
throw err2;
}
console.info('Channel created');
var queue = 'q_ttl_app';
var msg = JSON.stringify({
'text': 'TTL 15s.'
});
channel.assertQueue(queue, {
messageTtl: 15000
});
channel.sendToQueue(queue, Buffer.from(msg));
console.log("OK");
});
ttl_producer.js
Producent tworzy kolejkę z ustawionym maksymalnym czasem życia wiadomości na 15 sek.
Expiry
connection.createChannel(function (err2, channel) {
if (err2) {
throw err2;
}
console.info('Channel created');
var queue = 'q_expiry_app';
channel.assertQueue(queue, {
expires: 15000
});
var msg = JSON.stringify({
'text': 'Hello world'
});
channel.sendToQueue(queue, Buffer.from(msg));
console.log(" [x] Sent %s", msg);
});
expiry_producer.js
Producent tworzy kolejkę z ustawionym maksymalnym czasem życia na 15 sek. Jeśli w tym czasie nie podłączy się żaden konsument to kolejka zostanie usunięta.
Expiry
connection.createChannel(function(err2, channel) {
if (err2)
{
throw err2;
}
channel.consume('q_expiry_app', function(msg) {
console.log(msg);
});
console.info('OK');
});
expiry_consumer.js
Konsument podtrzymujący życie kolejki do czasu rozłączenia. Brak potwierdzenia pobrania wiadomości spowoduje oznaczenie wiadomości jako Unacked
Tymczasowe
Kolejka jest usuwana, gdy ostatni konsument zakończy połączenie
Tymczasowe
Uruchamiamy plik consumer_connect.js jego jedynym zadaniem jest podłączenie do kolejki. Pobranie wiadomości jeśli są jakieś w kolejce i wyświetlenie ich w konsoli.
W kolejce wyświetli się informacja, że jest podłączony jeden konsument.
Tymczasowe
Po zakończeniu działania konsumenta Ctrl + C kolejka powinna zostać automatycznie usunięta. A nam w panelu wyświetli się poniższy komunikat.
Tymczasowe
connection.createChannel(function (err2, channel) {
if (err2) {
throw err2;
}
console.info('Channel created');
channel.assertQueue('q_auto_delete', {
autoDelete: true
});
console.log("OK");
});
Producent tworzy kolejkę, która zostanie usunięta automatycznie po rozłączeniu ostatniego konsumenta
auto_delete_producer.js
Tymczasowe
connection.createChannel(function(err2, channel) {
if (err2)
{
throw err2;
}
channel.consume('q_auto_delete', function(msg) {
console.log(msg);
});
console.info('OK');
});
Konsument kolejki po rozłączeniu spowoduje automatyczne usunięcie kolejki
auto_delete_consumer.js
Max Length
connection.createChannel(function(err2, channel) {
if (err2)
{
throw err2;
}
console.info('Channel created');
var queue = 'q_max_length_10_app';
channel.assertQueue(queue, {
maxLength: 10
});
for (var i = 0; i < 20; i++)
{
var msg = JSON.stringify({
'text': 'Message ' + i
});
console.log(" [x] Sent %s", msg);
channel.sendToQueue(queue, Buffer.from(msg));
}
});
Umożliwia określenie maksymalnej ilości wiadomości jaka może być przechowywana w kolejce.
max_length_producer.js
Max Length + Overflow
connection.createChannel(function(err2, channel) {
if (err2){
throw err2;
}
console.info('Channel created');
var queue = 'q_max_length_10_app';
channel.assertQueue(queue, {
maxLength: 10,
overflow: 'reject-publish'
});
for (var i = 0; i < 20; i++)
{
var msg = JSON.stringify({
'text': 'Message ' + i
});
console.log(" [x] Sent %s", msg);
channel.sendToQueue(queue, Buffer.from(msg));
}
});
Parametr x-max-length występuje w połączeniu z parametrem
x-overflow. A to dlatego, że przepełnienie kolejki może spowodować pewne zachowania:
max_length_overflow_reject_producer.js
Single Active Consumer
Pozwala na wykorzystywanie kolejki tylko jednemu konsumentowi.
Single Active Consumer
connection.createChannel(function(err2, channel) {
if (err2)
{
throw err2;
}
channel.consume('q_single_active_consumer_app', function(msg) {
setTimeout(function () {
console.log(msg.content.toString());
channel.ack(msg);
}, 1000);
});
console.info('OK');
});
single_active_consumer_consumer.js
Konsument
Lazy
Dead Letter Exchange - DLX
Dead Letter Exchange - DLX
Dead Letter Exchange - DLX
Dead Letter Routing Key
Zastępuje routingKey, gdy wiadomość jest niepotwierdzona
PRODUCENT
KONSUMENT
RabbitMQ
Pobieranie wiadomości
connection.createChannel(function(err2, channel) {
if (err2) {
throw err2;
}
channel.consume('report_app', function(msg) {
console.log(msg.content.toString());
});
console.info('OK');
});
Pobieranie wiadomości
fields
properties
content
Zwracana wiadomość zawiera:
Potwierdzanie wiadomości
connection.createChannel(function(err2, channel) {
if (err2) {
throw err2;
}
channel.consume('test', function(msg) {
console.log(msg.content.toString());
// console.log('Potwierdzam przetworzenie');
// channel.ack(msg);
});
console.info('OK');
});
Obsługa błędów
connection.createChannel(function(err2, channel) {
if (err2) {
throw err2;
}
channel.consume('report_app', function(msg) {
console.log(msg.content.toString());
console.log('Error - powrót do kolejki');
setTimeout(function() {
channel.nack(msg);
}, 15000);
});
console.info('OK');
});
Plik konfiguracyjny
/etc/rabbitmq/rabbitmq.conf
# od wersji 3.8
load_definitions = /etc/rabbitmq/definitions.json
# starsze wersje
# management.load_definitions = /etc/rabbitmq/definitions.json
/etc/init.d/rabbitmq-server restart
/etc/init.d/rabbitmq-server status
Plik konfiguracyjny
/etc/rabbitmq/definitions.json
{
"users": [
{
"name": "app_2",
"password": "app_2",
"tags": "administrator"
}
]
}
Plik konfiguracyjny
/etc/rabbitmq/definitions.json
{
"vhosts":[
{
"name":"app_2"
}
]
}
Plik konfiguracyjny
/etc/rabbitmq/definitions.json
{
"permissions": [
{
"user": "app_2",
"vhost": "app_2",
"configure": ".*",
"write": ".*",
"read": ".*"
}
]
}
Plik konfiguracyjny
/etc/rabbitmq/definitions.json
{
"exchanges": [
{
"vhost": "app_2",
"name": "reports_ex",
"type": "direct",
"durable": true,
"auto_delete": false,
"arguments": {}
}
]
}
Plik konfiguracyjny
/etc/rabbitmq/definitions.json
{
"queues":[
{
"name":"reports_pdf",
"vhost":"app_2",
"durable":true,
"auto_delete":false,
"arguments":{}
}
]
}
Plik konfiguracyjny
/etc/rabbitmq/definitions.json
{
"bindings": [
{
"arguments":{},
"destination":"reports_pdf",
"destination_type":"queue",
"routing_key":"download_pdf",
"source":"reports_ex",
"vhost":"app_2"
}
]
}
Plik konfiguracyjny
/etc/rabbitmq/definitions.json
{
"users": [
{
"name": "app_2",
"password": "app_2",
"tags": "administrator"
}
],
"vhosts":[
{
"name":"app_2"
}
],
"permissions": [
{
"user": "app_2",
"vhost": "app_2",
"configure": ".*",
"write": ".*",
"read": ".*"
}
],
"parameters": [],
"policies": [],
"queues":[
{
"name":"reports_pdf",
"vhost":"app_2",
"durable":true,
"auto_delete":false,
"arguments":{}
}
],
"exchanges": [
{
"vhost": "app_2",
"name": "reports_ex",
"type": "direct",
"durable": true,
"auto_delete": false,
"arguments": {}
}
],
"bindings": [
{
"arguments":{},
"destination":"reports_pdf",
"destination_type":"queue",
"routing_key":"download_pdf",
"source":"reports_ex",
"vhost":"app_2"
}
]
}
Plik konfiguracyjny
rabbitmqctl import_definitions /etc/rabbitmq/definitions.json
Import pliku - nie wymaga pluginu management
rabbitmqadmin import /etc/rabbitmq/definitions.json
Import pliku - wymaga pluginu management
curl --user guest:guest --data "@/etc/rabbitmq/definitions.json" --header "Content-Type: application/json" --request POST http://127.0.0.1:15672/api/definitions
Import pliku przez API
Plik konfiguracyjny
rabbitmqctl export_definitions /etc/rabbitmq/definitions.file.json
Eksport danych do pliku - nie wymaga pluginu management
rabbitmqadmin export /etc/rabbitmq/definitions_export.json
Eksport danych do pliku - wymaga pluginu management
curl --user guest:guest --header "Content-Type: application/json" --request GET http://127.0.0.1:15672/api/definitions > export_data.json
Eksport do pliku przez API
REST API
REST API
http://localhost:15672/api/vhosts
Lista vhosts
http://localhost:15672/api/exchanges
Lista central
curl -u guest:guest http://localhost:15672/api/vhosts
Lista vhosts - CURL
curl -u guest:guest http://localhost:15672/api/vhosts | json_pp -json_opt pretty,canonical
Z linii poleceń - CLI
rabbitmqctl list_queues name durable arguments
rabbitmqctl list_queues name type durable --formatter=pretty_table
Ładne formatowanie zwracanych informacji
Standardowe formatowanie zwracanych informacji
rabbitmqadmin list queues
Ładne formatowanie zwracanych informacji
rabbitmqctl list_queues name durable arguments --formatter=pretty_table --silent
Ładne formatowanie zwracanych informacji - bez nagłówka
Replikacja w RabbitMQ wspierana jest natywnie w trybie master-slave.
W RabbitMQ znajdziesz dwa typy węzłów:
docker run -d \
--name rabbit_cluster_1 \
--hostname docker1 \
-e RABBITMQ_ERLANG_COOKIE="cookie" \
-e RABBITMQ_NODENAME=rabbit1 \
-p 5673:5672 \
-p 15673:15672 \
-p 25673:25672 \
rabbitmq:3.8-management
Node 1 - master
Tworzymy klaster
Tworzymy klaster
docker run -d \
--name rabbit_cluster_2 \
--hostname docker2 \
-e RABBITMQ_ERLANG_COOKIE="cookie" \
-e RABBITMQ_NODENAME=rabbit2 \
-p 5674:5672 \
-p 15674:15672 \
-p 25674:25672 \
--link rabbit_cluster_1 \
rabbitmq:3.8-management
Node 2 - slave
docker run -d \
--name rabbit_cluster_3 \
--hostname docker3 \
-e RABBITMQ_ERLANG_COOKIE="cookie" \
-e RABBITMQ_NODENAME=rabbit3 \
-p 5675:5672 \
-p 15675:15672 \
-p 25675:25672 \
--link rabbit_cluster_1 \
--link rabbit_cluster_2 \
rabbitmq:3.8-management
Node 3 - slave
Panel administracyjny - brak węzłów
docker exec -it rabbit_cluster_2 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit1@docker1
rabbitmqctl start_app
Konieczne jest usuniecie stanu środowiska uruchomieniowego RabbitMQ w tym węźle, aby pomyślnie dołączyć go do klastra
Zatrzymujemy wewnętrzne procesy
Dołączamy do klastra
Uruchamiamy wewnętrzne procesy
Dodawanie węzłów do klastra
Panel administracyjny - po dodaniu węzła
Konfiguracja
version: '3'
services:
rabbit1:
image: rabbitmq:3.8-management
hostname: docker1
environment:
- RABBITMQ_ERLANG_COOKIE=test
- RABBITMQ_NODENAME=rabbit1
ports:
- "5673:5672"
- "15673:15672"
- "25673:25672"
volumes:
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
rabbit2:
image: rabbitmq:3.8-management
depends_on:
- rabbit1
hostname: docker2
ports:
- "5674:5672"
- "15674:15672"
- "25674:25672"
environment:
- RABBITMQ_ERLANG_COOKIE=test
- RABBITMQ_NODENAME=rabbit2
volumes:
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
rabbit3:
image: rabbitmq:3.8-management
depends_on:
- rabbit1
hostname: docker3
ports:
- "5675:5672"
- "15675:15672"
- "25675:25672"
environment:
- RABBITMQ_ERLANG_COOKIE=test
- RABBITMQ_NODENAME=rabbit3
volumes:
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
docker-compose.yml
Plik konfiguracyjny
loopback_users = none
listeners.tcp.default = 5672
cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
cluster_formation.classic_config.nodes.1 = rabbit1@docker1
cluster_formation.classic_config.nodes.2 = rabbit2@docker2
cluster_formation.classic_config.nodes.3 = rabbit3@docker3
rabbitmq.conf
docker-compose.yml
volumes:
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
Plik konfiguracyjny
global
log 127.0.0.1 local1
maxconn 4096
defaults
log global
mode tcp
option tcplog
retries 3
option redispatch
maxconn 2000
timeout connect 5000
timeout client 50000
timeout server 50000
listen stats
bind *:1936
mode http
stats enable
stats hide-version
stats realm Haproxy\ Statistics
stats uri /
listen rabbitmq
bind *:5672
mode tcp
balance roundrobin
timeout client 3h
timeout server 3h
option clitcpka
server rabbitmq1 rabbitmq1:5672 check inter 5s rise 2 fall 3
server rabbitmq2 rabbitmq2:5672 check inter 5s rise 2 fall 3
server rabbitmq3 rabbitmq3:5672 check inter 5s rise 2 fall 3
listen mgmt
bind *:15672
mode tcp
balance roundrobin
timeout client 3h
timeout server 3h
option clitcpka
server rabbitmq1 rabbitmq1:15672 check inter 5s rise 2 fall 3
server rabbitmq2 rabbitmq2:15672 check inter 5s rise 2 fall 3
server rabbitmq3 rabbitmq3:15672 check inter 5s rise 2 fall 3
haproxy.cfg
Producent
let amqp = require('amqp-connection-manager');
let q = 'tasks_app';
function sleep(ms) {
if(ms <= 0){
return
}
return new Promise(resolve => setTimeout(resolve, ms));
}
let main = async () => {
var connection = amqp.connect([
'amqp://localhost:5672',
'amqp://localhost:5673',
'amqp://localhost:5674',
]);
var channelWrapper = connection.createChannel({
json: true,
setup: function(channel) {
return channel.assertQueue(q, { durable: true });
}
});
console.log('Starting message stream')
while (true) {
await channelWrapper.sendToQueue(q, { value: Math.random() })
await sleep(100)
}
}
main()
cluster_producer.js
Konsument
let amqp = require('amqp-connection-manager');
let q = 'tasks_app';
let main = async () => {
var connection = amqp.connect([
'amqp://localhost:5672',
'amqp://localhost:5673',
'amqp://localhost:5674',
]);
var channelWrapper = connection.createChannel({
json: true,
setup: function(channel) {
return channel.assertQueue(q, { durable: true });
}
});
channelWrapper.addSetup(function(channel) {
return Promise.all([
channel.consume(q, (msg) => {
console.log(msg.content.toString())
}, {noAck: true , exclusive: false })
])
});
}
main()
cluster_consumer.js
Mirrored Queues
Mirrored Queues
Replikacja danych ma kilka trybów ha-mode:
Dodawanie kolejnych replik może odbywa się w dwóch trybach synchronizacji ha-sync-mode:
W przypadku awarii master node, następuje wyłonienie nowego master node z slave node.
Mirrored Queues
Mirrored Queues
Quorum Queues
Quorum Queues
Zalety
Quorum Queues
Ograniczenia
Quorum Queues
Dodajemy kolejkę
Quorum Queues
Po dodaniu kolejki na liście powinna pojawi się nasza kolejka typu Quorum oraz informacja o jej synchronizacji
Quorum Queues
quorum_producer.js
connection.createConfirmChannel(function(err2, channel) {
if (err2){
throw err2;
}
console.info('Channel created');
var queue = 'quorum_q1';
var msg = JSON.stringify({
'text': 'Message 1'
});
console.log(" [x] Sent %s", msg);
channel.sendToQueue(queue, Buffer.from(msg), {}, errorCallback(msg));
channel.waitForConfirms(function(err3) {
if (err3) {
return console.log('Not confirm all message');
}
console.log('All messages confirm');
});
});
Connected
Channel created
[x] Sent {"text":"Message 1"}
{"text":"Message 1"} - confirm
All messages confirm
wynik działania
Quorum Queues
Quorum Queues
Quorum Queues
docker stop mle_rabbit2_1
docker ps
node quorum_producer.js
Connected
Channel created
[x] Sent {"text":"Message 1"}
{"text":"Message 1"} - confirm
All messages confirm
Quorum Queues
docker stop mle_rabbit3_1
node quorum_producer.js
Connected
Channel created
[x] Sent {"text":"Message 1"}
Message: {"text":"Message 1"} - NOT confirm
Not confirm all message