В первой статье серии мы построили отказоустойчивый кластер с quorum-очередями: он переживает падение ноды, не теряя подтверждённых сообщений. Во второй разобрали гарантии доставки: publisher confirms и manual ack — на уровне клиентов, DLQ — на уровне брокера. Но оба механизма работают внутри одного кластера.
Что происходит, когда нужно связать два независимых кластера — в разных регионах, разных ДЦ или просто логически разделённых? Отдельный кластер в регионе B не знает ничего о потоке сообщений в регионе A. Именно здесь появляются Federation и Shovel.
В статье
- Зачем связывать кластеры
- Federation: подписка downstream на upstream
- Shovel: точечная перекачка сообщений
- Federation vs Shovel: когда что выбирать
- Geo-распределение и DR
- Вывод
Зачем связывать кластеры
Три основных сценария, при которых возникает потребность в межкластерном взаимодействии.
Geo-распределение. Сервисы работают в нескольких регионах. Каждый регион имеет свой кластер RabbitMQ — это правильно с точки зрения латентности и автономности. Но события, производимые в одном регионе, нужно консюмить в другом: заказы из EU-кластера обрабатываются сервисами в US-регионе. Растянуть один кластер на два региона — плохая идея: кворум через WAN даёт неприемлемую задержку на каждый publish.
Disaster Recovery. Нужен резервный кластер в другом ДЦ, который получает копию критичных сообщений. Если основной кластер полностью выходит из строя — DR-кластер подхватывает нагрузку.
Разгрузка и изоляция доменов. Отдельный кластер для конкретного сервиса или направления — и нужен мост между ними. Иногда это требование безопасности (изоляция PCI DSS-трафика), иногда — команды не хотят делить один брокер.
Оба инструмента — Federation и Shovel — реализованы как плагины RabbitMQ и работают поверх обычных AMQP-соединений. Это ключевое отличие от внутрикластерной репликации (Raft у quorum-очередей): Federation и Shovel работают между независимыми брокерами, не требуя Erlang-кластеризации.
Federation: подписка downstream на upstream
Концепция
Federation — это механизм репликации потока сообщений между брокерами на уровне exchange или queue. Основная идея: downstream-кластер подписывается на upstream и получает сообщения, которые публикуются там.
Несколько ключевых свойств, которые важно понять сразу:
- Federation-link инициируется downstream’ом. Downstream-нода устанавливает AMQP-соединение к upstream и «вычитывает» сообщения. Upstream при этом ничего специально не настраивает — он просто принимает соединение.
- Копирование, а не перемещение (exchange federation). Federation создаёт внутреннего consumer’а на upstream и копирует сообщения на downstream, когда у downstream-exchange есть связанные очереди с подписчиками. Сообщение на upstream при этом не удаляется — оно остаётся доступным для локальных потребителей upstream.
- Асинхронность и устойчивость. Federation работает асинхронно. Если линк временно недоступен (сеть, рестарт upstream), downstream продолжает обслуживать локальных потребителей. При восстановлении соединения federation-link поднимается автоматически.
Exchange federation vs queue federation
RabbitMQ поддерживает два варианта.
Exchange federation — наиболее распространённый. Federated exchange на downstream «видит» сообщения, опубликованные в exchange того же имени на upstream. Federation создаёт внутреннего consumer’а на upstream и копирует только те сообщения, которые реально нужны downstream-потребителям. На upstream сообщение не удаляется — это именно копирование.
Queue federation — downstream-потребители получают сообщения непосредственно из upstream-очереди. В отличие от exchange federation, сообщения при этом уходят из upstream-очереди (как при обычном consume) — дублирования нет. Смысл не в work-queue между кластерами, а в том, чтобы перенести потребление ближе к downstream-потребителям: они читают через локальный брокер, который вычитывает с upstream. Используется реже exchange federation.
Настройка: upstream и политика
Federation настраивается двумя объектами.
Federation upstream — описывает, куда подключаться downstream-кластеру:
rabbitmqctl set_parameter federation-upstream my-upstream \
'{"uri":"amqp://user:pass@upstream-host:5672","expires":3600000}'
Политика — определяет, на какие exchange или очереди распространяется federation:
rabbitmqctl set_policy \
--vhost / \
--apply-to exchanges \
federate-orders \
"^orders\." \
'{"federation-upstream-set":"all"}'
Этой политикой все exchange с именем, начинающимся на orders., становятся federated. Downstream-брокер начинает тянуть сообщения с upstream автоматически.
Демо: federation-demo.sh
В demo-стенде есть скрипт scripts/federation-demo.sh, который показывает настоящую cross-cluster federation. Стенд проверен на RabbitMQ 4.3.
Топология стенда:
┌─────────────────────────────────────┐ AMQP Federation ┌──────────────────────────┐
│ Кластер A (UPSTREAM) │ amqp://rabbit1:5672/%2F │ Кластер B (DOWNSTREAM) │
│ rabbit1 / rabbit2 / rabbit3 │ ─────────────────────────► │ rabbit-b (одна нода) │
│ AMQP: 5672/5673/5674 │ │ AMQP: 5681 │
│ Management: 15672 │ │ Management: 15681 │
└─────────────────────────────────────┘ └──────────────────────────┘
rabbit-b — отдельный кластер с собственным Erlang cookie. Он не входит в кластер rabbit1/2/3, но подключён к той же docker-сети rabbitnet и поэтому может достучаться до rabbit1 по AMQP.
Что делает скрипт:
- Объявляет exchange
demo.fedна upstream (rabbit1). - На downstream (rabbit-b) создаёт тот же exchange
demo.fed, quorum-очередьdemo.fed.qи связывает их. - На rabbit-b задаёт federation-upstream
upstream-rabbit1с URIamqp://demo:demo@rabbit1:5672/%2F. Нюанс: vhost/кодируется как%2F— в RabbitMQ 4.x trailing slash в URI трактуется как пустой vhost и даёт ошибкуnot_allowed. - Применяет политику
federate-fed-bна exchangedemo.fedна rabbit-b — federation-link устанавливается за ~5 сек. - Публикует сообщение в upstream (rabbit1) → оно реально появляется в
demo.fed.qна downstream (rabbit-b). Это настоящая cross-cluster репликация: сообщение пересекает границу кластеров по AMQP-линку.
# Запустить демо (из папки ha-cluster):
bash scripts/federation-demo.sh
# Проверить статус federation-links на downstream (rabbit-b):
docker exec rabbit-b rabbitmqctl list_parameters
docker exec rabbit-b rabbitmqctl eval 'rabbit_federation_status:status().'
# Очереди на downstream после публикации:
docker exec rabbit-b rabbitmqctl list_queues name messages
# Удалить артефакты:
bash scripts/federation-demo.sh clean
Management UI обоих кластеров:
- Upstream rabbit1: http://localhost:15672 (demo/demo)
- Downstream rabbit-b: http://localhost:15681 (demo/demo)
Статус federation-links виден на downstream: Admin → Federation Status. Там отображаются активные линки: от какого upstream, на каких exchange/queue, состояние соединения (running).
flowchart LR
subgraph upstream["Upstream — кластер A (rabbit1/2/3, порты 5672/15672)"]
direction TB
UE["orders.exchange\n(federated, durable)"]
UQ["orders.queue\n(quorum)"]
UP["Producer A"]
UC["Consumer A"]
UP -->|"publish"| UE
UE --> UQ
UQ -->|"consume"| UC
end
subgraph downstream["Downstream — кластер B (rabbit-b, порты 5681/15681)"]
direction TB
DE["orders.exchange\n(federated, durable)"]
DQ["orders.queue\n(quorum)"]
DC["Consumer B"]
DE --> DQ
DQ -->|"consume"| DC
end
downstream -->|"federation-link\n(downstream подключается к upstream, pull)"| upstream
UE -.->|"сообщения\n(копия, только нужные Consumer B)"| DE
style upstream fill:#f9f3e3,stroke:#8b7355
style downstream fill:#c9e4c5,stroke:#5b8a5e
style UE fill:#e8d5c4,stroke:#a0785a
style DE fill:#e8d5c4,stroke:#a0785a
style UQ fill:#c9e4c5,stroke:#5b8a5e
style DQ fill:#c9e4c5,stroke:#5b8a5e
Два реальных кластера: как это выглядит на практике
Demo-стенд (federation-demo.sh) уже работает с двумя настоящими независимыми кластерами: rabbit1/2/3 (upstream, кластер A) и rabbit-b (downstream, кластер B). Это ровно та же модель, что применяется в production между кластерами в разных регионах или сетях — разница только в том, что в реальной инфраструктуре вместо docker-сети будет WAN и TLS.
На upstream-кластере ничего специального не нужно. Он просто принимает входящие AMQP-соединения — federation работает через стандартный протокол. Убедитесь, что порт 5672 (или 5671 для TLS) доступен с downstream, и есть пользователь с нужными правами.
На downstream-кластере три шага:
-
Объявить federation-upstream — AMQP-URI удалённого кластера:
# Синтаксис set_parameter стабилен, но конкретные параметры URI # могут меняться в зависимости от версии плагина — сверяйтесь с документацией. rabbitmqctl set_parameter federation-upstream region-a \ '{"uri":"amqps://federation-user:secret@rabbit.region-a.example.com:5671", "expires":3600000}' -
Применить политику федерации на нужные exchange (или queue):
rabbitmqctl set_policy \ --vhost / \ --apply-to exchanges \ federate-orders \ "^orders\." \ '{"federation-upstream":"region-a"}' -
Проверить статус линка:
rabbitmqctl eval 'rabbit_federation_status:status().' # или в Management UI: Admin → Federation Status
Главное отличие от demo: URI указывает на реальный хост другого кластера в другой сети. Всё остальное — та же модель: downstream устанавливает соединение, создаёт внутреннего consumer’а на upstream, копирует сообщения по мере необходимости.
Безопасность межкластерных соединений
Federation и Shovel ходят по AMQP между кластерами — нередко через интернет или между регионами. Несколько практических правил:
TLS обязателен. Используйте amqps:// вместо amqp:// для межкластерных URI. Передача сообщений в открытом виде между регионами — неприемлемый риск. RabbitMQ поддерживает TLS из коробки; настройка описана в официальной документации.
Отдельный пользователь для federation. Не используйте admin-аккаунт в URI federation-upstream или shovel. Заведите отдельного пользователя с минимально необходимыми правами: read на нужные exchange/queue на upstream, без доступа к management API. Если credentials утекут — радиус поражения ограничен.
Shovel: точечная перекачка сообщений
Shovel — другой плагин, другая модель. Если Federation — это «подписка», то Shovel — это «насос»: он вычитывает сообщения из источника (source) и публикует их в назначение (destination). Работает направленно: из точки A в точку B, без обратного пути.
Static vs dynamic shovel
Static shovel описывается в файле rabbitmq.conf или через definitions.json. Поднимается при старте брокера и не меняется без рестарта или обновления конфига. Подходит для постоянных, заранее известных маршрутов.
# rabbitmq.conf (фрагмент)
shovel.my-shovel.source.protocol = amqp091
shovel.my-shovel.source.uris.1 = amqp://upstream-host:5672
shovel.my-shovel.source.queue = source.queue
shovel.my-shovel.destination.protocol = amqp091
shovel.my-shovel.destination.uris.1 = amqp://destination-host:5672
shovel.my-shovel.destination.queue = destination.queue
Dynamic shovel настраивается через runtime-параметры (rabbitmqctl set_parameter), то есть без рестарта брокера. Удобен для временных задач: запустить, выполнить перекачку, отключить.
rabbitmqctl set_parameter shovel migrate-orders \
'{
"src-protocol": "amqp091",
"src-uri": "amqp://source-host:5672",
"src-queue": "legacy.orders",
"dest-protocol": "amqp091",
"dest-uri": "amqp://target-host:5672",
"dest-queue": "orders"
}'
Что делает Shovel с сообщениями
Shovel вычитывает сообщение из источника, публикует в назначение и только после успешного подтверждения публикации отправляет ack источнику. Это обеспечивает at-least-once семантику: если сбой произошёл между публикацией и ack’ом источника, сообщение будет доставлено повторно — но не потеряется.
Shovel работает между любыми двумя AMQP-брокерами — не только RabbitMQ. Это иногда используется как мост с другими системами.
Federation vs Shovel: когда что выбирать
| Federation | Shovel | |
|---|---|---|
| Модель | Downstream «подтягивает» нужные сообщения с upstream | Насос: вычитывает из A, публикует в B |
| Инициатор | Downstream устанавливает соединение к upstream | Shovel-агент на брокере, где он запущен |
| Гранулярность | Exchange или queue | Конкретная очередь или exchange → очередь/exchange |
| Направление | Upstream → downstream (pull) | Источник → назначение (push) |
| Копирование vs перемещение | Копирует (сообщение остаётся на upstream) | Перемещает (удаляет из источника после успешной публикации) |
| Настройка | Upstream + policy | Static (конфиг) или dynamic (runtime-параметр) |
| Топология | Многие-ко-многим через upstreams + policies | Точка-точка |
| Типичный сценарий | Geo-fan-out, DR, события между регионами | Миграция, мост, разовая перекачка |
| Прозрачность | Потребители downstream не знают об upstream | Явный маршрут; потребители работают с назначением |
Выбирайте Federation, когда:
- нужно, чтобы сообщения с upstream-кластера были доступны потребителям в другом регионе автоматически;
- хотите «логически объединить» несколько кластеров в единое пространство имён;
- важно, чтобы сообщения оставались на upstream для локальных потребителей и копировались на downstream только при наличии там интереса;
- топология «hub-and-spoke» или «mesh» между несколькими регионами.
Выбирайте Shovel, когда:
- нужно переместить (не скопировать) сообщения из одного брокера в другой;
- выполняете миграцию: переносите данные из старого кластера в новый;
- нужен мост между двумя конкретными очередями с явным маршрутом;
- задача разовая или условно временная — dynamic shovel проще включить и выключить без рестарта.
Geo-распределение и DR
Типовые топологии
Hub-and-spoke. Центральный кластер (hub) принимает сообщения, региональные кластеры (spoke) подписываются на него через federation. Производители пишут в hub, потребители в каждом регионе читают локально. Подходит, когда у вас один источник событий и много потребителей в разных точках.
Active-active. Каждый кластер самостоятелен: производители и потребители есть в каждом регионе. Federation работает в обе стороны: кластер A подписывается на B, B подписывается на A. Сложнее в конфигурации, но даёт полную автономию регионов. Важно: federation предотвращает циклы через механизм hop counting — каждое сообщение несёт счётчик прыжков, и federation не пересылает его при достижении max-hops (по умолчанию 1). Так сообщение, пришедшее из A в B, не вернётся обратно в A.
DR (резервный кластер). Один кластер — основной, второй — горячий резерв. Shovel или federation копирует поток в DR-кластер. При отказе основного переключение — ручное или автоматическое (зависит от вашей инфраструктуры; RabbitMQ сам не делает failover между кластерами).
Латентность и направление потока
Geo-распределение через federation или shovel всегда асинхронно. Это принципиальное ограничение: нельзя получить низкую латентность и сильную согласованность одновременно через WAN.
На практике это означает:
- Производители пишут локально — в свой региональный кластер. Это даёт минимальную латентность на publish и отсутствие зависимости от WAN при публикации.
- Federation-линк несёт задержку — сообщение появится на downstream с задержкой, определяемой скоростью WAN-соединения и объёмом трафика.
- Порядок сообщений между регионами не гарантируется: разные federation-линки работают параллельно, порядок доставки может отличаться от порядка публикации.
Именно поэтому попытка растянуть один RabbitMQ-кластер на несколько географически разнесённых ДЦ — плохая идея. Quorum-очереди используют Raft, каждый publish ждёт подтверждения от большинства реплик. Через WAN это означает сотни миллисекунд на каждое сообщение. Federation решает задачу иначе: каждый регион работает независимо, WAN-соединение нужно только для репликации потока, но не для каждого отдельного подтверждения.
Вывод
Federation и Shovel закрывают разные задачи, и эта разница принципиальная.
Federation — это механизм прозрачной подписки downstream на upstream через AMQP-линк. Downstream тянет то, что ему нужно; сообщения могут оставаться на upstream для локальных потребителей. Это инструмент для постоянных топологий: geo-fan-out, DR-репликация, объединение кластеров в разных регионах.
Shovel — направленный насос: вычитывает из источника, публикует в назначение, удаляет из источника. Явный маршрут, явная семантика. Лучше подходит для миграций, точечных мостов между конкретными очередями и разовых задач перекачки.
Оба инструмента работают асинхронно поверх обычных AMQP-соединений и не требуют Erlang-кластеризации между брокерами. Это позволяет связывать кластеры в разных ДЦ, регионах и даже у разных провайдеров.
В следующей статье серии разберём мониторинг и продакшн-чеклист: какие метрики критичны, как настроить алерты и что проверить перед тем, как пустить кластер в бой — «RabbitMQ: мониторинг и продакшн-чеклист».
Комментарии