Модуль 3. Инциденты RabbitMQ: memory alarm, «мёртвый хват», flow control, poison-циклы
Цель модуля: вести инцидент RabbitMQ от симптома к причине: пять канонических отказов с разбором «симптом → метрика → причина → лечение» и воспроизведением на кластере. Родственно курсам Redis/Kafka: методика одна — словарь причин разный.
Перед началом: Модули 1–2 (особенно alarms и триада метрик). Трудоёмкость: ~10–12 ч (разборы 5 ч · воспроизведения 5 ч · квиз 1 ч).
📖 3.1. Методика: триада решает
Первый взгляд — всегда на триаду очереди (consumers → unacked → ready):
- consumers = 0 при живом publish — воркеры умерли/не подключились: ищите деплой, креды, сеть; брокер почти ни при чём;
- unacked растёт/висит — потребители живы, но не подтверждают: обработка зависла или длиннее prefetch-окна;
- ready растёт при живых consumers — не успевают: масштаб или медленная обработка;
- publish_rate упал на всём кластере — чей-то alarm или flow control (§3.2, §3.5): сервер спасает себя.
📖 3.2. Инцидент «Memory alarm под нагрузкой»
Симптом: продюсеры по всему кластеру встали (сообщения копятся на
стороне приложений), UI показывает memory alarm на одном узле.
Метрики: rabbitmq_node_mem_alarm = 1, node_mem_used растёт,
publish_rate → 0 на всех узлах (alarm синхронизируется, Модуль 2 §2.2).
Причина (по частоте):
- Глубокая очередь — сообщения в ready живут в памяти узлов (классические очереди могут частично уходить на диск, но рост глубины = рост памяти);
- Много unacked — огромный prefetch у прожорливых потребителей;
- Утечка каналов/очередей — авто-delete выключен, очереди плодятся (тысячи пустых очередей тоже едят память);
- Смешанные нагрузки: TTL-сообщения копятся до истечения.
Лечение:
- срочно: разгрузить готовые (временно подключить drain-воркер),
поднять watermark (
rabbitmqctl set_vm_memory_high_watermap absolute "8GB"— только если память физически есть) — но лучше этого не делать под огнём; - системно: лимит глубины (
x-max-length+overflow=reject-publish— backpressure на издателя!), prefetch по формуле, чистка брошенных очередей (rabbitmqctl list_queues name consumers messages); - профилактика: отдельный кластер/вhost под жирные потоки, алерт на node_mem_used > 70 % watermark.
📖 3.3. Инцидент «Мёртвый хват»: unacked у зависшего потребителя
Симптом: ready = 0, unacked = prefetch × consumers и не падает; новые сообщения не приходят потребителям; воркер «работает» (логи живые) или висит.
Метрики: unacked стоит на потолке; deliver_rate упал; ack_rate → 0.
Причина: потребитель получил сообщения и не шлёт ack — обработка зависла (DB-лок, внешний сервис без таймаута) либо код забыл ack после успеха. Сообщения «в руках» — и недоступны другим.
Лечение:
- heartbeat +
consumer_timeout(RabbitMQ разрывает таких потребителей и делает redelivery — убедитесь, что таймаут меньше вашего SLA); - prefetch подбирайте от p99 обработки: prefetch ≈ rate × p99;
- в коде: ack строго после успеха (контур из «Фундамента» М1), обработка с context-таймаутом, panic-обработка в воркере — соединение закроется, сообщения вернутся живым;
- диагностика:
rabbitmqctl list_queues name messages_unacknowledged consumersи per-consumer в management UI.
📖 3.4. Инцидент «Poison-цикл через DLX»
Симптом: сообщения «кружат»: одна очередь копится и пустеет ритмично, delivery count растёт, воркеры делают одну и ту же работу; лаг CPU на репакете.
Метрики: publish/deliver по очередям показывает цикл
(work → retry → work), в management UI у сообщения огромный
x-death count.
Причина: retry-топология зациклена: DLX возвращает сообщение в рабочую очередь без счётчика попыток (или TTL-ретрай с нулевым заголовком), либо nack с requeue=true навсегда.
Лечение:
- счётчик попыток в заголовке (
x-deathили свойattempt), после N — в DLQ (кладбище, не круговорот); nack(requeue=false)+ DLX вместо requeue=true для повторов — повтор управляем, а не бесконечен;- мониторинг DLQ: глубина > 0 — это алерт (у нас «кладбище» переполняется) и регулярный разбор.
📖 3.5. Инцидент «Flow control / blocked publishers»
Симптом: продюсеры периодически «замечают» паузы записи; в логах
клиентов — connection blocked и flow; alarms при этом чистые.
Метрики: rabbitmq_connection_blocked... точнее — на узлах
rabbitmq_node_* в норме, но в management UI клиенты со статусом
flow/blocking; publish_rate пилит.
Причина: credit flow — RabbitMQ лимитирует канал, который не
успевает обрабатывать (обычно медленный потребительский канал тащит за
собой издателя на том же соединении, или сетевой узел перегружен).
Это не отказ, а честный backpressure — но постоянный flow = деградация.
Лечение:
- развести продюсерские и потребительские соединения (не смешивать в одном connection);
- чинить медленного потребителя (это его credit исчерпал);
- если
flowритмичен и совпадает с батчами — увеличить consumer_prefetch/publish batching на клиенте.
📖 3.6. Инцидент «Шторм соединений»
Симптом: connections_opened_total растёт стеной; fd_used к лимиту;
брокер отвечает медленно; через минуту всё «прошло», повторяется по
расписанию.
Метрики: rate(opened) − rate(closed), fd_used/sockets_used к лимитам.
Причина: балансировщик/K8s liveness-пробы открывают соединения без переиспользования; клиентская библиотека создаёт connection на каждое сообщение (антипаттерн: AMQP-соединение дорогое, канал — дешёвый).
Лечение:
- пул длинных соединений, каналы на операцию (или долгоживущие); amqp091-go: один Connection, Channel на goroutine-воркер;
- пробы — через health-endpoint приложения, а не через брокер;
- лимиты клиента (max channels per connection) и алерт на churn.
💻 Практика
- Memory alarm:
make mem-alarmпод нагрузкой → publish встал; разгрузка drain-воркером → alarm снялся. Запишите время блокировки. - Мёртвый хват: воркер с обработчиком «висит на DB-локе» —
unacked на потолке; включите короткий
consumer_timeoutи убедитесь, что redelivery возвращает сообщения живым. - Poison-цикл: соберите retry-топологию без счётчика → цикл; добавьте счётчик попыток и DLQ → цикл разорван, DLQ-алерт работает.
- Flow control: смешайте продюсера и медленного потребителя в одном
соединении — поймайте
flowв UI; разведите соединения —flowисчезает. - Шторм соединений: скрипт открывает по connection на сообщение — churn на графике; переделайте на пул из 2 соединений × каналы.
Артефакт: журнал пяти воспроизведений «симптом / ведущая метрика / лечение» + топология retry с DLQ из шага 3.
🧩 Quiz "Инцидент-квиз: причина по симптому"
✅ Итоги модуля
| Инцидент | Ведущая метрика | Ядро причины |
|---|---|---|
| Memory alarm | node_mem_alarm / mem_used | глубина очереди в памяти; alarm блочит весь кластер |
| Мёртвый хват | unacked на потолке, ack_rate=0 | зависшая обработка / забытый ack; лечит consumer_timeout |
| Poison-цикл | цикл в publish/deliver + x-death | retry без счётчика попыток; лечит DLQ-топология |
| Flow control | статус flow у клиентов | credit flow: медленный канал; разводить соединения |
| Шторм соединений | opened/closed rate, fd_used | connection на сообщение; лечит пул + каналы |
🔗 Мостик к Модулю 4
Пять инцидентов закрывают эксплуатацию классической AMQP-модели. Последний модуль курса — RabbitMQ Streams и multi-DC: лог-модель внутри Rabbit (replay, офсеты, большие retention), shovel и federation для связи кластеров, и честный ответ «когда RabbitMQ Streams, а когда всё-таки Kafka».