RabbitMQ - сервер черг, написаний на мові Erlang, що реалізує платформу обміну повідомленнями між компонентами програмної системи.
RabbitMQ побудований на фреймоворку Open Telecom Platform та реалізує стандарт обміну повідомленнями AMQP версії 0-9-1 (через плагіни також AMQP версії 1)
Для роботи з RabbitMQ існує багато готових бібліотек для основних мов програмування, що дозволяють без особливих надзусиль почати використовувати сервер у власній інфраструктурі.
Наразі розробкою RabbitMQ займається компанія Pivotal, що в свою чергу є підрозділом Dell. Код сервера відкритий і розповсюджується по ліцензії Mozilla Public License.
Перш за все розглянемо основні логічні одиниці, з яких складається процес роботи брокера повідомлень RabbitMQ (протоколу AMQP):
Отже, програма-генератор повідомлень (Producer) створює повідомлення і у разі, якщо в користувача, від якого вона працює, є права на їх запис у конкретний Virtual Host, повідомлення потрапляє до точки обміну (Exchange). У залежності від її типу та правил, за якою вона працює (Bindings), повідомлення записуються в черги (Queues). Далі вони відправляються до програм-споживачів (Consumers), що підписані на зміни до черг (push API), або ж останні самостійно їх забирають (pull API). У випадку push API споживачі повідомлень підписуються на зміни і, як результат, надсилають запити на реєстрацію (підписку) до черги. Кожен споживач має унікальний ідентифікатор, що зветься consumer tag. Він може використовуватись брокером для відписки останніх від черги.
Мережа - ненадійна річ, тому, для убезпечення від втрати повідомлень, програма-генератор потребує запит-підтвердження від брокера (confirm), що повідомлення було прийнято до черги. У деяких випадках, коли повідомлення не може бути коректно направлене через точку обміну, воно може бути повернене до програми-генератора чи навіть знищене. Деякі брокери повідомлень, що працюють по протоколу AMQP, також мають dead letter чергу для таких цілей.
Програми-споживачі також надсилають брокеру підтвердження у разі успішного отримання чи навіть кінцевої обробки повідомлень (acknowledgement): в залежності від реалізації програми-споживача розробником. Після цього брокер видаляє повідомлення з черги. Інакше, не дочекавшись підтвердження, брокер може повторно надіслати повідомлення.
Споживач також може відхилити повідомлення і повідомити про це брокера, якщо обробка повідомлення неможливе чи воно має невірну структуру. При такій ситуації брокер може видалити "неправильне" повідомлення з черги.
Точки обміну (Exchanges) в RabbitMQ можуть бути наступними:
• Fan-out Exchange. Повідомлення буде скопійоване до всіх черг, що пов'язані з такою точкою обміну. Такий собі броадкастинг повідомлень.
Ключі маршрутизації повідомлень (routing key) в цьому випадку не використовуються.
• Direct Exchange. Повідомлення відправляється лише у відповідну чергу за умови, якщо binding-ключ такої черги буде рівний ключу маршрутизації повідомлення (routing key).
• Topic Exchange. При такому типу маршрутизації, binding-ключ черги може задаватись як регулярний вираз. Отже, якщо routing ключ повідомлення покривається регулярним виразом binding-ключа - повідомлення потрапляє у відповідну чергу
RabbitMQ побудований на фреймоворку Open Telecom Platform та реалізує стандарт обміну повідомленнями AMQP версії 0-9-1 (через плагіни також AMQP версії 1)
Для роботи з RabbitMQ існує багато готових бібліотек для основних мов програмування, що дозволяють без особливих надзусиль почати використовувати сервер у власній інфраструктурі.
Наразі розробкою RabbitMQ займається компанія Pivotal, що в свою чергу є підрозділом Dell. Код сервера відкритий і розповсюджується по ліцензії Mozilla Public License.
Перш за все розглянемо основні логічні одиниці, з яких складається процес роботи брокера повідомлень RabbitMQ (протоколу AMQP):
Отже, програма-генератор повідомлень (Producer) створює повідомлення і у разі, якщо в користувача, від якого вона працює, є права на їх запис у конкретний Virtual Host, повідомлення потрапляє до точки обміну (Exchange). У залежності від її типу та правил, за якою вона працює (Bindings), повідомлення записуються в черги (Queues). Далі вони відправляються до програм-споживачів (Consumers), що підписані на зміни до черг (push API), або ж останні самостійно їх забирають (pull API). У випадку push API споживачі повідомлень підписуються на зміни і, як результат, надсилають запити на реєстрацію (підписку) до черги. Кожен споживач має унікальний ідентифікатор, що зветься consumer tag. Він може використовуватись брокером для відписки останніх від черги.
Мережа - ненадійна річ, тому, для убезпечення від втрати повідомлень, програма-генератор потребує запит-підтвердження від брокера (confirm), що повідомлення було прийнято до черги. У деяких випадках, коли повідомлення не може бути коректно направлене через точку обміну, воно може бути повернене до програми-генератора чи навіть знищене. Деякі брокери повідомлень, що працюють по протоколу AMQP, також мають dead letter чергу для таких цілей.
Програми-споживачі також надсилають брокеру підтвердження у разі успішного отримання чи навіть кінцевої обробки повідомлень (acknowledgement): в залежності від реалізації програми-споживача розробником. Після цього брокер видаляє повідомлення з черги. Інакше, не дочекавшись підтвердження, брокер може повторно надіслати повідомлення.
Споживач також може відхилити повідомлення і повідомити про це брокера, якщо обробка повідомлення неможливе чи воно має невірну структуру. При такій ситуації брокер може видалити "неправильне" повідомлення з черги.
Точки обміну (Exchanges) в RabbitMQ можуть бути наступними:
• Fan-out Exchange. Повідомлення буде скопійоване до всіх черг, що пов'язані з такою точкою обміну. Такий собі броадкастинг повідомлень.
Ключі маршрутизації повідомлень (routing key) в цьому випадку не використовуються.
• Direct Exchange. Повідомлення відправляється лише у відповідну чергу за умови, якщо binding-ключ такої черги буде рівний ключу маршрутизації повідомлення (routing key).
• Topic Exchange. При такому типу маршрутизації, binding-ключ черги може задаватись як регулярний вираз. Отже, якщо routing ключ повідомлення покривається регулярним виразом binding-ключа - повідомлення потрапляє у відповідну чергу
У цьому випадку, повідомлення з ключем маршрутизації usa.news та usa.weather потраплять в одну чергу, завдяки binding-ключу usa.# (# описує нуль або більше слів). У свою чергу повідомлення з ключем маршрутизації usa.weather та europe.weather потраплять вже в іншу чергу, завдяки binding-ключу #.weather і т.п. Тобто кожне повідомлення, що приведене на малюнку вище, потрапить в різні 2 черги згідно країни чи тематики. Є звісно і більш комплексні регулярні вирази. Наприклад, binding-ключ маршрутизатора *.stock.# переадресує повідомлення в чергу у разі, якщо ключ маршрутизації повідомлення буде usd.stock чи euro.stock.db, але, в той же час, повідомлення на зразок stock.nasdaq будуть відкинуті точкою обміну.
• Headers Exchange. Для маршрутизації аналізуються заголовки повідомлень, а ключі маршрутизації не використовуються. Правил для аналізу маршрутизації може бути декілька, параметр "x-match" може управляти логічними OR та AND для цих правил. Якщо параметр "x-match" приймає значення "all" - для успішного роутингу повідомлення необхідно, щоб повідомлення, що надходить до такої точки обміну, задовольняло всім описаним правилам, а "any" - хоча б одному з них. Значення цього параметру виставляє розробник при створення черг. Цей тип маршрутизації можна розглядати як "direct exchange на стероїдах".
По-замовчуванню, кожна черга створюється з дефолтними правилами маршрутизації за якими кожне повідомлення потрапляє до черги, якщо ключ маршрутизації повідомлення збігається з назвою черги (direct exchange). Таким чином повідомлення ніби-то напряму будуть записуватись до черги повідомлень.
Окрім того, точки обміну та черги (Queues) можуть мати такі характеристики (тобто параметри, котрі можуть бути призначені під час їх створення):
• Ім'я черги/точки обміну (розміром до 255 байтів в кодуванні UTF-8). Може генеруватись на основі запиту чи самостійно брокером.
• Durable. Опція збереження на диску. Інакше черга/обміну буде видалена при перевантаженні сервісу.
• Exclusive. Можливість використання черги лише через єдине з'єднанням (connection). Якщо це з'єднання закриється - то і черга відповідно буде видалена. Точки обміну не мають такої можливості.
• Auto-delete. Опція, що призводить до видалення черги у разі повної відписки від неї усіх програм-споживачів. Якщо ж черга перестане використовувати точку обміну, створену з цією ж опцією, точка обміну також буде видалена.
• Arguments. Додаткові аргументи, використовуючи які реалізуються такі штуки як TTL для черги (час життя). Часом до цих метаданих може бути прив'язана деяка логіка на стороні програми-споживача цих повідомлень і для самого брокера вони можуть не мати ніякого сенсу і т.п.
Повідомлення ж, що ставиться в чергу, має такі основні поля:
• Content type. Тип контенту
• Content encoding. Тип кодування контенту . Наприклад, указання, що в повідомленні JSON-документ.
• Routing key. Ключ маршрутизації
• Delivery mode. Режим доставки
• Message priority. Пріоритет доставки повідомлення (реалізовано не у всіх брокерах)
• Message publishing timestamp. Час публікації повідомлення
• Expiration period. Час актуальності повідомлення
• Publisher application id. Id програми, що опублікувала повідомлення
• Payload. Поле даних, що власне і несе корисне навантаження. Це може бути JSON, Thrift, Protocol Buffers і т.п.
Але полів може бути і більше, якщо їх оголосить розробник.
Клієнт (програма-producer), що працює по протоколу AMQP 0-9-1, самостійно робить запити на створення черг, точок обміну та правил маршрутизації, які він потребує. Це не обов'язково має оголошувати саме адміністратор RabbitMQ (брокера), достатньо лише необхідних прав для доступу до конкретного virtual host-у.
Приступимо до самого налаштування RabbitMQ. У процесі я розповім про те як налаштувати кластер RabbitMQ, та часом я буду перериватись на теорію (надіюсь, що я поки не перебрав із нею) як він працює і які в даній схемі особливості.
Отже, для налаштування кластеру RabbitMQ я обрав наступні адреси:
rabbit1 192.168.1.71
rabbit2 192.168.1.72
rabbit3 192.168.1.73
Котрі необхідно описати в /etc/hosts, адже RabbitMQ (Erlang/Mnesia) дуже чутливий до імен. Кластер ймовірніше за все просто не збереться за відсутності коректного резолву вузлів по доменним іменам.
На кожний із перерахованих вузлів установлюємо RabbitMQ сервер:
# echo 'deb http://www.rabbitmq.com/debian/ testing main' | tee /etc/apt/sources.list.d/rabbitmq.list
# wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | apt-key add -
# apt-get update
# apt-get install rabbitmq-server
У стандартних репозиторіях версія RabbitMQ може бути дещо застарою.
Сервер після установки стартує автоматично:
# ps aux | grep rabbit
rabbitmq 4160 0.0 0.6 2198008 52396 ? Ssl 18:45 0:02 /usr/lib/erlang/erts-7.3/bin/beam.smp -W w -A 64 -P 1048576 -t 5000000 -stbt db -K true -- -root /usr/lib/erlang -progname erl -- -home /var/lib/rabbitmq -- -pa /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin -noshell -noinput -s rabbit boot -sname rabbit@rabbit1 -boot start_sasl -kernel inet_default_connect_options [{nodelay,true}] -sasl errlog_type error -sasl sasl_error_logger false -rabbit error_logger {file,"/var/log/rabbitmq/rabbit@rabbit1.log"} -rabbit sasl_error_logger {file,"/var/log/rabbitmq/rabbit@rabbit1-sasl.log"} -rabbit enabled_plugins_file "/etc/rabbitmq/enabled_plugins" -rabbit plugins_dir "/usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/plugins" -rabbit plugins_expand_dir "/var/lib/rabbitmq/mnesia/rabbit@rabbit1-plugins-expand" -os_mon start_cpu_sup false -os_mon start_disksup false -os_mon start_memsup false -mnesia dir "/var/lib/rabbitmq/mnesia/rabbit@rabbit1" -kernel inet_dist_listen_min 25672 -kernel inet_dist_listen_max 25672
rabbitmq 4233 0.0 0.0 26304 228 ? S 18:45 0:00 /usr/lib/erlang/erts-7.3/bin/epmd -daemon
rabbitmq 4366 0.0 0.0 7504 1044 ? Ss 18:45 0:00 inet_gethost 4
rabbitmq 4367 0.0 0.0 9624 1568 ? S 18:45 0:00 inet_gethost 4
root 4402 0.0 0.0 14224 932 ? S+ 18:46 0:00 grep --color=auto rabbit
RabbitMQ працює у віртуальній машині Erlang. Тому і у виводі від імені користувача rabbitmq, окрім самої програми сервера, запущений також epmd.
На момент написання цієї статті, остання актуальна версія RabbitMQ - 3.6.5.
Одразу активуємо rabbitmq_management плагін, виключно для зручності:
# rabbitmq-plugins enable rabbitmq_management
The following plugins have been enabled:
mochiweb
webmachine
rabbitmq_web_dispatch
amqp_client
rabbitmq_management_agent
rabbitmq_management
Applying plugin configuration to rabbit@rabbit1... started 6 plugins.
По залежностях окрім rabbitmq_management було встановлено також додаткові плагіни, що будуть відображатись позначкою [e] при виводі списку плагінів:
# rabbitmq-plugins list
Configured: E = explicitly enabled; e = implicitly enabled
| Status: * = running on rabbit@rabbit1
|/
[e*] amqp_client 3.6.5
[ ] cowboy 1.0.3
[ ] cowlib 1.0.1
[e*] mochiweb 2.13.1
[ ] rabbitmq_amqp1_0 3.6.5
...
[E*] rabbitmq_management 3.6.5
[e*] rabbitmq_management_agent 3.6.5
[ ] rabbitmq_management_visualiser 3.6.5
...
[e*] rabbitmq_web_dispatch 3.6.5
[ ] rabbitmq_web_stomp 3.6.5
[ ] rabbitmq_web_stomp_examples 3.6.5
[ ] sockjs 0.3.4
[e*] webmachine 1.10.3
Адміністративна панель RabbitMQ дуже потужна: мабуть, вона покриває 80% усіх можливих операцій з налаштування та подальшого управління.
За необхідності, вищезгаданий плагін можна деактивувати наступним чином:
# rabbitmq-plugins disable rabbitmq_management
Окрім цього для RabbitMQ є купа неофіційних плагінів, проте, як і з будь-якими неофіційними речами, гарантувати його роботу виробник не може.
Як я вже згадував, RabbitMQ, на відміну від Apache Kafka, має можливість на рівні сервера обмежувати доступи до черг для різних користувачів. Юзер, що підключився до конкретного 'virtual host' може мати окремі права на конфігурацію ресурсів, читання та запис.
Надаються такі права за допомогою утиліти rabbitmqctl і ця команда має наступний синтаксис:
# rabbitmqctl set_permissions [-p vhost] {user} {conf} {write} {read}
Наприклад, так виглядатиме команда для надання користувачу "tonyg" прав на конфігурацію всіх ресурсів, що починаються на ім'я "tonyg-", на читання та запис до всіх ресурсів в межах віртуального хосту "/myvhost":
# rabbitmqctl set_permissions -p /myvhost tonyg "^tonyg-.*" ".*" ".*"
Створимо admin-користувача для доступу до панелі адміністрування. Спочатку задаємо його ім'я та пароль:
# rabbitmqctl add_user newadmin s0m3p4ssw0rd
Creating user "newadmin" ...
Пароль звісно необхідно обрати свій. Надамо користувачу права адміністратора:
# rabbitmqctl set_user_tags newadmin administrator
Setting tags for user "newadmin" to [administrator] ...
Адміністратор зможе управляти іншими, вже доступними, користувачами, створювати нові віртуальні хости (про це трохи далі) та керувати доступами до них.
Тепер надамо новоствореному користувачу administrator повний доступ до всіх віртуальних хостів "/":
# rabbitmqctl set_permissions -p / newadmin ".*" ".*" ".*"
Setting permissions for user "newadmin" in vhost "/" ...
Наразі ми можемо поглянути на адміністративну вуб-панель RabbitMQ, що розміщена на порту 15672:
Веб-панель відтримується офіційно і має дуже обширний функціонал. Здається, що вона вміє мало не все те ж, що можна зробити в консолі, плюс відображає статистику підключень, використання ресурсів і т.п.
По замовчуванню, інсталяція RabbitMQ вже має локального користувача 'guest', котрий активний лише для локальних підключень. Його, задля підвищення рівня безпеки, краще видалити:
# rabbitmqctl delete_user guest
Deleting user "guest" ...
Віртуальна машина Erlang використовує значення erlang.cookie для визначення чи можуть декілька серверів в мережі комунікувати один з одним. Тож erlang.cookie має бути однаковим на всіх вузлах, що будуть входити в єдиний кластер:
# cat /var/lib/rabbitmq/.erlang.cookie
EQZOZBUQKOAPXDCAQAEA
# ls -la /var/lib/rabbitmq/.erlang.cookie
-r-------- 1 rabbitmq rabbitmq 20 Nov 9 00:00 /var/lib/rabbitmq/.erlang.cookie
Значення .erlang.cookie можна обрати на власний розсуд, а права доступу до цього файлу, в цілях безпеки, варто лишити мінімальними.
Збираємо кластер з наявних вузлів. Зупиняємо на rabbit2 додаток RabbitMQ:
# rabbitmqctl stop_app
Stopping node rabbit@rabbit2 ...
Про всяк випадок, видаляємо всі ресурси (черги, точки обміну і т.п.), котрі можуть бути доступні на ньому:
# rabbitmqctl reset
Resetting node rabbit@rabbit2 ...
Підключаємось до першої ноди у якості вузла кластера:
# rabbitmqctl join_cluster rabbit@rabbit1
Clustering node rabbit@rabbit2 with rabbit@rabbit1 ...
Перевріяємо статус кластера:
# rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit2 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
{alarms,[{rabbit@rabbit1,[]}]}]
До цього часу cluster_status показував, що кожний вузол в своєму кластері, до якого лише він і підключений.
Запустимо додаток RabbitMQ:
# rabbitmqctl start_app
Starting node rabbit@rabbit2 ...
Кластер, котрий складається поки лише з двох вузлів, вже працює. Варто звернути увагу на поле running_nodes, котрого в попередньому виводі не було:
# rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit2 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
{running_nodes,[rabbit@rabbit1,rabbit@rabbit2]},
{cluster_name,<<"rabbit@rabbit1">>},
{partitions,[]},
{alarms,[{rabbit@rabbit1,[]},{rabbit@rabbit2,[]}]}]
Два вузли, які наразі працюють - це disc-ноди і всі метадані (біндинги, точки обміну) вони зберігають на диск і, таким чином, одночасне перевантаження обох вузлів не критичне для роботи кластеру. Опціонально можна створити ram-вузол, котрий згадані вище метадані буде тримати в оперативній пам'яті. Це ми і зробимо на третьому сервері групи:
# rabbitmqctl stop_app
Stopping node rabbit@rabbit3 ...
# rabbitmqctl reset
Resetting node rabbit@rabbit3 ...
Тут варто звернути увагу на додатковий ключ "--ram":
# rabbitmqctl join_cluster --ram rabbit@rabbit2
Clustering node rabbit@rabbit3 with rabbit@rabbit2 ...
# rabbitmqctl start_app
Starting node rabbit@rabbit3 ...
Ram-вузол працює дещо швидше за disc-ноди, адже йому немає необхідності постійно синхронізувати метадані на диск. У разі падіння такого вузла, дані про точки обміну і біндинги будуть втрачені, але вони будуть скопійовані від інших вузлів кластеру.
Наразі статус кластра виглядає так:
# rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit3 ...
[{nodes,[{disc,[rabbit@rabbit2,rabbit@rabbit1]},{ram,[rabbit@rabbit3]}]},
{running_nodes,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]},
{cluster_name,<<"rabbit@rabbit1">>},
{partitions,[]},
{alarms,[{rabbit@rabbit1,[]},{rabbit@rabbit2,[]},{rabbit@rabbit3,[]}]}]
Це ж саме, звісно, відображається і у веб-панелі:
Отже, всі вузли наразі в єдиній групі. Перевіримо роботу кластеру. Для цього створимо користувача, віртуальний хост для нього та надамо всі необхідні права:
# rabbitmqctl add_user test_user s0m3p4ssw0rd
# rabbitmqctl add_vhost /test_queue
# rabbitmqctl set_permissions -p /test_queue test_user ".*" ".*" ".*"
Для створення черги ми скористаємось python-бібліотекою Pika, що попередньо має бути установлена, наприклад, через pip:
# aptitude -y install python-pip
# pip install pika
Код на створення черги (точки обміну) і запис повідомлення до неї виглядатиме наступним чином:
А код на отримання повідомлень з черги 'test_queue' буде мати такий вигляд:
Отже з лістингу коду можна помітити, що повідомлення в чергу буде ставитись з серверу rabbit1, а читатись вже з rabbit3. Черга буде створена не довговічна (transient), та підтвердження програмі-споживачу надсилати не треба (no_ack=True). Запускаємо створення черги та повідомлень:
# for i in 1 2; do python send.py; done
[x] Sent 'Hello World!'
[x] Sent 'Hello World!'
На цьому етапі засобами RabbitMQ можна переглянути які черги було створено і яка кількість повідомлень в цій черзі:
# rabbitmqctl -p /test_queue list_queues
Listing queues ...
test_queue 2
Зчитаємо ці повідомлення:
# for i in 1 2; do python receive.py; done
[*] Waiting for messages. To exit press CTRL+C
[x] Received 'Hello World!'
[x] Received 'Hello World!'
Чудово. Приклади створення інших черг, точок обміну та біндинг-правил до них можна побачити тут.
По-замовчуванню, в кластерній конфігурації RabbitMQ кожна нова черга створюється лише на одному вузлі (окрім точок обміну та біндингів, вони створюються по-замовчуванню одразу на всіх вузлах кластеру). Це звісно не додає надійності, адже при виході з ладу сервера, котрий фізично зберігає цю чергу, остання буде безповоротно втрачена. Щоб уникнути цього, варто задати відповідну політику збереження реплік для кожної черги (mirroring policy). Є декілька опцій для керування цими правилами:
• '{"ha-mode":"all"}'. Черга буде реплікована на всі ноди кластеру. Якщо буде доданий новий вузол - дані будуть скопійовані і на нього.
• '{"ha-mode":"exactly","ha-params":count}'. Черга та її копії будуть зберігатись на 'count' серверах. Якщо серверів менше ніж 'count' - черга буде зберігатись на всіх вузлах кластеру і у разі появи додаткового сервера - буде скопійована на нього також.
• '{"ha-mode":"nodes", "ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'. Копії черги будуть розміщені на серверах кластеру, що перераховані.
Активуємо нову політику розміщення вже створеної вище черги:
# rabbitmqctl -p /test_queue set_policy ha-all "^test" '{"ha-mode":"all"}'
Setting policy "ha-all" for pattern "^test" to "{\"ha-mode\":\"all\"}" with priority "0" ...
Ця політика по-замовчуванню активуються одразу як для черги (queue), так і для точки обміну (exchange) 'test_queue'. Проте ключем "--apply-to" її можна активувати лише для черги (--apply-to queues) або точки обміну (--apply-to exchanges).
Перевіряємо чи встановлена політика активна і для яких саме черг/точок обміну:
# rabbitmqctl -p /test_queue list_policies
Listing policies ...
/test_queue ha-all all ^test {"ha-mode":"all"} 0
Тепер черга буде існувати до наявності хоча б одного сервера в кластері. Створимо ще одну чергу під іменем 'quest_queue' (наприклад, наведеним вище send.py скриптом) після чого опишемо для неї наступну політику реплікації:
# rabbitmqctl -p /test_queue set_policy ha-exactly "^quest" '{"ha-mode":"exactly","ha-params":2}' --priority 1 --apply-to queues
Setting policy "ha-exactly" for pattern "^quest" to "{\"ha-mode\":\"exactly\",\"ha-params\":2}" with priority "1" ...
Вона аналогічно з'явиться у переліку всіх наявних політик віртуалхосту '/test_queue':
# rabbitmqctl -p /test_queue list_policies
Listing policies ...
/test_queue ha-all all ^test {"ha-mode":"all"} 0
/test_queue ha-exactly queues ^quest {"ha-mode":"exactly","ha-params":2} 1
Ця черга буде розміщена лише на 2-х з 3-х серверів кластеру. Якщо ж один із вузлів, що хостить цю чергу, впаде - черга скопіюється на інший живий вузол для досягнення необхідної кількості копій, що зазначена у призначеній політиці. Коли ж ці сервера впадуть разом одночасно - черга знову ж таки буде безповоротно втрачена.
RabbitMQ підтримує як запис так і читання черги лише з одного вузла кластеру, всі інші лише підвищують рівень доступності системи. Тобто дані спочатку читаються/записуються і лише потім реплікуються на інші черги-копії на інших вузлах. Звісно у випадку декількох черг, кожен сервер може бути місцем запису/читання (майстром) для кожної із них, тобто певний рівень масштабування все таки є.
Останні версії RabbitMQ можуть проводити реплікацію в batch-режимі, тобто групами. До версії 3.6 кожна зміна реплікувалась на слейви окремо.
Тепер постає наступне питання: як RabbitMQ обирає, хто буде майстром для кожних черги і чи можна цим якось керувати? Так, можна, за допомогою наступних параметрів, котрі слід додати до конфігураційного файлу /etc/rabbitmq/rabbitmq.conf:
• min-masters. Буде обраний вузол з найменшою кількістю майстер-черг.
• client-local. Для створення майстер-черги буде обраний вузол до якого прийшов запит від клієнта
• random. Буде обраний просто випадковий вузол кластеру.
У разі ж якщо майстер-вузол для будь-якої реплікованої черги впаде, буде обраний інший майстер серед доступних черг-копій. Підняття попереднього майстра, звісно-що, не призводить до зворотньої міграції.
RabbitMQ намагається виявити несправності в роботі якомога швидше, і тому кожний вузол кластеру надсилає heartbeat перевірки до своїх сусідів. У випадку якщо на протязі 'net_ticktime' вузол не відповість - він буде вважатись непрацюючим. . Про те, як RabbitMQ поводиться під час мережевих проблем в кластері (відпадання груп серверів і т.п.) я згадаю далі.
У разі перемикання майстра для черги, деякі повідомлення, статус доставки яких не встиг реплікувався на інший сервер, що став новим майстром, можуть бути надіслані повторно програмі-споживачу з тегом 'redelivered'. Клієнт, помітивши цей тег, може перевірити чи брав він на обробку ці дані. Звісно, що 'redelivered' тег не гарантія, що після перемикання майстра для черги, деякі повідомлення не будуть втрачені, але це значно мінімізує такі втрати.
Окрім цього можна також впливати на логіку поводження кластеру у випадку мережевих проблем, що призводить до розділення кластеру. За це відповідає параметр 'cluster_partition_handling', котрий необхідно оголосити в конфігураційному файлі RabbitMQ. Цей параметр може приймати наступні значення:
• pause-minority. У такому випадку RabbitMQ зупинить групу нод, що перебувають в меншості і сервера якої "бачать" один одного, якщо в той же час інша група, що перебуває в більшості, не відповідає на запити меншості. Ось так от, трохи заплутано. Таким чином RabbitMQ робить пріорітет виконання умов стійкості до розділення (Partition tolerance) над доступністю (Availability) теореми CAP. Тільки-но проблемні ноди "оживуть" - група, що перебуває в меншості, знову запрацює. У такому випадку робиться припущення, що група, більша за кількістю вузлів в цей час працює і обробляє запити.
• pause-if-all-down. Кластер RabbitMQ зупинить вузли, що не "бачать" хоча б одну ноду із лістингу до цього параметру. Тобто, скажімо, у випадку, коли дві ноди кластеру знаходяться в одній стійці, а дві інших - в іншій і дві останні перераховані в цьому параметрі, то група перших буде зупинена, якщо останні вузли будуть для них недоступні. Ймовірна також ситуація, якщо для одного сервера групи, що не описаний в параметрі, буде видимий лише перший сервер описаної групи, а для іншого неописаного сервера - навпаки, другий. Для таких ситуацій є можливість додавати аргументи, інші параметри: ignore чи autoheal. Це буде виглядати наступним чином:
pause_if_all_down, [nodeA, nodeB, nodeN], ignore | autoheal}
• autoheal. У цьому режимі роботи RabbitMQ перевантажить групу, що має менше підключених клієнтів і не бачить іншу группу. Якщо підключених клієнтів порівну - перевантажить меншу, за кількістю вузлів в ній, групу. Якщо ж і групи однакові по кількості вузлів - перевантажить випадкову групу на власний розсуд.
Звісно, що ніхто не відміняв того правила, що кількість вузлів в кластері має бути не парною задля зменшення наслідків від можливих Split Brain. Щоб хоч якось убезпечитись від них, у кластерах з парною кількістю вузлів, RabbitMQ зупинить всі групи, що не у строгій більшості (strict majority). Тобто у випадках, коли по обидва боки розділення знаходяться однакова кількість вузлів (наприклад, 3 з однієї сторони та 3 з іншої).
На цій останній ноті все ж таки хотілось би порівняти між собою брокери RabbitMQ та Apache Kafka:
• У Apache Kafka за позицію в черзі (offset) відповідає програма-споживач. Черга в Apache Kafka не зменшується в процесі її читання (проте черга може чиститись в процесі роботи, в залежності від внутрішніх політик), на відміну від RabbitMQ. У цьому є як і позитивні так і негативні моменти. До позитивних, наприклад, можна віднести те, що в Kafka декілька споживачів можуть обробляти ті ж повідомлення в одній черзі, до того ж їм не обов’язково бути онлайн постійно, адже черга нікуди не дінеться. Звісно, що в такому випадку відсутні відповіді-підтвердження читання повідомлень зі сторони споживачів, що з однієї сторони збільшує швидкість читання повідомлень, а з іншої - ускладнює моніторинг обробки черги і вся коректність її обробки лягає на логіку роботи клієнта-споживача. Масштабувати кількість споживачів в Kafka можна лише до кількості партицій, на які поділена черга (topic) на диску, і такі користувачі мають бути об’єднані в групу (consumer group). У RabbitMQ таких обмежень немає, адже кожна прочитане повідомлення в черзі видаляється фізично з сервера.
• Kafka вміє ділити черги на партиції (partitioning) і з їхнім знаходженням допомагає Zookeeper. Для RabbitMQ розмір диску системи являється лімітом розміру черги, partitioning відсутній. Інші сервери кластеру RabbitMQ в межах однієї черги виконують лише задачі зберігання і підтримки актуальності реплік.
• Згідно термінології RabbitMQ, Apache Kafka підтримує лише direct exchange черги: дані просто записуються в чергу і потім вже з неї читаються. Цього може бути не досить для реалізації програмної логіки.
• RabbitMQ працює по протоколу AMQP 0-9-1, що в свою чергу дозволяє працювати з бібліотеками, що підтримують саме протокол, а не сервіс. Apache Kafka використовує свої алгоритми передачі даних.
• Kafka значно швидше працює (але підтримує лише direct exchange чергу в термінології RabbitMQ). У ~100 раз швидше за RabbitMQ приймає повідомлення в чергу і в ~5 разів швидше читає з черги на виході. Я про це писав у своїй попредній статті про Kafka.
• Спосіб дистрибуції Apache Kafka бажає кращого. Це звичайні архіви з бінарним вмістом всередині. Init-скрипт, директорію для логів, користувача від якого працюватиме процес і т.п. необхідно створювати самому власноруч. І це без того, що рано чи пізно варто буде задумуватись як це все оновлювати до останніх версій.
• На відміну від RabbitMQ, Apache Kafka не має можливості обмежувати доступ до черг через певні права чи корустувачів.
Посилання:
https://habr.com/company/itsumma/blog/416629/
https://www.cloudamqp.com/blog/2015-05-18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html
https://www.cloudamqp.com/blog/2015-05-21-part2-3-rabbitmq-for-beginners_example-and-sample-code-python.html
https://www.cloudamqp.com/blog/2015-05-27-part3-rabbitmq-for-beginners_the-management-interface.html
https://www.cloudamqp.com/blog/2015-09-03-part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html
http://www.rabbitmq.com/clustering.html
https://www.rabbitmq.com/tutorials/tutorial-one-python.html
http://www.rabbitmq.com/man/rabbitmqctl.1.man.html
https://www.rabbitmq.com/tutorials/amqp-concepts.html
https://code.tutsplus.com/tutorials/managing-your-rabbitmq-cluster--cms-25597
http://jessesnet.com/development-notes/2015/rabbitmq-cluster/
http://www.slideshare.net/carrja99/high-powered-messaging-with/
http://www.slideshare.net/rahula24/amqp-basic
http://www.slideshare.net/somic/introduction-to-amqp-messaging-with-rabbitmq
https://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol
https://www.quora.com/What-are-the-differences-between-Apache-Kafka-and-RabbitMQ
• Headers Exchange. Для маршрутизації аналізуються заголовки повідомлень, а ключі маршрутизації не використовуються. Правил для аналізу маршрутизації може бути декілька, параметр "x-match" може управляти логічними OR та AND для цих правил. Якщо параметр "x-match" приймає значення "all" - для успішного роутингу повідомлення необхідно, щоб повідомлення, що надходить до такої точки обміну, задовольняло всім описаним правилам, а "any" - хоча б одному з них. Значення цього параметру виставляє розробник при створення черг. Цей тип маршрутизації можна розглядати як "direct exchange на стероїдах".
По-замовчуванню, кожна черга створюється з дефолтними правилами маршрутизації за якими кожне повідомлення потрапляє до черги, якщо ключ маршрутизації повідомлення збігається з назвою черги (direct exchange). Таким чином повідомлення ніби-то напряму будуть записуватись до черги повідомлень.
Окрім того, точки обміну та черги (Queues) можуть мати такі характеристики (тобто параметри, котрі можуть бути призначені під час їх створення):
• Ім'я черги/точки обміну (розміром до 255 байтів в кодуванні UTF-8). Може генеруватись на основі запиту чи самостійно брокером.
• Durable. Опція збереження на диску. Інакше черга/обміну буде видалена при перевантаженні сервісу.
• Exclusive. Можливість використання черги лише через єдине з'єднанням (connection). Якщо це з'єднання закриється - то і черга відповідно буде видалена. Точки обміну не мають такої можливості.
• Auto-delete. Опція, що призводить до видалення черги у разі повної відписки від неї усіх програм-споживачів. Якщо ж черга перестане використовувати точку обміну, створену з цією ж опцією, точка обміну також буде видалена.
• Arguments. Додаткові аргументи, використовуючи які реалізуються такі штуки як TTL для черги (час життя). Часом до цих метаданих може бути прив'язана деяка логіка на стороні програми-споживача цих повідомлень і для самого брокера вони можуть не мати ніякого сенсу і т.п.
Повідомлення ж, що ставиться в чергу, має такі основні поля:
• Content type. Тип контенту
• Content encoding. Тип кодування контенту . Наприклад, указання, що в повідомленні JSON-документ.
• Routing key. Ключ маршрутизації
• Delivery mode. Режим доставки
• Message priority. Пріоритет доставки повідомлення (реалізовано не у всіх брокерах)
• Message publishing timestamp. Час публікації повідомлення
• Expiration period. Час актуальності повідомлення
• Publisher application id. Id програми, що опублікувала повідомлення
• Payload. Поле даних, що власне і несе корисне навантаження. Це може бути JSON, Thrift, Protocol Buffers і т.п.
Але полів може бути і більше, якщо їх оголосить розробник.
Клієнт (програма-producer), що працює по протоколу AMQP 0-9-1, самостійно робить запити на створення черг, точок обміну та правил маршрутизації, які він потребує. Це не обов'язково має оголошувати саме адміністратор RabbitMQ (брокера), достатньо лише необхідних прав для доступу до конкретного virtual host-у.
Приступимо до самого налаштування RabbitMQ. У процесі я розповім про те як налаштувати кластер RabbitMQ, та часом я буду перериватись на теорію (надіюсь, що я поки не перебрав із нею) як він працює і які в даній схемі особливості.
Отже, для налаштування кластеру RabbitMQ я обрав наступні адреси:
rabbit1 192.168.1.71
rabbit2 192.168.1.72
rabbit3 192.168.1.73
Котрі необхідно описати в /etc/hosts, адже RabbitMQ (Erlang/Mnesia) дуже чутливий до імен. Кластер ймовірніше за все просто не збереться за відсутності коректного резолву вузлів по доменним іменам.
На кожний із перерахованих вузлів установлюємо RabbitMQ сервер:
# echo 'deb http://www.rabbitmq.com/debian/ testing main' | tee /etc/apt/sources.list.d/rabbitmq.list
# wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | apt-key add -
# apt-get update
# apt-get install rabbitmq-server
У стандартних репозиторіях версія RabbitMQ може бути дещо застарою.
Сервер після установки стартує автоматично:
# ps aux | grep rabbit
rabbitmq 4160 0.0 0.6 2198008 52396 ? Ssl 18:45 0:02 /usr/lib/erlang/erts-7.3/bin/beam.smp -W w -A 64 -P 1048576 -t 5000000 -stbt db -K true -- -root /usr/lib/erlang -progname erl -- -home /var/lib/rabbitmq -- -pa /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin -noshell -noinput -s rabbit boot -sname rabbit@rabbit1 -boot start_sasl -kernel inet_default_connect_options [{nodelay,true}] -sasl errlog_type error -sasl sasl_error_logger false -rabbit error_logger {file,"/var/log/rabbitmq/rabbit@rabbit1.log"} -rabbit sasl_error_logger {file,"/var/log/rabbitmq/rabbit@rabbit1-sasl.log"} -rabbit enabled_plugins_file "/etc/rabbitmq/enabled_plugins" -rabbit plugins_dir "/usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/plugins" -rabbit plugins_expand_dir "/var/lib/rabbitmq/mnesia/rabbit@rabbit1-plugins-expand" -os_mon start_cpu_sup false -os_mon start_disksup false -os_mon start_memsup false -mnesia dir "/var/lib/rabbitmq/mnesia/rabbit@rabbit1" -kernel inet_dist_listen_min 25672 -kernel inet_dist_listen_max 25672
rabbitmq 4233 0.0 0.0 26304 228 ? S 18:45 0:00 /usr/lib/erlang/erts-7.3/bin/epmd -daemon
rabbitmq 4366 0.0 0.0 7504 1044 ? Ss 18:45 0:00 inet_gethost 4
rabbitmq 4367 0.0 0.0 9624 1568 ? S 18:45 0:00 inet_gethost 4
root 4402 0.0 0.0 14224 932 ? S+ 18:46 0:00 grep --color=auto rabbit
RabbitMQ працює у віртуальній машині Erlang. Тому і у виводі від імені користувача rabbitmq, окрім самої програми сервера, запущений також epmd.
На момент написання цієї статті, остання актуальна версія RabbitMQ - 3.6.5.
Одразу активуємо rabbitmq_management плагін, виключно для зручності:
# rabbitmq-plugins enable rabbitmq_management
The following plugins have been enabled:
mochiweb
webmachine
rabbitmq_web_dispatch
amqp_client
rabbitmq_management_agent
rabbitmq_management
Applying plugin configuration to rabbit@rabbit1... started 6 plugins.
По залежностях окрім rabbitmq_management було встановлено також додаткові плагіни, що будуть відображатись позначкою [e] при виводі списку плагінів:
# rabbitmq-plugins list
Configured: E = explicitly enabled; e = implicitly enabled
| Status: * = running on rabbit@rabbit1
|/
[e*] amqp_client 3.6.5
[ ] cowboy 1.0.3
[ ] cowlib 1.0.1
[e*] mochiweb 2.13.1
[ ] rabbitmq_amqp1_0 3.6.5
...
[E*] rabbitmq_management 3.6.5
[e*] rabbitmq_management_agent 3.6.5
[ ] rabbitmq_management_visualiser 3.6.5
...
[e*] rabbitmq_web_dispatch 3.6.5
[ ] rabbitmq_web_stomp 3.6.5
[ ] rabbitmq_web_stomp_examples 3.6.5
[ ] sockjs 0.3.4
[e*] webmachine 1.10.3
Адміністративна панель RabbitMQ дуже потужна: мабуть, вона покриває 80% усіх можливих операцій з налаштування та подальшого управління.
За необхідності, вищезгаданий плагін можна деактивувати наступним чином:
# rabbitmq-plugins disable rabbitmq_management
Окрім цього для RabbitMQ є купа неофіційних плагінів, проте, як і з будь-якими неофіційними речами, гарантувати його роботу виробник не може.
Як я вже згадував, RabbitMQ, на відміну від Apache Kafka, має можливість на рівні сервера обмежувати доступи до черг для різних користувачів. Юзер, що підключився до конкретного 'virtual host' може мати окремі права на конфігурацію ресурсів, читання та запис.
Надаються такі права за допомогою утиліти rabbitmqctl і ця команда має наступний синтаксис:
# rabbitmqctl set_permissions [-p vhost] {user} {conf} {write} {read}
Наприклад, так виглядатиме команда для надання користувачу "tonyg" прав на конфігурацію всіх ресурсів, що починаються на ім'я "tonyg-", на читання та запис до всіх ресурсів в межах віртуального хосту "/myvhost":
# rabbitmqctl set_permissions -p /myvhost tonyg "^tonyg-.*" ".*" ".*"
Створимо admin-користувача для доступу до панелі адміністрування. Спочатку задаємо його ім'я та пароль:
# rabbitmqctl add_user newadmin s0m3p4ssw0rd
Creating user "newadmin" ...
Пароль звісно необхідно обрати свій. Надамо користувачу права адміністратора:
# rabbitmqctl set_user_tags newadmin administrator
Setting tags for user "newadmin" to [administrator] ...
Адміністратор зможе управляти іншими, вже доступними, користувачами, створювати нові віртуальні хости (про це трохи далі) та керувати доступами до них.
Тепер надамо новоствореному користувачу administrator повний доступ до всіх віртуальних хостів "/":
# rabbitmqctl set_permissions -p / newadmin ".*" ".*" ".*"
Setting permissions for user "newadmin" in vhost "/" ...
Наразі ми можемо поглянути на адміністративну вуб-панель RabbitMQ, що розміщена на порту 15672:
Веб-панель відтримується офіційно і має дуже обширний функціонал. Здається, що вона вміє мало не все те ж, що можна зробити в консолі, плюс відображає статистику підключень, використання ресурсів і т.п.
По замовчуванню, інсталяція RabbitMQ вже має локального користувача 'guest', котрий активний лише для локальних підключень. Його, задля підвищення рівня безпеки, краще видалити:
# rabbitmqctl delete_user guest
Deleting user "guest" ...
Віртуальна машина Erlang використовує значення erlang.cookie для визначення чи можуть декілька серверів в мережі комунікувати один з одним. Тож erlang.cookie має бути однаковим на всіх вузлах, що будуть входити в єдиний кластер:
# cat /var/lib/rabbitmq/.erlang.cookie
EQZOZBUQKOAPXDCAQAEA
# ls -la /var/lib/rabbitmq/.erlang.cookie
-r-------- 1 rabbitmq rabbitmq 20 Nov 9 00:00 /var/lib/rabbitmq/.erlang.cookie
Значення .erlang.cookie можна обрати на власний розсуд, а права доступу до цього файлу, в цілях безпеки, варто лишити мінімальними.
Збираємо кластер з наявних вузлів. Зупиняємо на rabbit2 додаток RabbitMQ:
# rabbitmqctl stop_app
Stopping node rabbit@rabbit2 ...
Про всяк випадок, видаляємо всі ресурси (черги, точки обміну і т.п.), котрі можуть бути доступні на ньому:
# rabbitmqctl reset
Resetting node rabbit@rabbit2 ...
Підключаємось до першої ноди у якості вузла кластера:
# rabbitmqctl join_cluster rabbit@rabbit1
Clustering node rabbit@rabbit2 with rabbit@rabbit1 ...
Перевріяємо статус кластера:
# rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit2 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
{alarms,[{rabbit@rabbit1,[]}]}]
До цього часу cluster_status показував, що кожний вузол в своєму кластері, до якого лише він і підключений.
Запустимо додаток RabbitMQ:
# rabbitmqctl start_app
Starting node rabbit@rabbit2 ...
Кластер, котрий складається поки лише з двох вузлів, вже працює. Варто звернути увагу на поле running_nodes, котрого в попередньому виводі не було:
# rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit2 ...
[{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
{running_nodes,[rabbit@rabbit1,rabbit@rabbit2]},
{cluster_name,<<"rabbit@rabbit1">>},
{partitions,[]},
{alarms,[{rabbit@rabbit1,[]},{rabbit@rabbit2,[]}]}]
Два вузли, які наразі працюють - це disc-ноди і всі метадані (біндинги, точки обміну) вони зберігають на диск і, таким чином, одночасне перевантаження обох вузлів не критичне для роботи кластеру. Опціонально можна створити ram-вузол, котрий згадані вище метадані буде тримати в оперативній пам'яті. Це ми і зробимо на третьому сервері групи:
# rabbitmqctl stop_app
Stopping node rabbit@rabbit3 ...
# rabbitmqctl reset
Resetting node rabbit@rabbit3 ...
Тут варто звернути увагу на додатковий ключ "--ram":
# rabbitmqctl join_cluster --ram rabbit@rabbit2
Clustering node rabbit@rabbit3 with rabbit@rabbit2 ...
# rabbitmqctl start_app
Starting node rabbit@rabbit3 ...
Ram-вузол працює дещо швидше за disc-ноди, адже йому немає необхідності постійно синхронізувати метадані на диск. У разі падіння такого вузла, дані про точки обміну і біндинги будуть втрачені, але вони будуть скопійовані від інших вузлів кластеру.
Наразі статус кластра виглядає так:
# rabbitmqctl cluster_status
Cluster status of node rabbit@rabbit3 ...
[{nodes,[{disc,[rabbit@rabbit2,rabbit@rabbit1]},{ram,[rabbit@rabbit3]}]},
{running_nodes,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]},
{cluster_name,<<"rabbit@rabbit1">>},
{partitions,[]},
{alarms,[{rabbit@rabbit1,[]},{rabbit@rabbit2,[]},{rabbit@rabbit3,[]}]}]
Це ж саме, звісно, відображається і у веб-панелі:
Отже, всі вузли наразі в єдиній групі. Перевіримо роботу кластеру. Для цього створимо користувача, віртуальний хост для нього та надамо всі необхідні права:
# rabbitmqctl add_user test_user s0m3p4ssw0rd
# rabbitmqctl add_vhost /test_queue
# rabbitmqctl set_permissions -p /test_queue test_user ".*" ".*" ".*"
Для створення черги ми скористаємось python-бібліотекою Pika, що попередньо має бути установлена, наприклад, через pip:
# aptitude -y install python-pip
# pip install pika
Код на створення черги (точки обміну) і запис повідомлення до неї виглядатиме наступним чином:
А код на отримання повідомлень з черги 'test_queue' буде мати такий вигляд:
Отже з лістингу коду можна помітити, що повідомлення в чергу буде ставитись з серверу rabbit1, а читатись вже з rabbit3. Черга буде створена не довговічна (transient), та підтвердження програмі-споживачу надсилати не треба (no_ack=True). Запускаємо створення черги та повідомлень:
# for i in 1 2; do python send.py; done
[x] Sent 'Hello World!'
[x] Sent 'Hello World!'
На цьому етапі засобами RabbitMQ можна переглянути які черги було створено і яка кількість повідомлень в цій черзі:
# rabbitmqctl -p /test_queue list_queues
Listing queues ...
test_queue 2
Зчитаємо ці повідомлення:
# for i in 1 2; do python receive.py; done
[*] Waiting for messages. To exit press CTRL+C
[x] Received 'Hello World!'
[x] Received 'Hello World!'
Чудово. Приклади створення інших черг, точок обміну та біндинг-правил до них можна побачити тут.
По-замовчуванню, в кластерній конфігурації RabbitMQ кожна нова черга створюється лише на одному вузлі (окрім точок обміну та біндингів, вони створюються по-замовчуванню одразу на всіх вузлах кластеру). Це звісно не додає надійності, адже при виході з ладу сервера, котрий фізично зберігає цю чергу, остання буде безповоротно втрачена. Щоб уникнути цього, варто задати відповідну політику збереження реплік для кожної черги (mirroring policy). Є декілька опцій для керування цими правилами:
• '{"ha-mode":"all"}'. Черга буде реплікована на всі ноди кластеру. Якщо буде доданий новий вузол - дані будуть скопійовані і на нього.
• '{"ha-mode":"exactly","ha-params":count}'. Черга та її копії будуть зберігатись на 'count' серверах. Якщо серверів менше ніж 'count' - черга буде зберігатись на всіх вузлах кластеру і у разі появи додаткового сервера - буде скопійована на нього також.
• '{"ha-mode":"nodes", "ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'. Копії черги будуть розміщені на серверах кластеру, що перераховані.
Активуємо нову політику розміщення вже створеної вище черги:
# rabbitmqctl -p /test_queue set_policy ha-all "^test" '{"ha-mode":"all"}'
Setting policy "ha-all" for pattern "^test" to "{\"ha-mode\":\"all\"}" with priority "0" ...
Ця політика по-замовчуванню активуються одразу як для черги (queue), так і для точки обміну (exchange) 'test_queue'. Проте ключем "--apply-to" її можна активувати лише для черги (--apply-to queues) або точки обміну (--apply-to exchanges).
Перевіряємо чи встановлена політика активна і для яких саме черг/точок обміну:
# rabbitmqctl -p /test_queue list_policies
Listing policies ...
/test_queue ha-all all ^test {"ha-mode":"all"} 0
Тепер черга буде існувати до наявності хоча б одного сервера в кластері. Створимо ще одну чергу під іменем 'quest_queue' (наприклад, наведеним вище send.py скриптом) після чого опишемо для неї наступну політику реплікації:
# rabbitmqctl -p /test_queue set_policy ha-exactly "^quest" '{"ha-mode":"exactly","ha-params":2}' --priority 1 --apply-to queues
Setting policy "ha-exactly" for pattern "^quest" to "{\"ha-mode\":\"exactly\",\"ha-params\":2}" with priority "1" ...
Вона аналогічно з'явиться у переліку всіх наявних політик віртуалхосту '/test_queue':
# rabbitmqctl -p /test_queue list_policies
Listing policies ...
/test_queue ha-all all ^test {"ha-mode":"all"} 0
/test_queue ha-exactly queues ^quest {"ha-mode":"exactly","ha-params":2} 1
Ця черга буде розміщена лише на 2-х з 3-х серверів кластеру. Якщо ж один із вузлів, що хостить цю чергу, впаде - черга скопіюється на інший живий вузол для досягнення необхідної кількості копій, що зазначена у призначеній політиці. Коли ж ці сервера впадуть разом одночасно - черга знову ж таки буде безповоротно втрачена.
RabbitMQ підтримує як запис так і читання черги лише з одного вузла кластеру, всі інші лише підвищують рівень доступності системи. Тобто дані спочатку читаються/записуються і лише потім реплікуються на інші черги-копії на інших вузлах. Звісно у випадку декількох черг, кожен сервер може бути місцем запису/читання (майстром) для кожної із них, тобто певний рівень масштабування все таки є.
Останні версії RabbitMQ можуть проводити реплікацію в batch-режимі, тобто групами. До версії 3.6 кожна зміна реплікувалась на слейви окремо.
Тепер постає наступне питання: як RabbitMQ обирає, хто буде майстром для кожних черги і чи можна цим якось керувати? Так, можна, за допомогою наступних параметрів, котрі слід додати до конфігураційного файлу /etc/rabbitmq/rabbitmq.conf:
• min-masters. Буде обраний вузол з найменшою кількістю майстер-черг.
• client-local. Для створення майстер-черги буде обраний вузол до якого прийшов запит від клієнта
• random. Буде обраний просто випадковий вузол кластеру.
У разі ж якщо майстер-вузол для будь-якої реплікованої черги впаде, буде обраний інший майстер серед доступних черг-копій. Підняття попереднього майстра, звісно-що, не призводить до зворотньої міграції.
RabbitMQ намагається виявити несправності в роботі якомога швидше, і тому кожний вузол кластеру надсилає heartbeat перевірки до своїх сусідів. У випадку якщо на протязі 'net_ticktime' вузол не відповість - він буде вважатись непрацюючим. . Про те, як RabbitMQ поводиться під час мережевих проблем в кластері (відпадання груп серверів і т.п.) я згадаю далі.
У разі перемикання майстра для черги, деякі повідомлення, статус доставки яких не встиг реплікувався на інший сервер, що став новим майстром, можуть бути надіслані повторно програмі-споживачу з тегом 'redelivered'. Клієнт, помітивши цей тег, може перевірити чи брав він на обробку ці дані. Звісно, що 'redelivered' тег не гарантія, що після перемикання майстра для черги, деякі повідомлення не будуть втрачені, але це значно мінімізує такі втрати.
Окрім цього можна також впливати на логіку поводження кластеру у випадку мережевих проблем, що призводить до розділення кластеру. За це відповідає параметр 'cluster_partition_handling', котрий необхідно оголосити в конфігураційному файлі RabbitMQ. Цей параметр може приймати наступні значення:
• pause-minority. У такому випадку RabbitMQ зупинить групу нод, що перебувають в меншості і сервера якої "бачать" один одного, якщо в той же час інша група, що перебуває в більшості, не відповідає на запити меншості. Ось так от, трохи заплутано. Таким чином RabbitMQ робить пріорітет виконання умов стійкості до розділення (Partition tolerance) над доступністю (Availability) теореми CAP. Тільки-но проблемні ноди "оживуть" - група, що перебуває в меншості, знову запрацює. У такому випадку робиться припущення, що група, більша за кількістю вузлів в цей час працює і обробляє запити.
• pause-if-all-down. Кластер RabbitMQ зупинить вузли, що не "бачать" хоча б одну ноду із лістингу до цього параметру. Тобто, скажімо, у випадку, коли дві ноди кластеру знаходяться в одній стійці, а дві інших - в іншій і дві останні перераховані в цьому параметрі, то група перших буде зупинена, якщо останні вузли будуть для них недоступні. Ймовірна також ситуація, якщо для одного сервера групи, що не описаний в параметрі, буде видимий лише перший сервер описаної групи, а для іншого неописаного сервера - навпаки, другий. Для таких ситуацій є можливість додавати аргументи, інші параметри: ignore чи autoheal. Це буде виглядати наступним чином:
pause_if_all_down, [nodeA, nodeB, nodeN], ignore | autoheal}
• autoheal. У цьому режимі роботи RabbitMQ перевантажить групу, що має менше підключених клієнтів і не бачить іншу группу. Якщо підключених клієнтів порівну - перевантажить меншу, за кількістю вузлів в ній, групу. Якщо ж і групи однакові по кількості вузлів - перевантажить випадкову групу на власний розсуд.
Звісно, що ніхто не відміняв того правила, що кількість вузлів в кластері має бути не парною задля зменшення наслідків від можливих Split Brain. Щоб хоч якось убезпечитись від них, у кластерах з парною кількістю вузлів, RabbitMQ зупинить всі групи, що не у строгій більшості (strict majority). Тобто у випадках, коли по обидва боки розділення знаходяться однакова кількість вузлів (наприклад, 3 з однієї сторони та 3 з іншої).
На цій останній ноті все ж таки хотілось би порівняти між собою брокери RabbitMQ та Apache Kafka:
• У Apache Kafka за позицію в черзі (offset) відповідає програма-споживач. Черга в Apache Kafka не зменшується в процесі її читання (проте черга може чиститись в процесі роботи, в залежності від внутрішніх політик), на відміну від RabbitMQ. У цьому є як і позитивні так і негативні моменти. До позитивних, наприклад, можна віднести те, що в Kafka декілька споживачів можуть обробляти ті ж повідомлення в одній черзі, до того ж їм не обов’язково бути онлайн постійно, адже черга нікуди не дінеться. Звісно, що в такому випадку відсутні відповіді-підтвердження читання повідомлень зі сторони споживачів, що з однієї сторони збільшує швидкість читання повідомлень, а з іншої - ускладнює моніторинг обробки черги і вся коректність її обробки лягає на логіку роботи клієнта-споживача. Масштабувати кількість споживачів в Kafka можна лише до кількості партицій, на які поділена черга (topic) на диску, і такі користувачі мають бути об’єднані в групу (consumer group). У RabbitMQ таких обмежень немає, адже кожна прочитане повідомлення в черзі видаляється фізично з сервера.
• Kafka вміє ділити черги на партиції (partitioning) і з їхнім знаходженням допомагає Zookeeper. Для RabbitMQ розмір диску системи являється лімітом розміру черги, partitioning відсутній. Інші сервери кластеру RabbitMQ в межах однієї черги виконують лише задачі зберігання і підтримки актуальності реплік.
• Згідно термінології RabbitMQ, Apache Kafka підтримує лише direct exchange черги: дані просто записуються в чергу і потім вже з неї читаються. Цього може бути не досить для реалізації програмної логіки.
• RabbitMQ працює по протоколу AMQP 0-9-1, що в свою чергу дозволяє працювати з бібліотеками, що підтримують саме протокол, а не сервіс. Apache Kafka використовує свої алгоритми передачі даних.
• Kafka значно швидше працює (але підтримує лише direct exchange чергу в термінології RabbitMQ). У ~100 раз швидше за RabbitMQ приймає повідомлення в чергу і в ~5 разів швидше читає з черги на виході. Я про це писав у своїй попредній статті про Kafka.
• Спосіб дистрибуції Apache Kafka бажає кращого. Це звичайні архіви з бінарним вмістом всередині. Init-скрипт, директорію для логів, користувача від якого працюватиме процес і т.п. необхідно створювати самому власноруч. І це без того, що рано чи пізно варто буде задумуватись як це все оновлювати до останніх версій.
• На відміну від RabbitMQ, Apache Kafka не має можливості обмежувати доступ до черг через певні права чи корустувачів.
Посилання:
https://habr.com/company/itsumma/blog/416629/
https://www.cloudamqp.com/blog/2015-05-18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html
https://www.cloudamqp.com/blog/2015-05-21-part2-3-rabbitmq-for-beginners_example-and-sample-code-python.html
https://www.cloudamqp.com/blog/2015-05-27-part3-rabbitmq-for-beginners_the-management-interface.html
https://www.cloudamqp.com/blog/2015-09-03-part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html
http://www.rabbitmq.com/clustering.html
https://www.rabbitmq.com/tutorials/tutorial-one-python.html
http://www.rabbitmq.com/man/rabbitmqctl.1.man.html
https://www.rabbitmq.com/tutorials/amqp-concepts.html
https://code.tutsplus.com/tutorials/managing-your-rabbitmq-cluster--cms-25597
http://jessesnet.com/development-notes/2015/rabbitmq-cluster/
http://www.slideshare.net/carrja99/high-powered-messaging-with/
http://www.slideshare.net/rahula24/amqp-basic
http://www.slideshare.net/somic/introduction-to-amqp-messaging-with-rabbitmq
https://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol
https://www.quora.com/What-are-the-differences-between-Apache-Kafka-and-RabbitMQ
Немає коментарів:
Дописати коментар