Лучшие практики управления памятью и высокой пропускной способностью RabbitMQ
Настройте память, дисковые лимиты, очереди и потребителей RabbitMQ так, чтобы высокая пропускная способность не превращалась в нагрузку на брокер.
Лучшие практики управления памятью и высокой пропускной способностью RabbitMQ
RabbitMQ может обрабатывать множество сообщений, но он не рад, когда память становится планом переполнения. Брокеру нужна память для соединений, каналов, процессов очередей, метаданных сообщений, неподтвержденных доставок, плагинов, метрик и самой среды выполнения Erlang. Если издатели работают быстрее потребителей достаточно долго, вопрос перестает быть "Как быстро может работать RabbitMQ?" и становится "Где сначала проявится нагрузка?"
Хорошее управление памятью в основном заключается в том, чтобы сделать эту нагрузку видимой и контролируемой. Вы хотите, чтобы RabbitMQ применял обратное давление до того, как операционная система начнет убивать процессы. Вам также нужно достаточно дискового пространства, чтобы постоянные сообщения и внутреннее состояние могли быть безопасно записаны.
Начните с сигнала тревоги по памяти, но не считайте его волшебной настройкой
RabbitMQ использует vm_memory_high_watermark, чтобы решить, когда использование памяти слишком велико. Когда порог превышен, RabbitMQ поднимает сигнал тревоги по памяти и блокирует издателей до тех пор, пока память не снизится. Такое поведение является преднамеренным. Заблокированный издатель раздражает; брокер с нехваткой памяти — хуже.
Распространенная отправная точка — относительный порог около 40% доступной памяти:
vm_memory_high_watermark.relative = 0.40
Это число не является священным. Небольшой виртуальной машине с другими сервисами может потребоваться более низкий порог. Выделенный брокер с хорошо изученными рабочими нагрузками может допускать другое значение. Смысл в том, чтобы оставить место для кэша страниц ОС, активности файловой системы, агентов мониторинга и всплесков, которые происходят до того, как ваши графики успеют среагировать.
Вы также можете установить абсолютное значение, что часто проще в контейнерах или средах, где "доступная память" может быть неправильно понята:
vm_memory_high_watermark.absolute = 6GiB
Используйте один стиль, соответствующий тому, как развернут узел. В контейнерах проверьте, что RabbitMQ видит ожидаемый вами лимит контейнера, а не полную память хоста. Порог, основанный на неправильном общем объеме памяти, — это тихий способ создать инцидент в production.
Лимит свободного диска — это еще одна предохранительная планка
Настройка защиты диска RabbitMQ — disk_free_limit. Она основана на свободном месте, а не на проценте disk_high_watermark. Когда свободное дисковое пространство падает ниже настроенного лимита, RabbitMQ поднимает сигнал тревоги по диску и блокирует издателей.
Для многих production узлов абсолютный лимит понятнее относительного:
disk_free_limit.absolute = 20GB
Правильное значение зависит от размера сообщений, скорости публикации, персистентности, ротации логов и того, как быстро ваша команда может добавить место или очистить очереди. Узел, получающий большие постоянные сообщения, нуждается в гораздо большем запасе, чем узел, обрабатывающий крошечные временные события.
Не устанавливайте это значение слишком маленьким, просто чтобы избежать сигналов тревоги. Дисковые сигналы тревоги существуют для защиты брокера. Если на диске закончатся свободные байты, вы можете столкнуться с неудачными записями, нарушением доступности и гораздо более сложным восстановлением.
Поймите, что на самом деле использует память
Когда память растет, избегайте догадок. RabbitMQ предоставляет разбивку памяти через интерфейс управления и CLI:
rabbitmq-diagnostics memory_breakdown
rabbitmqctl status
rabbitmqctl list_queues name type messages_ready messages_unacknowledged memory
Наиболее полезное первое разделение — это готовые сообщения (ready) против неподтвержденных (unacknowledged). Готовые сообщения все еще ждут в очереди. Неподтвержденные сообщения были доставлены потребителям и ожидают basic.ack, basic.nack или закрытия канала.
Если количество готовых сообщений растет, производители опережают потребителей или потребители не подключены. Если количество неподтвержденных сообщений растет, потребители берут сообщения, но не завершают их. Это разные проблемы. Повышение лимитов памяти только выиграет время, если дисбаланс потока сохранится.
Большие сообщения заслуживают особого внимания. Очередь со скромным количеством сообщений все равно может потреблять много памяти, если каждое сообщение имеет большую полезную нагрузку. Если сообщения содержат изображения, документы или большие JSON-блоки, рассмотрите возможность хранения полезной нагрузки в другом месте и отправки ссылки через RabbitMQ. Брокеры сообщений обычно лучше подходят для перемещения уведомлений о работе, чем для работы в качестве хранилищ больших двоичных объектов.
Настройте prefetch, чтобы остановить скрытые отставания
Prefetch контролирует, сколько неподтвержденных сообщений RabbitMQ может доставить потребителю. Высокое значение prefetch может улучшить пропускную способность для быстрых потребителей, но оно также перемещает отставание из очереди в память потребителя.
Например, десять потребителей с prefetch_count=500 могут удерживать до 5000 неподтвержденных сообщений вне очереди готовых. Если каждое сообщение большое или медленно обрабатывается, это может создать нагрузку на память и неравномерную задержку. Новое сообщение может ждать позади сотен старых сообщений, уже находящихся внутри одного медленного потребителя.
Начните со значения prefetch, соответствующего работе. Для медленных вызовов API или записи в базу данных попробуйте небольшое число, например 5 или 10, и увеличивайте только после измерений. Для очень быстрой локальной работы CPU более высокие значения могут помочь. Для строгой справедливости prefetch_count=1 иногда является правильным компромиссом, даже если общая пропускная способность ниже.
Ключ в том, чтобы измерить время обработки и задержку подтверждения. RabbitMQ не может завершить сообщения за вас. Он может только ограничить количество незавершенной работы, которую он раздает.
По возможности держите очереди короткими
RabbitMQ работает лучше всего, когда сообщения проходят через систему, а не сидят в очередях часами. Очередь, которая обычно около нуля и иногда всплескивает, — это здорово. Очередь, которая растет весь день и очищается ночью, — это предупреждение о пропускной способности. Очередь, которая только растет, — это сбой в замедленной съемке.
Для длительных отставаний решите, ожидается ли отставание. Если оно ожидается, используйте тип очереди и дизайн хранилища, которые подходят. Кворумные очереди хороши для надежных реплицированных рабочих нагрузок. Потоки могут подойти для рабочих нагрузок типа воспроизведения. Классические очереди могут быть хороши для более простой временной работы. Если отставание не ожидается, исправьте потребителей или нижестоящие сервисы, прежде чем настраивать память брокера.
Устанавливайте TTL сообщений только тогда, когда просроченная работа действительно бесполезна. TTL не заменяет пропускную способность. Он может защитить систему от обработки устаревших сообщений, но также может скрыть потерю данных, если применяется небрежно.
Очереди мертвых писем помогают отделить ядовитые сообщения от нормального потока. Без стратегии мертвых писем одна плохая полезная нагрузка может повторяться вечно, потреблять ресурсы и делать очередь медленнее, чем она есть на самом деле.
Персистентность меняет бюджет пропускной способности
Устойчивые очереди и постоянные сообщения — правильный выбор, когда сообщения должны пережить перезапуск брокера. Они также требуют записи на диск. Подтверждения издателя добавляют сигнал надежности, чтобы издатели знали, когда брокер принял на себя ответственность за сообщение.
Медленный паттерн — публиковать одно постоянное сообщение, синхронно ждать его подтверждения, затем публиковать следующее. Это просто и безопасно, но пропускная способность будет ограничена временем кругового пути и поведением диска. Лучший паттерн — использовать асинхронные подтверждения издателя или небольшие пакеты, при этом обрабатывая отрицательные подтверждения и тайм-ауты.
Избегайте транзакций AMQP для высокопроизводительной публикации, если у вас нет очень конкретной причины. Подтверждения издателя — это обычный инструмент надежности для издателей RabbitMQ.
Дайте RabbitMQ скучную инфраструктуру
RabbitMQ любит предсказуемые машины: достаточно памяти, быстрые диски для постоянных рабочих нагрузок, стабильная задержка сети и отсутствие шумных соседей, ворующих CPU. Если брокер делит хост с базой данных, процессором логов и случайными cron-задачами, настройка памяти становится гаданием.
Используйте SSD или NVMe для постоянных высокопроизводительных очередей. Следите за задержкой диска, а не только за его использованием. Диск может показывать умеренную пропускную способность и при этом иметь болезненную задержку записи. В облачных средах предоставленные IOPS и кредиты burst могут иметь большее значение, чем метка диска.
Ограничьте смену соединений. Долгоживущие соединения и каналы дешевле, чем открытие новых для каждой публикации. Если приложение создает тысячи короткоживущих соединений, использование памяти и файловых дескрипторов может расти, даже если скорость сообщений обычная.
Контейнеры требуют явного мышления
RabbitMQ хорошо работает в контейнерах, но лимиты памяти должны быть четкими. Порог памяти брокера полезен только в том случае, если он рассчитан на лимит, который контейнер может фактически использовать. Если RabbitMQ думает, что у него есть память хоста, но среда выполнения контейнера обеспечивает меньший лимит, контейнер может быть убит до того, как собственное поведение RabbitMQ по сигнализации защитит его.
Установите лимит памяти контейнера, затем установите абсолютный порог RabbitMQ, который оставляет место внутри этого лимита:
vm_memory_high_watermark.absolute = 3GiB
Например, в контейнере с ограничением 4 GiB порог брокера 3 GiB может быть разумным для выделенного pod, в то время как более низкое значение может быть лучше, если sidecar или плагины используют значительную память. Не копируйте это число слепо. Смысл в том, чтобы сделать отношение явным.
Постоянные данные также нуждаются в постоянном хранилище. Если перезапуск контейнера приведет к потере каталога данных RabbitMQ, устойчивые очереди и постоянные сообщения вас не спасут. Используйте правильные тома, понимайте свой класс хранилища и тестируйте перезапуск брокера, прежде чем доверять настройке.
Ленивые очереди, кворумные очереди и ожидания по памяти
Более старые советы по RabbitMQ часто говорят "используйте ленивые очереди для больших отставаний". Этот совет требует контекста. Классические ленивые очереди были разработаны для хранения большего количества сообщений на диске и снижения нагрузки на память для длинных очередей. Они все еще могут быть полезны для рабочих нагрузок классических очередей, где ожидаются большие отставания.
Кворумные очереди ведут себя иначе и обычно используются для реплицированных надежных рабочих нагрузок. Они могут обрабатывать отставания, но они также реплицируют данные и имеют свой собственный профиль памяти и диска. Кворумная очередь — это прежде всего выбор надежности. Это не shortcut для неограниченного отставания.
Если бизнес ожидает, что сообщения будут лежать днями и воспроизводиться многими потребителями, поток или другая система типа журнала могут подойти лучше, чем обычная рабочая очередь. RabbitMQ отлично подходит для распределения работы. Менее приятно, когда он становится единственным долгосрочным слоем хранения для больших исторических полезных нагрузок.
Отделяйте симптомы брокера от симптомов рабочей нагрузки
Сигнал тревоги по памяти говорит вам, что RabbitMQ находится под давлением. Он не говорит вам, является ли RabbitMQ корневой причиной. Медленный API биллинга может привести к тому, что потребители перестанут подтверждать, что вызовет рост неподтвержденных сообщений, что повысит память брокера, что заблокирует издателей. Сигнал тревоги брокера реален, но первое исправление может быть вне брокера.
Во время анализа графиков вместе смотрите на скорость публикации, скорость доставки, скорость подтверждения, готовые сообщения, неподтвержденные сообщения, память, свободное место на диске и время обработки потребителя. Порядок движения имеет значение. Если скорость подтверждения падает до того, как память растет, смотрите на потребителей. Если задержка диска возрастает до того, как замедляются подтверждения, смотрите на хранилище. Если скорость публикации удваивается после запуска продукта, смотрите на пропускную способность и обратное давление.
Вот почему нагрузочные тесты должны включать потребителей и нижестоящие зависимости. Бенчмарк только на публикацию мало что доказывает о реальном рабочем процессе. Брокер может какое-то время быстро принимать сообщения, но система работает только в том случае, если потребители завершают их с требуемой скоростью.
Сделайте обратное давление видимым для команд приложений
Блокировка издателя не должна быть невидимой. Приложения должны логировать события блокировки и разблокировки соединений, когда клиентская библиотека их предоставляет, а издатели должны иметь тайм-ауты для путей публикации, которые питают пользовательские запросы.
Без такой видимости сигнал тревоги по памяти становится расплывчатой жалобой "приложение медленное". С ней команда может увидеть, что RabbitMQ применил обратное давление в определенное время, а затем сравнить эту метку времени с глубиной очереди, ошибками потребителей, задержкой диска и событиями развертывания.
Что я проверяю во время анализа высокой пропускной способности
Я начинаю с этих вопросов:
- Срабатывают ли сигналы тревоги по памяти или диску?
- Сообщения в основном готовы или неподтверждены?
- Какие очереди используют больше всего памяти?
- Успевают ли потребители за скоростью публикации?
- Являются ли подтверждения издателя асинхронными или блокирующими одно за другим?
- Не слишком ли велики сообщения?
- Растет ли задержка диска во время всплесков?
- Стабильны ли соединения или постоянно переподключаются?
Эти ответы обычно указывают на исправление. Иногда исправление — это изменение конфигурации. Чаще это изменение потока: более быстрые потребители, меньший prefetch, меньшие сообщения, лучшая пакетная обработка, путь мертвых писем или тип очереди, соответствующий рабочей нагрузке.
Высокая пропускная способность — это не просто большее число в бенчмарке. Это способность поглощать загруженные периоды без потери контроля над памятью, диском и задержкой. RabbitMQ дает вам предохранительные планки, но вы все равно должны поддерживать движение трафика.