Масштабирование RabbitMQ: руководство по оптимизации топологий кластера
Проектируйте кластеры RabbitMQ, которые масштабируются без путаницы между кластеризацией, репликацией и пропускной способностью.
Масштабирование RabbitMQ: руководство по оптимизации топологий кластера
Масштабирование RabbitMQ начинается с одного неприятного факта: кластер — это не волшебный большой брокер. Это набор брокеров, которые обмениваются метаданными и, в зависимости от типа очереди, могут реплицировать данные очереди. Если одна очередь перегружена, добавление узлов вокруг неё может повысить доступность, но не сделает эту очередь быстрее.
Это различие спасает от множества плохих проектов. Я видел команды, которые добавляли два узла в загруженное развёртывание RabbitMQ, ничего не перемещали, не меняли расположение очередей и удивлялись, почему та же очередь всё ещё забивается каждый день. Лидер очереди оставался на том же узле. Те же потребители выполняли ту же работу. В кластере было больше машин, но узкое место не сдвинулось.
Топология кластера RabbitMQ в основном сводится к решению, где живут очереди, сколько копий важных сообщений вам нужно и какой уровень сбоев вы можете выдержать до падения пропускной способности. Правильный ответ для краткосрочного конвейера метрик не совпадает с правильным ответом для платежей, обработки заказов или событий аудита.
Что на самом деле разделяет кластеризация
Узлы RabbitMQ в кластере разделяют определения: виртуальные хосты, пользователи, разрешения, обменники, очереди, привязки, политики и метаданные времени выполнения, необходимые для работы кластера. Производитель, подключённый к одному узлу, может публиковать в обменник, лидер очереди которого находится на другом узле. Потребитель может подключиться к другому узлу, отличному от того, на котором размещена очередь.
Это не означает, что каждое сообщение существует везде.
Классические очереди имеют лидера на одном узле. Кворумные очереди имеют лидера и реплики. Потоки имеют свою модель репликации. Если лидер очереди удалён от большинства клиентов, использующих её, RabbitMQ приходится передавать трафик через межкластерное соединение. Это нормально в умеренных количествах. Это становится дорого, когда каждый издатель подключается к узлу A, все горячие очереди находятся на узле B, а каждый потребитель подключается к узлу C.
Простое первое правило работает хорошо: подключайте приложения к узлам, которые находятся рядом с используемыми ими очередями, или разместите балансировщик нагрузки перед кластером и убедитесь, что лидеры очередей разумно сбалансированы. Не предполагайте, что циклические клиентские подключения создают циклическую нагрузку на очереди.
Предпочитайте три узла, прежде чем проявлять креативность
Для большинства производственных кластеров RabbitMQ три узла в одной группе с низкой задержкой или зоне доступности — это чистая отправная точка. Это даёт кворумным очередям модель большинства, которая может пережить отказ одного узла, и сохраняет координацию кластера достаточно простой для анализа во время инцидента.
Кластеры из двух узлов выглядят дешевле, но они неудобны для реплицируемых очередей. В системах на основе кворума требуется большинство. Если один из двух узлов исчезает, большинства нет. В некоторых распределённых системах можно добавить третий узел-свидетель, но для RabbitMQ обычно проще и надёжнее запускать три реальных узла с достаточной ёмкостью диска и сети.
Пять узлов могут иметь смысл, когда у вас много очередей, нужно больше вариантов размещения или вы хотите распределить нагрузку по большему количеству машин. Это также увеличивает объём межкластерного общения и операционную поверхность. Прежде чем переходить с трёх на пять, проверьте, решаете ли вы проблему насыщения узла или проблему проектирования очередей. Если одна очередь горячая, больше узлов само по себе не разделит работу этой очереди.
Кворумные очереди предназначены для реплицированной надёжности, а не для бесплатной скорости
Для новых высокодоступных рабочих нагрузок кворумные очереди обычно являются правильным выбором по умолчанию, когда важна долговечность сообщений. Они реплицируют сообщения с использованием протокола консенсуса. Кворумная очередь с тремя участниками может продолжать работу, если один участник недоступен, при условии, что большинство остаётся здоровым.
Компромисс — стоимость записи. Опубликованное постоянное сообщение должно быть реплицировано достаточному количеству участников, прежде чем оно будет считаться безопасно принятым. Это именно то, что нужно для важной работы, но это не тот же профиль производительности, что и у временной классической очереди.
Объявите кворумную очередь с помощью аргумента или политики, в зависимости от того, как ваше приложение управляет топологией:
rabbitmqadmin declare queue name=orders durable=true arguments='{"x-queue-type":"quorum"}'
Для политик ограничивайте их тщательно. Не конвертируйте случайно каждую очередь в виртуальном хосте в кворумную только потому, что широкий шаблон совпал с .*. Хорошее имя политики и узкий префикс очереди — это скучно в лучшем смысле:
rabbitmqctl set_policy qq-orders '^orders\.' '{"queue-type":"quorum"}' --apply-to queues
Если вы мигрируете с классических зеркальных очередей, относитесь к этому как к миграции, а не как к переключению флага. Классические зеркальные очереди и кворумные очереди ведут себя по-разному в отношении порядка, ядовитых сообщений, использования памяти и отказоустойчивости. Создайте новый тип очереди, направьте контролируемую часть трафика, следите за подтверждениями и задержкой потребителей, затем переместите остальное.
Классические очереди всё ещё имеют место
Классические очереди по-прежнему полезны для рабочих нагрузок, где репликация не требуется, где сообщения временны или где очередь локальна для сервиса и может быть восстановлена из другого источника. Они также подходят для высокообъёмных низкоценных событий, где потеря нескольких сообщений во время сбоя узла допустима.
Используйте классические очереди осознанно. Если классическая очередь долговечна и получает постоянные сообщения, эти сообщения хранятся на узле, на котором размещена очередь. Если этот узел выходит из строя, очередь недоступна до его возвращения. Это может быть нормально для фоновой задачи синхронизации. Обычно это не нормально для видимого клиенту состояния заказа.
Для длинных бэклогов подумайте, должна ли рабочая нагрузка быть потоком или другой системой хранения. RabbitMQ может содержать очереди, но очередь с миллионами старых сообщений часто является сигналом того, что потребители недостаточно мощны, нижестоящие системы выходят из строя или бизнес-процесс требует семантики воспроизведения, а не семантики очереди.
Установите границы задержки вокруг кластера
Кластеризация RabbitMQ ожидает низкую задержку и надёжные сетевые соединения. Растягивание одного кластера на удалённые регионы обычно является плохим компромиссом. Межузловой трафик становится медленнее, отказоустойчивость становится менее предсказуемой, а разделение сети может быть более разрушительным, чем сбой, которого вы пытались избежать.
Практичный дизайн — один кластер RabbitMQ на регион с маршрутизацией на уровне приложения или федерацией/шлюзом между регионами, когда требуется межрегиональное перемещение. Это обеспечивает быструю локальную публикацию и потребление. Это также делает зоны отказа чёткими: если регион A нездоров, регион B не втягивается в ту же проблему членства в кластере.
Мульти-AZ внутри одного региона — это другое. Если задержка между зонами низкая и стабильная, три узла в трёх зонах могут работать хорошо. Проверьте это под реальной нагрузкой. Тот факт, что облачный провайдер называет что-то зоной доступности, не говорит вам, как ваши размеры сообщений, подтверждения и кворумные очереди будут вести себя в час пик.
Балансируйте лидеров очередей, а не только узлы
Кластер может выглядеть сбалансированным на графике CPU, но быть сильно перекошенным на уровне очередей. Один узел может владеть лидерами самых загруженных очередей, в то время как другие в основном содержат тихие реплики.
Проверьте размещение очередей:
rabbitmqctl list_queues name type leader members messages_ready messages_unacknowledged
Если один узел владеет большинством горячих лидеров, переместите или перебалансируйте очереди с помощью поддерживаемых инструментов RabbitMQ для вашей версии и типа очереди. Для кворумных очередей важны размещение участников и расположение лидера. Для классических очередей размещение мастера очереди имеет значение в старой терминологии, хотя в новых версиях используется более последовательный язык лидера.
Хорошая топология распределяет несвязанные горячие очереди по узлам. Например, email.send, image.resize и billing.capture не должны все возглавляться одним узлом, если каждый имеет интенсивный трафик. Если billing.capture — единственная горячая очередь, разделите её по реальному бизнес-шарду, например, по группе продавцов или региону, только если потребители могут безопасно обрабатывать эти шарды независимо.
Проектируйте поведение клиентских подключений
Размещение клиентских подключений является частью топологии. Если каждое приложение подключается к первому результату DNS навсегда, один узел может нести большую часть клиентского трафика, даже если очереди распределены по кластеру. Балансировщик нагрузки может помочь, но он должен использовать проверки здоровья, которые понимают, доступен ли узел для трафика AMQP.
Поддерживайте долгоживущие соединения. RabbitMQ может обрабатывать много соединений, но частая смена соединений потребляет CPU, память, файловые дескрипторы и накладные расходы TLS. Веб-запрос не должен открывать новое соединение AMQP, публиковать одно сообщение и закрывать его. Используйте пул соединений или каналов, подходящий для клиентской библиотеки.
Также решите, что делают клиенты во время сбоя узла. Хорошие клиенты переподключаются с задержкой, повторно открывают каналы, повторно объявляют частную топологию при необходимости и осторожно возобновляют подтверждения или потребление. Плохие клиенты переподключаются в плотных циклах и превращают перезапуск узла в шквал подключений.
Для потребителей думайте о локальности, но избегайте чрезмерной привязки. Подключение потребителя к тому же узлу, что и лидер его очереди, может уменьшить межузловой трафик, но лидеры очередей могут перемещаться после сбоев. Потребитель должен пережить это без ручных изменений.
Разделы — это операционные события, а не просто настройки
Разделы сети — это то, где проверяются диаграммы кластера. RabbitMQ имеет режимы обработки разделов, но ни одна настройка не устраняет необходимость в чётком операционном решении. Если две стороны кластера не могут общаться, вы должны решить, что важнее для этой рабочей нагрузки: доступность или согласованность.
Кворумные очереди требуют большинства реплик. В этом суть: меньшинство не должно продолжать принимать записи, которые не могут быть безопасно согласованы. Это может удивить команды, которые ожидали, что каждый выживший узел останется доступным для записи. Планируйте это. Размещайте участников кворумной очереди там, где большинство может пережить сбои, которые вас волнуют.
Не распределяйте трёхузловую кворумную очередь по трём удалённым регионам и не ожидайте гладкого поведения при обычной задержке интернета. Кворум будет настолько приятным, насколько приятна сеть между участниками. Низкая задержка и низкие потери пакетов — это требования к ёмкости, а не приятные дополнения.
Проводите учения по разделению в непроизводственной среде. Блокируйте трафик между узлами, наблюдайте, какие очереди остаются доступными, следите за переподключением клиентов и записывайте шаги восстановления. Первый раз, когда вы узнаете о поведении при разделении, не должен быть во время реального сетевого инцидента.
Масштабируйте потребителей перед масштабированием брокеров
Когда симптом — растущая очередь, брокер не всегда является узким местом. Часто потребители просто медленнее, чем издатели. Прежде чем добавлять узлы RabbitMQ, проверьте загрузку потребителей, количество неподтверждённых сообщений, время обработки и задержку нижестоящих систем.
Если сообщения готовы, но не подтверждены, RabbitMQ имеет ожидающие сообщения, и потребители не забирают их достаточно быстро. Добавьте потребителей, исправьте предварительную выборку или устраните задержки нижестоящих систем. Если сообщения в основном неподтверждены, потребители уже получили работу и тратят слишком много времени на её подтверждение. Добавление узлов брокера не ускорит эти обработчики.
Предварительная выборка имеет значение. Предварительная выборка 500 на медленном работнике может скрыть бэклог внутри процессов потребителя. Предварительная выборка 1 на быстром локальном работнике может тратить время на круговые запросы. Начните с небольшого значения, измерьте сквозную задержку и память потребителя, затем отрегулируйте.
Следите за скучными лимитами
Планы масштабирования часто говорят о топологии и забывают о файловых дескрипторах, дисковых тревогах, тревогах памяти, смене подключений и количестве каналов. RabbitMQ чувствителен ко всем ним.
Для каждого узла отслеживайте используемую память, свободное место на диске, файловые дескрипторы, сокеты, память процессов очередей, скорость сообщений, задержку подтверждений и загрузку планировщика Erlang. На стороне клиента отслеживайте циклы переподключения и скорость создания каналов. Сервис, который открывает новое соединение для каждой публикации, может навредить кластеру задолго до того, как объём сообщений станет впечатляющим.
Используйте долгоживущие соединения и каналы, где ваша клиентская библиотека их поддерживает. Включите лимиты соединений и настройки heartbeat в проект, а не в паническое изменение во время сбоя.
Топология, которая обычно работает
Для типичного бизнес-приложения я бы начал с трёх узлов RabbitMQ в одном регионе, распределённых по зонам, если сеть хорошая. Используйте кворумные очереди для важных долговечных рабочих процессов. Используйте классические очереди для временной работы, где поведение при сбое приемлемо. Держите издателей и потребителей рядом с кластером. Используйте балансировщик нагрузки для доступа клиентов, но проверяйте баланс лидеров очередей, а не предполагайте, что балансировщик решил размещение брокеров.
Затем протестируйте сложные случаи: убейте один узел, приостановите группу потребителей, заполните очередь, замедлите диск и перезапустите издателя. Масштабирование RabbitMQ — это не столько самая красивая диаграмма, сколько знание того, что происходит, когда диаграмма находится под нагрузкой.