Highly Available кластер RabbitMQ

  • Tutorial
Знакомимся с RabbitMQ

Переводы на хабре:
RabbitMQ tutorial 1 — Hello World
RabbitMQ tutorial 2 — Очередь задач
RabbitMQ tutorial 3 — Публикация/Подписка

Сразу дополню некоторые недочеты. И кратко повторю основные термины.

Принцип работы архитектуры использующей rabbitMq

image



Есть приложение (клиент) генерирующее сообщения, сообщения попадают в точку обмена, в зависимости от параметров сообщения и настроек точки обмена сообщение копируется в одну или несколько очередей (или просто удаляется), после чего клиенты могут забрать сообщения из очередей.

Очередь связывается с точкой обмена по ключу маршрутизации или заголовку сообщения.

Ключ маршрутизации — значение, которое указывается при публикации сообщения в точку обмена, служит для определения очередей в которые попадет сообщение.

Заголовок сообщения — набор аргументов вида ключ-значение связанных с сообщением.

Точки обмена могут быть 4 типов:
  • direct — сообщения попавшие в эту точку обмена будут скопированы только в те очереди, которые связаны с точкой обмена строгим ключом маршрутизации.
  • topic — ключ маршрутизации может быть составным, и задаваться в виде паттерна, для чего существуют два специальных символа: * — обозначает одно слово, # — одно или несколько слов. Слова разделяются точкой. Пример: routingKey = "*.database" — все сообщения с ключами в которых вторым словом значится database будут скопированы в привязанные по паттерну очереди.
  • headers — очередь связывается с точкой обмена не по ключу маршрутизации, а по заголовку сообщения, указывается условие, какие аргументы и их значения ожидаются, и при получении точкой обмена сообщения с заголовком содержащим аргументы из условия, очередь его получает. Пример можно посмотреть тут.
  • fanout — сообщение поступившее в точку обмена копируется во все привязанные очереди, без проверки ключа маршрутизации или заголовка сообщения.


У каждой очереди есть 4 флага определяющих ее поведение:
  • auto_delete — если очередь пустая и к ней нет активных подключений, очередь автоматически удаляется
  • durable — устойчивая очередь, сообщения не теряются при рестарте rabbitMQ (или внезапной перезагрузке), при публикации и до окончания отдачи хранятся в базе данных
  • exclusive — очередь предназначена для не более чем одного подключения единовременно
  • passive — при объявлении очереди пассивной, при обращении клиента сервер будет считать что очередь уже создана, т.е. не будет автоматически создавать ее в случае отсутствия, этот вариант нужен если вы хотите обратиться к серверу не изменяя его состояние. Например, вам просто нужно проверить существует ли очередь. Для этого объявляете очередь пассивной, и если получаете ошибку, значит очередь не существует.


Теперь немного о самой работе rabbit'а, при установке и запуске в качестве службы используются настройки по-умолчанию, как пишут разработчики в официальной документации, этого должно хватить на большинство сценариев, однако я еще не встречал ситуаций в реальных продуктов когда хватало бы настроек по умолчанию. Параметры работы можно менять в рантайме используя служебные утилиты (расположены в каталоге \rabbitmq_server-3.2.0\sbin), однако изменения внесенные таким образом будут потеряны при перезапуске rabbitmq (соответственно и при перезагрузке). Переходим к следующей теме.

Конфигурация rabbitMQ


Конфигурация работы сервера RabbitMQ располагается в трех местах, это переменные окружения (задаются порты и расположения и имена файлов), файлы конфигурации (настройки доступа, кластеры, плагины), и настройки задаваемые в рантайме (политики, настройки производительности).

При установке в Windows файл конфигурации не создается, есть только его пример расположенный в каталоге \rabbitmq_server-3.2.0\etc\rabbitmq.config.example. Создаем свой файл конфигурации, называем его rabbitmq.config (расширение именно .config и никак иначе!), и заполняем простыми настройками:
%% Sample
[
	{rabbit,
		[
		{tcp_listeners, [5672]},
		{log_levels, [{connection, error}]},
		{default_vhost,       <<"/">>},
		{default_user,        <<"username">>},
		{default_pass,        <<"password">>},
		{default_permissions, [<<".*">>, <<".*">>, <<".*">>]},
		{heartbeat, 60},
		{frame_max, 1048576}
		]}
].

Обрамление <<>> — это не ошибка, так и должно быть.
Комментарии в настройках предваряются двойным символом процента — %%.

Теперь размещаем файл в удобном месте, например в корневой папке с установленным сервером RabbitMQ, для примера пусть будет путь:
c:\rabbitmq\rabbitmq.config

Для того чтобы RabbitMq смог увидеть файл конфигурации необходимо создать переменную окружения с его расположением
RABBITMQ_CONFIG_FILE = c:\rabbitmq\rabbitmq

Переменную стоит создать и в окружении пользователя и в окружении системы.
Пишем путь до имени файла, расширение обрезаем. Создание конфиг файла и настройку окружения лучше проводить до установки RabbitMQ сервера, или же переустановить его после. (For environment changes to take effect the service must be re-installed.) Простой перезапуск не помогает.

Теперь можно установить Эрланг и сервер RabbitMQ.

Создание и настройка кластера

Внимание
Все команды выполняемые в командной строке, выполняются в каталоге инструментов установленного RabbitMQ сервера:
RabbitMQ\RabbitMQ Server\rabbitmq_server-3.2.1\sbin

Кластер, в rabbitMQ, это связь одного и более серверов RabbitMQ друг с другом, в которой один из узлов выступает в роли мастер-сервера, остальные в роли слейв-серверов, на мастере задаются настройки кластера, которые дублируются на слейвы, к ним, в частности, относятся настройки доступа и политик. При падении мастера, один из слейвов берет на себя его роль, и становится мастером.

В первую очередь, до создания кластера нам нужно синхронизировать куки RabbitMQ узлов, куки в RabbitMQ это сгенерированный при установке хэш, который используется как идентификатор узла, т.к. кластер выступает как единый узел, на каждом сервере куки должны быть идентичны.
На мастер-сервере берем файл
%WINDOWS%\.erlang.cookie

и копируем его с заменой по пути
C:\Users\%CurrentUser%\.erlang.cookie

, после чего копируем с заменой на каждый узел кластера по указанным путям.

Создание кластера проводится выполнением следующих команд на каждом слейве:
rabbitmqctl stop_app
rabbitmqctl join_cluster --ram rabbit@master
rabbitmqctl start_app


Либо указанием в файле конфигурации:
{cluster_nodes, {["rabbit@master", "rabbit@host01"], disc}}


Данную процедуру требуется выполнить только один раз, при добавлении нового узла, в дальнейшем узел будет подключаться к кластеру автоматически (например, после перезагрузки сервера на котором поднят узел).

Кластер создан, но для полноценного использования не годится, на текущем этапе очереди и сообщения на каждом узле живут отдельно, никакой синхронизации не производится, а значит если два клиента подключаться к кластеру к разным узлам, то когда один из них опубликует сообщения в очередь, второй ничего об это не узнает. Также при падении одного из узлов, все сообщения которые были на нем будут потеряны.

Политики синхронизации


Заходим в инструменты и выполняем в рантайме следующую команду:
rabbitmqctl set_policy HA ".*" "{""ha-mode"": ""all""}"


Теперь все очереди и сообщения в них будут синхронизироваться. При публикации сообщения, оно становится доступным для клиентов только после копирования на все узлы кластера.

Осталась одна проблема, при падении одного из узлов, клиенты подключенные к нему должны определять факт падения и уметь переключаться на доступные узлы, разработчики RabbitMQ пишут:

Подключение к кластеру клиентских приложений.

Клиент может подключаться к любому узлу кластера. Если узел падает в то время как остальная часть кластера продолжает работать, то клиенты подключенные к нему должны определить факт падения и должны уметь переподключаться к кластеру, к рабочим узлам. Как правило не рекомендуется вносить реальные IP всех узлов кластера в клиентские приложения, это мешает гибкости как в работе самих приложений так и в настройке кластера. Вместо этого мы рекомендуем использовать более абстрагированный подход: это может быть какой-либо DNS сервис с очень малым значением TTL, или простой TCP балансировщик, или какого-либо рода мобильный IP, или аналогичные технологии. В общем, этот аспект выходит за рамки RabbitMQ, и мы рекомендуем использовать технологии разработанные для решения этих проблем.


Т.е. рекомендуется не писать велосипед, а воспользоваться готовым решением, в своем варианте я использую NLB, как нативное встроенное в Windows решение. Этот этап остается на ваше усмотрение.

Полезности

Пингуем узел из командной строки:
rabbitmqctl eval "net_adm:ping(rabbit@hostname)."

, если узел доступен получаем ответ pong

Ссылки

www.rabbitmq.com/clustering.html
www.rabbitmq.com/ha.html
Поддержать автора
Поделиться публикацией

Комментарии 25

    +1
    … но у меня так и не получилось заставить этот способ работать.

    Вот на этих словах заканчивается 95% всех HA-проектов.
    Если вы не смогли автоматизировать процесс восстановления — это уже все что угодно, но не HA.
      0
      Это не для восстановления, это для автоматического подключения новый ноды к кластеру.
      А официальная документация, вполне черным и достаточно по белому, об этом говорит:
      Set this to cause clustering to happen automatically when a node starts for the very first time. The first element of the tuple is the nodes that the node will try to cluster to. The second element is either disc or ram and determines the node type.

      PS: К автору статьи тоже относится, не вводите в заблуждение пожалуйста. Ноды самостоятельно прекрасно восстанавливаются, будучи подключены единожды к кластеру, при прочих равных.
        0
        Действительно, перепроверил, ноды отлично восстанавливаются после перезапуска сервера. Спасибо, дополню.
          0
          ок, теперь положите сеть между двумя нодами трехнодового кластере на минуту, затем верните обратно.
          все еще отлично восстанавливаются?
            0
            Поднял трехнодовый кластер, уронил сеть на 2 минуты
            image
            , включил сеть
            image
            перезапустил ноду
            image

            Запустил тестовое приложение, коннект нормальный, данные пишет, синхронизация отрабатывает.
            Вывод: нельзя чтобы падала сеть.
              +3
              > Вывод: нельзя чтобы падала сеть.

              Ха-ха.

              Кстати, вот вам еще пара тестов:
              — уроните одну ноду, потом вторую, потом поднимите первую, а затем вторую.
              — уроните первую ноду, потом поднимите ее через какое-то время и тут же положите вторую, а затем поднимите ее.

              Разумеется, тесты надо проводить, все это время помещая сообщения в очереди, желательно, с большой задержкой извлекая их после помещения.

              Подсказки:
              — в первом тесте на третьем шаге первая нода у вас тупо не поднимется без второй.
              — во втором тесте вы потеряете на четвертом шаге, когда поднимете вторую ноду, те сообщения, которые легли в очереди после падения первой ноды, но не успели извлечься потребителями до момента падения второй.

              И, вообще, чтобы в общем обрисовать ситуацию, отмечу несколько моментов:
              — на свежеподнятой (например, после падения) ноде все очереди переключаются в ведомое состояние и сбрасывают свое содержимое (даже если на ведущих очередях на другой ноде каких-то сообщений нет)
              — запись на диск содержимого персистентных очередей идет без каких-либо гарантий, т.е. никакого ACID
              — по-умолчанию, разные ноды кластера не синхронизируют содержимое очередей, потому, при восстановлении упавшей ноды сообщения, прилетевшие на живую после падения первой, существуют только на одной ноде и на свежеподнятую не будут реплицированы. т.е. реплицируется между нодами только входящий поток сообщений, но не содержимое очередей. В последних версиях синхронизация появилась, но она не гарантирует вам, что автоматически она будет работать правильно.

              Вот такие вот пироги.
                0
                Все верно но есть пара замечаний.

                1. 2 ноды еще не кластер, введение 3й значительно повышает consistency (даже великому mssql нужен арбитр)
                2. Кластеризация кролика (в текущем виде) не может быть гео-избыточной, вот хоть убейся, но это так.
                3. Не очень понял про запись на диск, mnesia же и есть запись в RAM.
                4. Зависит от типа падения. Если падение сервера — так не будет, если обрыв связи — да, split. Это решается настройкой авторитетной ноды.
                  0
                  Я конечно не настоящий Erlang-сварщик, но на счет mnesia помню, что ее отдельные ноды можно конфигурировать, чтобы они реплицировали данные на диск.
                    0
                    кролику надо хранить свою статистику и он требует для этого диска + у него есть системные обменники с признаком гарантированной доставки. им тоже нужен диск. в кластере всегда будет нода с требованием диска. как вариант, если не критично, выносить на tmpfs.
                    0
                    1. добавление 3ей ноды в кластер rabbitmq ничего общего с понятием кворума не имеет, поскольку нормального арбитража в rabbitmq нет, как и, к слову, нормальной синхронизации (синхронизация очередей сейчас выглядит как костыль, поскольку на время синхронизации работа с очередями недоступна). И, вообще, о какой consistency может идти речь, если нет даже write consisntecy между нодами кластера?
                    3. Запись на диск и гарантированная запись на диск — это две большие разницы. У вас может быть disk node с persistent queues, но при этом помещение сообщения в очередь не означает, что она легла на диск, а, например, не осела в кэше.
                    4. Я, возможно, недостаточно знаю про rabbitmq, но про «авторитетную ноду» в контексте mirrored queues слышу впервые. Состояние ведущего/ведомого имеют сами очереди, а не ноды.

                    Вообще, надо просто учитывать, что rabbitmq mirrored queues защищают от падения N-1 серверов в кластере и, вообще, имеют ряд специфичных особенностей в эксплуатации. Если серверы у вас поморгают в неудачной для вас последовательности, никто вам сохранность данных не гарантирует.
                    Потому рассматривать mirrored queues как универсальное средство обеспечения отказоустойчивости не следует.
                0
                Да. Новые сообщения будут синхронизироваться, старые останутся на нодах и будут отличаться. Стандартная ситуация для master-master.
                Решаем проблему настройкой весов и приоритетов. Для общего понимания гуглим split-brain и CAP-теорема.
                  0
                  Вопрос на засыпку: какие из букв аббревиатуры CAP обеспечивает кластер rabbitmq?
                    0
                    Вероятно, AP. Да и то, соответствующим клиентом.
                      0
                      Нет. PT отсутствет напрочь, о чем говорится в документации, да и в комментариях выше уже упоминалось. Сеть должна работать стабильно, иначе кластеру хана.
                      Фактически, rabbitmq обеспечивает только availability: про pt я только что сказал, строгая же consistency отсутствеут (репликация только входящего потока данных, притом асинхронная, и гарантия записи на диск в лучших традициях монги).
                      При этом, если мы попытаемся сделать сетевую консистентность чуть менее слабой (строгую мы не получим все равно), включив автосинхронизацию очередей, то мы потеряем даже availability, потому что в течение синхронизации очереди недоступны ни на запись, ни на чтение.
                        0
                        Можете привести пример брокера сообщений (желательно такого у которого есть клиенты для .net), который покрывает указанные недостатки RabbitMQ. До этой статьи мне казалось что уж брокер построенный на языке созданным специально для отказоустойчивых приложений и с приличной историей обязан быть в рядах лучших.
                        Из прочих я рассматривал ZeroMQ, но судя по всему он будет обладать аналогичными недостатками.
                          0
                          Zmq ни пол раза не брокер, к сожалению. Оно скорее шустрая библиотека для ipc со всякими плюшками (подтверждение доставки, роутинг).
                            0
                            Из брокеров я сам работал только rabbitmq, потому с уверенностью ничего посоветовать не могу.
                            Я знаю, что есть еще другие реализации amqp-брокеров, но они медленнее, чем rabbitmq, при использовании persistent очередей. Возможно, как раз за счет строгого соблюдения консистентности, но это лишь моя догадка.
                            0
                            Т. е. при split brain кролика «разрывает на куски». Печально.

                            Я не использовал rabbitmq в их «HA» конфигурациях.
              0
              если узел доступен получаем ответ pang

              либо исправить на pong, либо исправить на не доступен
                +7
                Уж простите что здесь отвечаю… Тема уж больно до боли знакомая.

                Что было у нас:

                Задача организовать подписку на события для клиентов (кроссплатформенный клиент-бороузер). расчетное кол-во клиентов 1000000. Стресс задача — все клиенты (сволочи) ломятся за событиями. Собирали стенд на 1000000 одновременных коннектов. остановились на 210000 потому, что понятно, что держит и как масштабироваться и смысл лупить дальше коннекты не имеет смысла.



                конфиг nginx

                #
                # Transport site
                #
                
                upstream rabbitmq {
                        server  dev-queue1:15674 weight=5;
                        server  dev-queue2:15674 weight=10;
                        server  dev-back1:15674  weight=10;
                        server  dev-back2:15674  weight=10;
                        server  dev-back3:15674  weight=10;
                        server  dev-back4:15674  weight=10;
                        server  dev-back5:15674  weight=10;
                        server  dev-back6:15674  weight=10;
                        server  dev-back7:15674  weight=10;
                        server  dev-back8:15674  weight=10;
                }
                
                server {
                    listen          80;
                    listen          88;
                
                    server_name 10.76.156.241 dev-front1;
                    access_log     off;
                    error_log          /dev/null;
                
                    root         /var/www;
                
                    location /stomp/ {
                        proxy_pass http://rabbitmq;
                        proxy_http_version 1.1;
                        proxy_set_header Upgrade $http_upgrade;
                        proxy_set_header Connection "upgrade";
                        proxy_buffering off;
                    }
                
                    location / {
                    }
                
                }
                
                
                #
                # end
                #
                


                Собственно как все решилось. Клиент использует websocket+stomp. кролик умеет stomp из коробки. между клиентами и кроликом стоит nginx, который умеет апстримы и вебсокеты. тем самым при выпадении любой нодды клиент просто переконекчивается и живет дальше. nginx так же осуществляет балансировку по нодам кролика. nginx балансируется DNS балансировкой с ТТЛ 60, тем самым решается автоматическое исключение фронта, т.е. кластер полностью автоматизирован.

                Во что уперлись. Да собственно в канал, потому, что выдать всем клиентам одновременно 1024байт (json) в таких масштабах превращается в гигабиты траффика. Мы уперлись на 1Гб в сетевухи. Инженеры могли переделать на 10Гб, но нам уже было не надо.

                Что имели при тестах. Без оптимизации ОС кролик благополучно падал так, что восстановить кластер было не возможно. Выедал всю память, Уходил в своп и тю-тю… kill -9 и кластера нет. Лечилось полной остановкой, удалением mnesia кролика и пересборкой кластера ручками.

                Рекомендации. Не жалейте памяти. Для кролика оно все. Считайте циферки до, чем после. Прежде чем что-то сделать — нарисуйте на бумаге и покажите знакомым. Может что посоветуют.

                Что использовали:

                — CentOS release 6.5 (Final) (тюненый TCP стек и ядро под HA)
                — {rabbit,«RabbitMQ»,«3.1.5»},
                — {mnesia,«MNESIA CXC 138 12»,«4.5»},
                — nginx version: nginx/1.4.4
                — клиент sock.js stomp.js из коробки (проверяли почти во всех броузерах. все пашет именно по вебсокетам на постоянном коннекте. старые ИЕ летят по HRX по лонг-поллинг)

                Резюме? Все работает почти из коробки. Немного напильника и жизнь удалась.
                  0
                  Так для того комментарии и нужны, было бы отлично если бы побольше людей делились советами из своего опыта.
                  0
                  Спасибо за статью, особенно спасибо товарищу 440hz за полезный коментарий, так как в данный момент как раз работаю над подобной задачей
                    0
                    будут вопросы — пиши. ответим. НА дело тонкое и полное нюансов. собственно мы отказались от единого кластера и разбиваем на мелкие по задачам, но фронт в лице нгинкса единый.
                    0
                    А эта упячка уже научилась не пересылать по сети лямбды?
                    Когда кластер разваливается от того, что на разных хостах разные версии виртуальной машины или самого кролика, ни о каком HA речи идти не может, поскольку оно не позволяет делать апгрейд без остановки.
                      0
                      мысль хорошая. про апгрейд даже пока не думали.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое