Эксплуатация кроликов (RabbitMQ) в режиме «Выжить любой ценой»

«Компания» — оператор связи ПАО «Мегафон»
«Нода» — сервер RabbitMQ.
«Кластер» — совокупность, в нашем случае трех, нод RabbitMQ работающих как единое целое.
«Контур» — совокупность кластеров RabbitMQ, правила работы с которыми определяются на стоящем перед ними балансировщике.
«Балансировщик», «хап» — Haproxy – балансировщик, выполняющий функции переключения нагрузки на кластеры в рамках контура. Для каждого контура используется пара серверов Haproxy, работающих параллельно.
«Подсистема» — публикатор и/или потребитель сообщений, передаваемых через кролика
«СИСТЕМА» — совокупность Подсистем, являющая собой единое программно-аппаратное решение, используемое в Компании, характеризующееся распределённостью по всей территории России, но обладающее несколькими центрами, куда стекается вся информация и где происходят основные расчёты и вычисления.
СИСТЕМА – географически распределённая система – от Хабаровска и Владивостока до Санкт-Петербурга и Краснодара. Архитектурно это несколько центральных Контуров, разделенных по особенностям подсистем, к ним подключённым.

Какая задача возлагается на транспорт в реалиях телекома?


В двух словах: на каждое действие абонента следует реакция Подсистем, которые в свою очередь информирует другие Подсистемы о событиях и последующих изменениях. Сообщения порождаются любыми действиями с СИСТЕМОЙ, не только со стороны абонентов, но и со стороны сотрудников Компании, и со стороны Подсистем (очень большое количество задач выполняется в автоматическом режиме).

Особенности транспорта в телекоме: большой, нет не так, БОЛЬШОЙ поток разнообразных данных, передаваемых через асинхронный транспорт.

Некоторые Подсистемы живут на отдельных Кластерах в силу тяжеловесности потоков сообщений – ни на кого другого на кластере просто не остаётся ресурсов, например, при потоке сообщений 5-6 тыс. сообщений / секунду, объём передаваемых данных может доходить до 170-190 Мегабайт / секунду. При таком профиле нагрузки попытка приземлить на этот кластер ещё кого ни будь, приведёт к печальным последствиям: поскольку ресурсов для обработки всех данных одновременно не хватает, кролик начнёт загонять входящие соединения во flow – начнётся простой публикаторов, со всеми последствиями для всех Подсистем и СИСТЕМЫ в целом.

Основные требования к транспорту:

  1. Доступность транспорта должны быть 99,99%. На практике это выливается в требование обеспечения работы 24/7 и способность автоматически реагировать на любые аварийные ситуации.
  2. Обеспечение сохранности данных: % потерянных сообщений на транспорте должен стремиться к 0.

Например, на сам факт совершения вызова, через асинхронный транспорт пролетает несколько разных сообщений. какие-то сообщения предназначены для подсистем, живущих в этом же Контуре, а какие-то предназначены для передачи в центральные узлы. Одно и то же сообщение может быть востребовано несколькими подсистемами, поэтому на этапе публикации сообщения в кролика происходит его копирование и направление к разным потребителям. А в некоторых случаях копирование сообщений вынужденно реализовывается на промежуточном Контуре – когда информацию надо доставить от Контура в Хабаровске, до контура в Краснодаре. Передача выполняется через один из центральных Контуров, где делаются копии сообщений, для центральных получателей.

Кроме событий, вызванных действиями абонента, через транспорт ходят сообщения служебные, которыми обмениваются Подсистемы. Таким образом получается несколько тысяч разных маршрутов передачи сообщений, некоторые пересекаются, некоторые существуют изолированно. Достаточно назвать количество вовлечённых в маршруты очередей на разных Контурах, чтобы понять примерный масштаб транспортной карты: На центральных контурах 600, 200, 260, 15…и на удалённых Контурах по 80-100…

При такой вовлечённости транспорта требования о 100% доступности всех транспортных узлов уже не кажутся чрезмерными. К реализации этих требований мы и переходим.

Как мы решаем поставленные задачи


Кроме непосредственно RabbitMQ, для балансировки нагрузки и обеспечения автоматического реагирования на аварийные ситуации, используется Haproxy.

Несколько слов об программно-аппаратном окружении, в котором существуют наши кролики:

  • Все сервера кроликов виртуальные, с параметрами 8-12 CPU, 16 Gb Mem, 200 Gb HDD. Как показал опыт, даже использование жутких не виртуальных серверов на 90 ядер и кучей ОЗУ обеспечивает небольшой прирост производительности при значительно больших затратах. Используемые версии: 3.6.6 (на практике – самая устойчивая из 3.6) с эрлангом 18.3, 3.7.6 с эрлангом 20.1.
  • Для Haproxy требования значительно ниже: 2 CPU, 4 Gb Mem, версия haproxy – 1.8 stable. Загруженность по ресурсам, на всех серверах haproxy, не превышает 15% CPU/Mem.
  • Располагается весь зоопарк в 14 ЦОДах на 7 площадках по всей стране, объединённых в единую сеть. В каждом из ЦОДов располагается Кластер из трёх Нод и один Хап.
  • Для удалённых Контуров используются по 2 ЦОДа, для каждого из центральных Контуров — по 4.
  • Центральные Контуры взаимодействуют как между собой, так и с удалёнными Контурами, в свою очередь удалённые Контуры работают только с центральными, прямой связи между собой не имеют.
  • Конфигурации Хапов и Кластеров в рамках одного Контура полностью идентичны. Точкой входа для каждого Контура псевдоним на несколько A-DNS записей. Таким образом, чтобы не произошло, будет доступен хотя бы один хап и хотя бы один из Кластеров (хотя бы одна нода в Кластере) в каждом Контуре. Поскольку случай выхода из строя даже 6 серверов в двух ЦОДах одновременно крайне маловероятен, принимается доступность близкая к 100%.

Выглядит задуманное (и реализованное) всё это примерно так:

image

image

Теперь немного конфигов.

Конфигурация haproxy
frontend center-rmq_5672
bind *:5672
mode tcp
maxconn 10000
timeout client 3h
option tcpka
option tcplog
default_backend center-rmq_5672
frontend center-rmq_5672_lvl_1
bind localhost:56721
mode tcp
maxconn 10000
timeout client 3h
option tcpka
option tcplog
default_backend center-rmq_5672_lvl_1
backend center-rmq_5672
balance leastconn
mode tcp
fullconn 10000
timeout server 3h
server srv-rmq01 10.10.10.10:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server srv-rmq03 10.10.10.11:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server srv-rmq05 10.10.10.12:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server localhost 127.0.0.1:56721 check inter 5s rise 2 fall 3 backup on-marked-down shutdown-sessions
backend center-rmq_5672_lvl_1
balance leastconn
mode tcp
fullconn 10000
timeout server 3h
server srv-rmq02 10.10.10.13:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server srv-rmq04 10.10.10.14:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions
server srv-rmq06 10.10.10.5:5672 check inter 5s rise 2 fall 3 on-marked-up shutdown-backup-sessions


Первая секция фронта описывает точку входа – ведущую на основной Кластер, вторая секция предназначена для балансировки резервного уровня. Если просто описать в секции backend все резервные сервера кроликов (инструкция backup), то срабатывать будет так же – при полной недоступности основного кластера, соединения пойдут на резервный, однако, все соединения пойдут на ПЕРВЫЙ в списке backup сервер. Для обеспечения балансировки нагрузки на все резервные ноды, как раз и вводим ещё один фронт, который делаем доступным только с localhost и именно его назначаем backup сервером.

Приведённый пример описывает балансировку удалённого Контура – который работает в рамках двух ЦОДов: сервера srv-rmq{01,03,05} – живут в ЦОД №1, srv-rmq{02,04,06} – в ЦОД №2. Таким образом для реализации четырёх-цодового решения нам требуется только дописать ещё два локальных фронта и две backend секции соответствующих серверов кроликов.

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

Опыт эксплуатации такой конфигурации показывает практически 100% доступность каждого из Контуров. Данное решение требует от Подсистем вполне законного и простого: уметь восстанавливать соединение с кроликом после разрыва связи.

Итак, балансировку нагрузки на произвольное количество Кластеров и автоматическое переключение между ними мы обеспечили, пора переходить непосредственно к кроликам.

Каждый Кластер создан из трёх нод, как показывает практика – наиболее оптимальное количество нод, при котором обеспечивается оптимальный баланс доступность / отказоустойчивость / скорость работы. Поскольку кролик горизонтально не масштабируется (производительность кластера равна производительности самого медленного сервера), создаём все ноды с одинаковыми, оптимальными параметрами по CPU/Mem/Hdd. Располагаем сервера как можно ближе друг к другу – в нашем случае запиливаем виртуальные машины в рамках одной фермы.

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

  1. Работа с кроликом идёт только по протоколу amqp/amqps – через балансировку. Авторизация под локальными учётками — в рамках каждого Кластера (ну и Контура в целом)
  2. Подсистемы подключаются к кролику в пассивном режиме: Никаких манипуляций с сущностями кролей (создание очередей/эксченджей/биндов) не допускается и ограничивается на уровне прав учётных записей – на конфигурирование прав просто не даём.
  3. Все необходимые сущности создаются централизовано, не средствами Подсистем и на всех Кластерах Контура делаются одинаково – для обеспечения автоматического переключения на резервный Кластер и обратно. Иначе можем получить картину: на резерв переключились, а очереди или бинда там нет, и можем получить на выбор либо ошибку подключения, либо пропажу сообщений.

Теперь непосредственно настройки на кроликах:


  1. У локальных УЗ нет доступа к Web интерфейсу
  2. Доступ к Web организуется чрез LDAP – интегрируем с AD и получаем логирование кто и куда на вебке ходил. На уровне конфигурации ограничиваем права у учёток AD, мало того, что требуем нахождение в определённой группе, так и права даём только на «посмотреть». Группы monitoring более чем достаточно. А права администратора навешиваем на другую группу в AD, таким образом сильно ограничивается круг влияния на транспорт.
  3. Для облегчения администрирования и отслеживания:
    На всех VHOST сразу вешаем политику 0 уровня с применением на все очереди (pattern: .*):

    • ha-mode: all – хранить все данные на всех нодах кластера, снижается скорость обработки сообщений, но обеспечивается их сохранность и доступность.
    • ha-sync-mode: automatic – поручаем кролю автоматически синхронизировать данные на всех нодах кластера: так же увеличивается сохранность и доступность данных.
    • queue-mode: lazy – пожалуй одна из самых полезных опций, появившаяся в кроликах с версии 3.6 – немедленная запись сообщений на HDD. Данная опция кардинально снижает потребление ОЗУ и увеличивает сохранность данных при остановках/падениях нод или кластера в целом.

  4. Настройки в файле конфигурации (rabbitmq-main/conf/rabbitmq.config):

    • Секция rabbit: {vm_memory_high_watermark_paging_ratio, 0.5} – порог выгрузки сообщений на диск 50%. При включённом lazy служит больше как страховка, когда нарисуем политику, например, 1 уровня, в которую забудем включить lazy.
    • {vm_memory_high_watermark, 0.95} – ограничиваем кроля 95% всей ОЗУ, поскольку на серверах живёт только кролик, нет смысла вводить более жёсткие ограничения. 5% «широким жестом» так и быть – оставляем ОС, мониторингу и прочим полезным мелочам. Поскольку это значение является верхней границей — ресурсов хватает всем.
    • {cluster_partition_handling, pause_minority} – описывает поведение кластера при возникновении Network Partition, для трёх и более нодового кластера рекомендуется именно такой флаг – позволяет кластеру самому восстановиться.
    • {disk_free_limit, «500MB»} – тут всё просто, когда останется свободного места на диске 500 MB – публикация сообщений будет остановлена, будет доступно только вычитывание.
    • {auth_backends, [rabbit_auth_backend_internal, rabbit_auth_backend_ldap]} — порядок авторизации на кроликах: Сначала проверяется наличие УЗ в локальной базе и если не находится – идём на сервера LDAP.
    • Секция rabbitmq_auth_backend_ldap – конфигурация взаимодействия с AD: {servers, [«srv_dc1»,«srv_dc2»]} – список контроллеров домена, на которых и будет проходить аутентификация.
    • Параметры, непосредственно описывающие пользователя в AD, порт LDAP и прочее, сугубо индивидуальны и подробно описаны в документации.
    • Самое важное для нас – описание прав и ограничений на администрирование и доступ к Web интерфейсу кролей: tag_queries:
      [{administrator,{in_group, «cn=rabbitmq-admins,ou=GRP,ou=GRP_MAIN,dc=My_domain,dc=ru»}},
      {monitoring,
      {in_group, «cn=rabbitmq-web,ou=GRP,ou=GRP_MAIN, dc=My_domain,dc=ru»}
      }]
      – данная конструкция обеспечивает административные привилегии всем пользователям группы rabbitmq-admins и права monitoring (минимально достаточные для доступа на просмотр) для группы rabbitmq-web.
    • resource_access_query:
      {for,
      [{permission,configure, {in_group, «cn=rabbitmq-admins,ou=GRP,ou=GRP_MAIN,dc=My_domain,dc=ru»}},
      {permission,write, {in_group, «cn=rabbitmq-admins,ou=GRP,ou=GRP_MAIN,dc=My_domain,dc=ru»}},
      {permission, read, {constant, true}}
      ]
      }
      – обеспечиваем права на конфигурирование и запись только группе администраторов, всем остальным, кто успешно авторизуется права доступны только на чтение – вполне может вычитать сообщения через Web интерфейс.

Получаем сконфигурированный (на уровне файла конфигурации и настроек в самом кролике) кластер, который максимально обеспечивает доступность и сохранность данных. Этим мы реализуем требование – обеспечение доступности и сохранности данных…в большинстве случаев.

Есть несколько моментов, которые стоит учитывать при эксплуатации подобных высоко нагруженных систем:

  1. Все дополнительные свойства очередей (TTL, expire, max-length и прочее) лучше организовывать политиками, а не вешать параметрами при создании очередей. Получается гибко настраиваемая структура, которую можно подгонять на лету под изменяющиеся реалии.
  2. Использование TTL. Чем длиннее очередь, тем выше будет нагрузка на CPU. Чтобы не допустить «пробивания потолка» лучше длину очереди так же ограничить через max-length.
  3. Кроме самого кролика на сервере крутится ещё некоторое количество служебных приложений, которым, как ни странно, тоже требуются ресурсы CPU. А прожорливый кролик, по умолчанию, занимает все доступные ядра…Может получиться неприятная ситуация: борьба за ресурсы, которая запросто приведёт к тормозам на кролике. Избежать возникновения такой ситуации можно, например, так: Изменить параметры запуска эрланга – ввести принудительное ограничение на количество используемых ядер. Делаем это следующим образом: находим файл rabbitmq-env, ищем параметр SERVER_ERL_ARGS= и добавляем к нему +sct L0-Xc0-X +S Y:Y. Где X-число ядер-1 (отсчёт начинается с 0), Y – Количество ядер -1 (отсчёт с 1). +sct L0-Xc0-X – меняет привязку к ядрам, +S Y:Y – изменяет число шедулеров, запускаемых эрлангом. Так для системы из 8 ядер добавляемые параметры примут вид: +sct L0-6c0-6 +S 7:7. Этим мы отдаём кролику только 7 ядер и рассчитываем, что ОС запуская другие процессы поступит оптимально и повесит их на не нагруженное ядро.

Нюансы эксплуатации получившегося зоопарка


От чего не может защитить никакая настройка, так это от развалившейся базы mnesia – к сожалению, случающееся с не нулевой вероятностью. К подобному плачевному результату приводят не глобальные сбои (например, полный выход из строя целого ЦОДа – нагрузка просто переключится на другой кластер), а сбои более локальные – в рамках одного сегмента сети.

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

Например, многократное моргание сети, причем с периодичностью больше 5 секунд (именно такой таймаут выставлен в настройках Хапов, можно им конечно поиграться, но для проверки эффективности потребуется повторение сбоя, чего никому не хочется).

Одну – две такие итерации кластер ещё выдержать может, но больше – уже шансы минимальны. В такой ситуации может спасти остановка выпавшей ноды, но успеть это сделать вручную практически невозможно. Чаще результатом является не просто выпадение ноды из кластера с сообщением «Network Partition», но и картина, когда данные по части очередей жили как раз этой ноде и не успели синхронизироваться на оставшиеся. Визуально – в данных по очередям стоит NaN.

И вот это уже однозначный сигнал – переключиться на резервный Кластер. Переключение обеспечит хап, требуется только остановить кроликов на основном кластере – дело нескольких минут. В результате получаем восстановление работоспособности транспорта и можем спокойно приступить к анализу аварии и её устранению.

Для того, чтобы вывести из-под нагрузки повреждённый кластер, с целью не допустить дальнейшей деградации, самое простое: заставить кролика работать на портах отличных от 5672. Поскольку Хапы у нас следят за кроликами именно по штатному порту, смещение его, например, на 5673 в настройках кролика позволит полностью безболезненно запускать кластер и попытаться восстановить его работоспособность и сообщения, оставшиеся на нём.

Делаем в несколько шагов:

  1. Останавливаем все ноды сбойного кластера – хап переключит нагрузку на резервный Кластер
  2. Добавляем RABBITMQ_NODE_PORT=5673 в файл rabbitmq-env – при запуске кролика настройки эти настройки подтянутся, при этом Web интерфейс по-прежнему будет работать на 15672.
  3. Указываем новый порт на всех нодах безвременно почившего кластера и запускаем их.

При запуске произойдёт перестройка индексов и в подавляющем большинстве случаев все данные восстанавливаются в полном объёме. К сожалению, случаются сбои в результате которых приходится физически удалять все сообщения с диска, оставляя только конфигурацию – в папке с базой удаляются директории msg_store_persistent, msg_store_transient, queues (для версии 3.6) или msg_stores (для версии 3.7).

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

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

Для удобства управления и обновления кроликов используется не готовая сборка в rpm, а разобранный с помощью cpio и переконфигурированный (изменили пути в скриптах) кролик. Основное отличие: не требует для установки/настройки прав root, не устанавливается в систему (пересобранный кролик отлично запаковывается в tgz) и запускается от любого пользователя. Такой подход позволяет гибко проводить обновление версий (если это не требует полной остановки кластера – в таком случае просто переключаем на резервный кластер и обновляемся, не забывая указать смещённый порт для работы). Возможен даже запуск нескольких экземпляров RabbitMQ на одной машине – для тестов вариант очень удобен – можно развернуть уменьшенную архитектурную копию боевого зоопарка.

В результате шаманства с cpio и путями в скриптах получили вариант сборки: две папки rabbitmq-base (в оригинальной сборке – папка mnesia) и rabbimq-main – сюда поместил все необходимые скрипты и самого кролика и эрланга.

В rabbimq-main/bin – симлинки к скриптам кролика и эрланга и скрипт слежения за кроликом (описание ниже).

В rabbimq-main/init.d – скрипт rabbitmq-server через который происходит старт/стоп/ротирование логов; в lib – сам кролик; в lib64 – erlang (используется урезанная, только для работы кролика, версия эрланга).

Получившуюся сборку крайне просто обновлять при выходе новых версий – добавить содержимое rabbimq-main/lib и rabbimq-main/lib64 из новых версий и заменить симлинки в bin. Если обновление затрагивает и управляющие скрипты – достаточно просто изменить в них пути на наши.

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

Поскольку падения кроликов событие хоть и редкое, но происходящее, требовалось воплотить механизм слежения за их самочувствием — поднятие в случае падения (с сохранением логов причин падения). Падение ноды в 99% случаев сопровождается записью в лог, даже kill и тот оставляет следы, это позволило реализовать с помощью простого скрипта слежение за состоянием кроля.

Для версий 3.6 и 3.7 скрипт немного отличается в силу отличий записей в логах.

Для версии 3.6
#!/usr/bin/python
import subprocess
import os
import datetime
import zipfile

def LastRow(fileName,MAX_ROW=200):
    with open(fileName,'rb') as f:
        f.seek(-min(os.path.getsize(fileName),MAX_ROW),2)
        return (f.read().splitlines())[-1]

if os.path.isfile('/data/logs/rabbitmq/startup_log'):
    if b'FAILED' in LastRow('/data/logs/rabbitmq/startup_log'):
        proc = subprocess.Popen("ps x|grep rabbitmq-server|grep -v 'grep'", shell=True, stdout=subprocess.PIPE)
        out = proc.stdout.readlines()
        if str(out) == '[]':
            cur_dt=datetime.datetime.now()
            try:
                os.stat('/data/logs/rabbitmq/after_crush')
            except:
                os.mkdir('/data/logs/rabbitmq/after_crush')
            z=zipfile.ZipFile('/data/logs/rabbitmq/after_crush/repair_log'+'-'+str(cur_dt.day).zfill(2)+str(cur_dt.month).zfill(2)+str(cur_dt.year)+'_'+str(cur_dt.hour).zfill(2)+'-'+str(cur_dt.minute).zfill(2)+'-'+str(cur_dt.second).zfill(2)+'.zip','a')
            z.write('/data/logs/rabbitmq/startup_err','startup_err')
            proc = subprocess.Popen("~/rabbitmq-main/init.d/rabbitmq-server start", shell=True, stdout=subprocess.PIPE)
            out = proc.stdout.readlines()
            z.writestr('res_restart.log',str(out))
            z.close()
            my_file = open("/data/logs/rabbitmq/run.time", "a")
            my_file.write(str(cur_dt)+"\n")
            my_file.close()



Для 3.7 изменяются только две строки
if (os.path.isfile('/data/logs/rabbitmq/startup_log')) and (os.path.isfile('/data/logs/rabbitmq/startup_err')):
    if ((b'  OK  ' in LastRow('/data/logs/rabbitmq/startup_log')) or (b'FAILED' in LastRow('/data/logs/rabbitmq/startup_log'))) and not (b'Gracefully halting Erlang VM' in LastRow('/data/logs/rabbitmq/startup_err')):



Заводим в crontab учётной записи под которой будет работать кролик (по умолчанию rabbitmq) выполнение этого скрипта (имя скрипта: check_and_run) каждую минуту (для начала просим админа выдать учётке права на использование crontab, ну а если права root есть – делаем сами):
*/1 * * * * ~/rabbitmq-main/bin/check_and_run

Второй момент использования пересобранного кроля — ротирование логов.

Поскольку мы не завязываемся на logrotate системы – используем функционал, предоставленный разработчиком: скрипт rabbitmq-server из init.d (для версии 3.6)
Внеся небольшие изменения в rotate_logs_rabbitmq()
Добавляем:

    find ${RABBITMQ_LOG_BASE}/http_api/*.log.*  -maxdepth 0 -type f ! -name "*.gz" | xargs -i gzip --force {}
    find ${RABBITMQ_LOG_BASE}/*.log.*.back  -maxdepth 0 -type f | xargs -i gzip {}
    find ${RABBITMQ_LOG_BASE}/*.gz -type f -mtime +30 -delete
    find ${RABBITMQ_LOG_BASE}/http_api/*.gz -type f -mtime +30 -delete

Результат запуска скрипта rabbitmq-server с ключом rotate-logs: логи сжимаются gzip-ом, и хранятся только за последние 30 дней. http_api – путь куда кролик складывает логи http – настраивается в файле конфигурации: {rabbitmq_management, [{rates_mode, detailed},{http_log_dir, path_to_logs/http_api"}]}

Заодно обращаю внимание на {rates_mode, detailed} – опция несколько увеличивает нагрузку, зато позволяет увидеть на WEB интерфейсе (и соответственно получить через API) информацию о том, кто публикует сообщения в эксченджи. Информация крайне нужная, т.к. все подключения идут через балансировщик – мы увидим только IP самих балансировщиков. А если озадачить все Подсистемы, работающие с кроликом, чтобы они заполняли параметры «Client properties» в свойствах своих подключений к кроликам, то можно будет на уровне коннектов детально получать информацию кто именно, куда и с какой интенсивностью публикует сообщения.

С выходом новых версий 3.7 произошёл полный отказ от скрипта rabbimq-server в init.d. С целью облегчения эксплуатации (однообразие команд управления независимо от версии кролика) и более плавного перехода между версиями, в пересобранном кролике мы продолжаем использование данного скрипта. Правда опять: немного изменим rotate_logs_rabbitmq(), поскольку в 3.7 поменялся механизм именования логов после ротирования:

    mv ${RABBITMQ_LOG_BASE}/$NODENAME.log.0 ${RABBITMQ_LOG_BASE}/$NODENAME.log.$(date +%Y%m%d-%H%M%S).back
    mv ${RABBITMQ_LOG_BASE}/$(echo $NODENAME)_upgrade.log.0 ${RABBITMQ_LOG_BASE}/$(echo $NODENAME)_upgrade.log.$(date +%Y%m%d-%H%M%S).back
    find ${RABBITMQ_LOG_BASE}/http_api/*.log.*  -maxdepth 0 -type f ! -name "*.gz" | xargs -i gzip --force {}
    find ${RABBITMQ_LOG_BASE}/*.log.* -maxdepth 0 -type f ! -name "*.gz" | xargs -i gzip --force {}
    find ${RABBITMQ_LOG_BASE}/*.gz -type f -mtime +30 -delete
    find ${RABBITMQ_LOG_BASE}/http_api/*.gz -type f -mtime +30 -delete

Теперь осталось только внести в crontab задание по ротированию логов – например каждый день в 23-00:
00 23 * * * ~/rabbitmq-main/init.d/rabbitmq-server rotate-logs

Перейдём к задачам, которые требуется решать в рамках эксплуатации «кроличьей фермы»:

  1. Манипуляции с сущностями кроликов — создание/удаление сущностей кролика: эксченджей, очередей, биндов, шовелов, пользователей, политик. Причем делать это абсолютно идентично на всех Кластерах Контура.
  2. После переключения на / с резервного Кластера требуется перенести сообщения, которые на нём остались на текущий Кластер.
  3. Создание резервных копий конфигураций всех Кластеров всех Контуров
  4. Полная синхронизация конфигураций Кластеров в рамках Контура
  5. Производить остановку/запуск кроликов
  6. Производить анализ текущих потоков данных: все ли сообщения ходят и если ходят, то куда надо или…
  7. Найти и отловить проходящие сообщения по каким-либо критериям

Эксплуатация нашего зоопарка и решение озвученных задач средствами поставляемого штатного rabbitmq_management плагина возможна, но крайне неудобна, именно поэтому была разработана и реализована оболочка для управления всем многообразием кроликов.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 30

    0
    Поскольку кролик горизонтально не масштабируется (производительность кластера равна производительности самого медленного сервера)

    Разве? Мне казалось, что он масштабируется при помощи шардирования, то есть у вас может быть, например, 5 нод и 3 очередей, причем для каждой очереди две копии. В таком случае добавление еще одной ноды вроде как может помочь. Или я не прав?

      0
      Масштпбируется раббит настройками репликации очередей
        0
        В нашем случае недостаточно, т.к. необходим баланс производительности/отказоустойчивости/доступности. Причём, скорость работы не является самым важным параметром этого баланса. По приоритетам получается Доступность-отказоустойчивость-скорость работы.
        Можно политиками поиграть и приземлять разные очереди на разные ноды, но это сильно снизит доступность сервиса в случае сбоев. Неоднократно происходили ситуации, когда по сети доступна оставалась только одна нода, и она обеспечивала работу, пока остальные не вернулись в строй. Причем со стороны подсистем это происходило незаметно.
          0
          в данной фразе идет недосказанность — кролик горизонтально не масштабируется при используемых у нас политиках отказоустойчивости. Описание политик дано ниже в статье (ha-mode, lazy)
          +3
          RabbitMQ при больших нагрузках — это боль. Точнее, для достижения нормального RPS нужно уж больно много ресурсов/нод/CPU.
          Перешли на Redis Cluster Streams и счастливы.
            0
            Опять же повторюсь: зависит от того, какие задачи предполагается решать. Если нужно обеспечить максимальную скорость передачи сообщений — это одно, но опять: сильно зависит от размера каждого сообщения. А если нужно обеспечить непрерывный доступный маршрут — это уже совсем другое.
            +2
            Kafka рассматривали? Геораспределенный кролик — это прмя боль.
              0
              В данном случае нельзя заменить одно на другое. Нам необходим именно транспорт, а не хранилище. Кафка это хранилище, но не маршрутизируемый на лету транспорт. При всём желании, передать асинхроном гигабайты сообщений с одного края страны на другой, кафкой будет минимум затруднительно. Каждое решение хорошо для своих задач. На мой взгляд, их вообще сравнивать можно только в ситуациях локального применения, когда не используется сильно распределённые потоки данных.
                0
                Ну, Вы прям совсем не правы.
                Кафка — прекрасно работает в качестве транспорта.
                При всём желании, передать асинхроном гигабайты сообщений с одного края страны на другой, кафкой будет минимум затруднительно.

                а кроликом нормально… ну-ну.
                  0
                  Кафка это решение в основном для хранения сообщений, а кролик для транспортировки. Разные подходы для разных целей. Кроликом вполне нормально. Многолетняя успешная эксплуатация это подтверждает.
              +1

              Спасибо за статью! Мы как раз сейчас на распутье, проблемы с кроликом начались, когда пришлось делать кластер. Разваливается достаточно часто, а процесс восстановления такое себе удовольствие. Сейчас как раз думаем, городить ли велосипеды типа вашего, либо переходить на что-то другое.

                0
                Смотря по какой причине разваливается. У нас падения были ровно до тех пор, пока lazy не появилось. Сейчас у нас пока до конца не решена проблема network partition, но уже есть мысли как решить:
                1. На уровне хапов проверку доступности сделать через lua скрипты — при получении через API overview получить имя ноды, с которой идут расхождения.
                2. заблокировать поток сообщений на неё.
                3. Отправить команду на рестарт.
                0

                Расскажите, как сообщения переносите на резервный кластер.

                  0
                  в конце статьи приведена ссылка на гитхаб. Там документ с описанием работы тулзы для администрирования. В том числе и перенос сообщений.
                  но если кратко — shovel для всех не пустых очередей.
                  главное допущение: считается, что порядок сообщений в очереди не важен. Либо если он критичен, то в виду уже произошедшей аварии потеря порядка исправляется на другом уровне.
                  0
                  По моему опыту хапрокси не очень хорошо взаимодействует с раббит кластером, и одного check было недостаточно, сокет ноды может отвечать, но нода может быть нерабочей. Сейчас на нодах раббита используется keepalived со скриптом проверки статуса ноды и привязкой вирт. айпи, плюс отказ от хапрокси позволил повысить пропускную способность кластера.
                    0
                    Можете рассказать — что за «сложная» логика инкапсулирован в скрипт «проверки статуса ноды»?
                      0
                      Поскольку кролик работает внутри виртуальной машины любое его падение, даже kill успевает вызвать запись в лог. Вот эти записи и анализируем. Других вариантов быстро не нашлось, а данное решение уже несколько лет успешно выполняет свою работу на нескольких сотнях серверов. Оно не претендует на какую-то красоту или элегантность. Простое как лом и такое же эффективное.
                      0
                      А можете мне объяснить, почему haproxy, а не предположим consul dns + consul health check? Их тогда не было или они чем-то хуже?
                        0
                        Наличие или отсутствие хапа у нас никак не сказывается на пропускной способности кластера: Нагрузка по ресурсам на хапах 10-15%, а нагрузка на сетевой интерфейс всего 2-4 гигабита из 10. Входящий хап это самое ненагруженное (по сети) место. Самая большая сетевая нагрузка как раз между нодами кластера. У нас кролик настроен именно для high availability и как раз синхронизационный трафик даёт основную сетевую нагрузку. При входящем 2-4 гигбита между нодами бегает 4-6 гигабит. Хап очень неплохо размазывает входящую нагрузку на ноды: все получаются равномерно нагружены.
                          0
                          не было подобных проблем? RabbitMQ and HAProxy: a timeout issue – Tony's press
                          deviantony.wordpress.com ›…
                            0
                            www.google.com/url?sa=t&source=web&rct=j&url=https://deviantony.wordpress.com/2014/10/30/rabbitmq-and-haproxy-a-timeout-issue/amp/&ved=2ahUKEwiagrmNobjfAhVphaYKHdJyB-8QFjAAegQIBBAB&usg=AOvVaw17dBygIt5ZMifs9AkEhA1q&ampcf=1
                              0
                              Обращаю внимание на наш конфиг хапа — значения таймаутов.
                                0
                                Сейчас уже не вспомню, пробовали разные варианты, ставили 3 часа таймаут тоже и получали зависшие сессии периодически
                                  0
                                  У нас зависших сессий не наблюдалось. Проблемы возникали только с подсистемой, у которой heart bit interval выставлен на 0 был. Регулярно получали разрывы коннектов.
                          0
                          А почему выбрали RabbitMQ, а не EMQ X Enterprise?
                            0
                            на момент проработки архитектуры (2014 год) был выбран RabbitMQ по соотношению цена/качество/возможности
                            0
                            Рекомендую ещё задать лимиты на пользователя и vhost на количество подключений. Без этих лимитов неосторожное приложение может отставить от кролика только шкурку…
                              0
                              Опыт эксплуатации показывает несколько иную картину:
                              Несколько раз были сбои в работе подсистем и они начинали со страшной силой плодить входящие каналы: Кролик выжил при 80000 каналов. Веб-морда тормозила нещадно, но падать отказался.
                              Кролик вполне спокойно держит нагрузку например такую: 9000 коннектов/9000 каналов, скорость обмена 5-8 тыс сообщений, объём 50-200 Мегабайт/секунду.
                              Мы пошли по пути:
                              Лучше отладить код подсистем, работающих с кроликом, нежели городить костыли, которые ограничат транспорт.
                                0
                                это не костыли, это настройка лимитов кролика. в веб морде вкладка limit
                              0
                              Я имею ввиду костыль — как средство ограничения. Ограничить можно что угодно и где угодно. Причем ещё на уровне хапа. Но решение у нас всё таки архитектурное. Если неожиданно наплодилось нештатное количество коннектов — это всё отображается на мониторинге и принимаются меры. Устраняется баг в коде или изменяется архитектурная часть.

                              Only users with full accounts can post comments. Log in, please.