Active-Active и Active-Passive топологии
Выбор топологии Multi-DC — один из самых важных архитектурных решений при проектировании отказоустойчивой системы на Kafka. Два принципиально разных подхода решают разные задачи: Active-Passive ставит простоту failover выше всего, Active-Active минимизирует задержку за счёт операционной сложности.
Не существует “правильной” топологии. Существует топология, соответствующая вашим требованиям по RPO, RTO, пропускной способности и операционной зрелости команды.
Active-Passive: надёжность через простоту
В Active-Passive топологии один кластер (Primary) обслуживает 100% трафика. Второй кластер (DR Standby) получает реплицированные данные, но в нормальном режиме не обслуживает ни продюсеров, ни consumer’ов.
DC-1 (Primary)
DC-1 Primary: обслуживает 100% продюсеров и consumer'ов. Все бизнес-события записываются сюда. Consumer group 'orders-processor' читает топик 'orders' с offset'ом, который отслеживается на DC-1. При нормальной работе DC-2 не получает запросов от приложений.Producers
Продюсеры пишут заказы в топик orders на DC-1. Producer config: bootstrap.servers=dc1-broker1:9092,dc1-broker2:9092. В нормальном режиме продюсеры никогда не обращаются к DC-2.Consumers
Consumer'ы читают из DC-1. Consumer group 'orders-processor' committed offset на DC-1. После failover на DC-2 эти offset'ы нужно транслировать через MirrorCheckpointConnector — offset 5000 на DC-1 может соответствовать offset 5123 на DC-2.orders (12 партиций)
Топик orders на DC-1. 12 партиций, RF=3, retention=7 дней. DefaultReplicationPolicy: при репликации на DC-2 топик получает имя dc1.orders. Consumer группы отслеживают offset именно по топику 'orders', партиция [0..11].payments (6 партиций)
Топик payments на DC-1. Реплицируется на DC-2 как dc1.payments. При failover consumer'ы, читавшие 'payments' на DC-1 с offset 8000, должны переключиться на чтение 'dc1.payments' на DC-2 с транслированным offset'ом.DC-2 (DR Standby)
DC-2 DR Standby: получает реплицированные данные. В нормальном режиме consumer'ы не работают, продюсеры не подключены. При failover: DC-2 становится новым Primary. Время переключения (RTO): 5-20 минут включая обнаружение сбоя, трансляцию offset'ов и DNS-переключение.dc1.orders
Реплицированный топик dc1.orders на DR-кластере. Содержит те же данные что и orders на DC-1, но имя другое (DefaultReplicationPolicy). Consumer'ы после failover читают dc1.orders вместо orders. sync.group.offsets.enabled=true автоматически транслирует committed offset'ы.dc1.payments
Реплицированный топик dc1.payments на DR-кластере. Lag между DC-1 orders offset и dc1.orders offset на DC-2 определяет RPO: если лаг = 2 секунды, то RPO = 2 секунды.Процедура failover в Active-Passive
Failover — это последовательность шагов, каждый из которых влияет на RPO или RTO. Понимание порядка критично:
-
Обнаружение сбоя. Мониторинг heartbeat-топика (
heartbeats) на DC-2. Если heartbeat_age превышает порог (обычно 30-60 секунд), DC-1 считается недоступным. Также: потеря TCP-соединений от продюсеров. -
Остановка продюсеров на DC-1. Это предотвращает запись данных в DC-1, которые не успеют реплицироваться. Application-level circuit breaker или внешний оркестратор отключает продюсеров.
-
Ожидание дренирования MM2. После остановки продюсеров MM2 продолжает реплицировать оставшиеся записи. Ожидание:
tasks.max * replication_task_poll_interval. Если DC-1 полностью недоступен, этот шаг пропускается, принимая RPO = текущий lag. -
Трансляция offset’ов consumer groups. При
sync.group.offsets.enabled=true— автоматически. Иначе — вручную черезRemoteClusterUtils.translateOffsets(). Подробно в уроке 03. -
Переключение DNS/Load Balancer. Bootstrap servers продюсеров и consumer’ов перенаправляются на DC-2. TTL DNS влияет на скорость распространения изменения.
-
Перезапуск consumer’ов и продюсеров на DC-2. Consumer’ы подключаются к DC-2, находят committed offset’ы (автоматически транслированные) и возобновляют чтение из
dc1.orders. -
Верификация потока данных. Проверка через consumer lag мониторинг:
records-lag-maxдля всех consumer groups должен снижаться.
Active-Active: двунаправленная репликация
В Active-Active оба кластера одновременно обслуживают продюсеров и consumer’ов. MM2 реплицирует данные в обоих направлениях. Цель: каждый регион обслуживает локальных пользователей с минимальной задержкой, при этом оба кластера содержат (почти) полный набор данных.
IdentityReplicationPolicy: обязательное требование
Для Active-Active обязательно использовать IdentityReplicationPolicy. Без неё:
- DefaultReplicationPolicy: DC-1 реплицирует
orders-> DC-2 какdc1.orders. DC-2 реплицируетorders-> DC-1 какdc2.orders. - Результат: на DC-1 существуют два разных топика:
orders(локальный) иdc2.orders(реплика). Consumer’ы на DC-2 читаютdc2.orders, а неorders. Логика приложения разрушена.
С IdentityReplicationPolicy: и DC-1, и DC-2 имеют топик orders с одинаковым именем. Consumer’ы используют одну и ту же конфигурацию независимо от того, к какому кластеру они подключены.
# Active-Active конфигурация mm2.properties
clusters = dc1, dc2
dc1.bootstrap.servers = dc1-broker1:9092,dc1-broker2:9092
dc2.bootstrap.servers = dc2-broker1:9092,dc2-broker2:9092
# Двунаправленная репликация
dc1->dc2.enabled = true
dc1->dc2.topics = .*
dc2->dc1.enabled = true
dc2->dc1.topics = .*
# Исключить внутренние топики MM2 из репликации (критично!)
dc1->dc2.topics.exclude = .*\.internal, heartbeats, .*\.replica
dc2->dc1.topics.exclude = .*\.internal, heartbeats, .*\.replica
# IdentityReplicationPolicy: топики сохраняют оригинальное имя
replication.policy.class = org.apache.kafka.connect.mirror.IdentityReplicationPolicy
# Heartbeat и checkpoint для обоих направлений
dc1->dc2.emit.heartbeats.enabled = true
dc2->dc1.emit.heartbeats.enabled = true
dc1->dc2.emit.checkpoints.enabled = true
dc2->dc1.emit.checkpoints.enabled = true
Предотвращение циклической репликации
Главная техническая проблема Active-Active: если DC-1 реплицирует запись на DC-2, а DC-2 реплицирует её обратно на DC-1, возникает бесконечный цикл.
MM2 решает это через заголовки. К каждой реплицированной записи добавляется заголовок __mm2.record.header.source.cluster.alias=dc1. Когда MirrorSourceConnector на DC-2 читает эту запись при репликации dc2->dc1, он проверяет заголовок: если source == “dc1” (текущий target), запись пропускается.
Дополнительная защита — явное исключение внутренних топиков через topics.exclude. Топики heartbeats, *.checkpoints.internal, *.internal не должны реплицироваться обратно.
Разрешение конфликтов в Active-Active
Kafka не имеет встроенного разрешения конфликтов для Active-Active записи. Если два продюсера в DC-1 и DC-2 одновременно записывают разные данные по одному ключу, порядок записей в обоих кластерах будет определяться задержкой репликации и порядком прибытия.
Практические стратегии:
Partition Affinity (рекомендованное решение). Каждый DC “владеет” своим диапазоном ключей. Например: DC-1 обслуживает пользователей с ID A-M, DC-2 обслуживает N-Z. Ни один ключ никогда не записывается одновременно из двух DC. Конфликтов нет по определению.
Application-level idempotency. Consumer’ы хранят версию каждой записи. При получении дублирующей записи проверяется версия: если входящая версия не выше уже обработанной, запись игнорируется. Требует хранилища версий (Redis, PostgreSQL).
CRDT-based merging. Конфликтующие значения merge’атся через структуры данных без конфликтов (Conflict-free Replicated Data Types). Применимо только для специфических типов данных (счётчики, наборы, timestamps).
Сравнение топологий: когда что использовать.
| Критерий | Active-Passive | Active-Active |
|---|---|---|
| Основная цель | Disaster Recovery | Низкая задержка, гео-распределение |
| RPO | Секунды (зависит от MM2 lag) | Практически 0 (локальная запись) |
| RTO | 5-20 минут | Практически 0 (нет failover) |
| Конфликты | Нет | Требует стратегии |
| Операционная сложность | Низкая | Высокая |
| Использование ресурсов DR | Idle (только репликация) | 100% utilization обоих DC |
| ReplicationPolicy | DefaultReplicationPolicy | IdentityReplicationPolicy (обязательно) |
Выбирайте Active-Active только если ваши SLA требуют нулевого RTO и у вас есть чёткая стратегия разрешения конфликтов.
Hub-and-Spoke и Fan-Out топологии
Помимо двух основных топологий, MM2 поддерживает более сложные паттерны для специфических архитектур.
Hub-and-Spoke: агрегация из множества источников
Edge-1 (EU)
Edge-1: IoT-устройства региона Европа. Продюсеры пишут sensor-данные локально. MM2 Edge1->Hub реплицирует всё в центральный Hub. Topic prefix с DefaultReplicationPolicy: edge1.sensor-data.Edge-2 (Asia)
Edge-2: IoT-устройства региона Азия. Независимый кластер. MM2 Edge2->Hub реплицирует локальные данные. Topic prefix: edge2.sensor-data. Hub видит оба потока данных параллельно.Edge-3 (US)
Edge-3: IoT-устройства региона США. Третий независимый кластер. MM2 Edge3->Hub. Topic prefix: edge3.sensor-data. На Hub доступны все три потока для global analytics.Central Hub
Central Hub: агрегирует данные из всех edge-кластеров. Доступны топики edge1.sensor-data, edge2.sensor-data, edge3.sensor-data. Kafka Streams на Hub может join'ить данные из всех регионов для global analytics. JDBC Sink может загружать в Data Warehouse.edge1., edge2., edge3.*
Агрегированные топики на Hub: edge1.sensor-data (данные из EU), edge2.sensor-data (Asia), edge3.sensor-data (US). Analytics consumer группы читают все три топика. Hub не является производителем данных -- только агрегатором.Применение Hub-and-Spoke: IoT-платформы (сотни edge-устройств -> центральная аналитика), многорегиональный CDC (несколько PostgreSQL баз -> центральный Kafka для стриминга в Data Warehouse), мультиарендные системы (каждый tenant = отдельный кластер, Hub = master).
Fan-Out: распределение из центра
Fan-Out — инверсия Hub-and-Spoke. Центральный кластер является источником данных (например, глобальный каталог товаров, конфигурация), а edge-кластеры получают реплики для локальных consumer’ов.
Central Hub
Central Hub: источник глобальных данных. Команды публикуют конфигурацию, справочники, права доступа в топики config.global, catalog.products. MM2 распределяет эти данные во все региональные кластеры.config.global / catalog.products
Топики Hub: config.global (конфигурация сервисов), catalog.products (каталог товаров для E-commerce), permissions.roles (права пользователей). Все реплицируются во все edge-кластеры через MM2 hub->edge1, hub->edge2, hub->edge3.Edge-1 (EU)
Edge-1 (EU): локальные consumer'ы читают hub.config.global без cross-DC latency. Данные актуальны с задержкой репликации MM2 (1-2 секунды). Продюсеры на edge не пишут в hub.* топики -- только читают.Edge-2 (Asia)
Edge-2 (Asia): то же самое. Локальная копия глобальных данных. Если Hub временно недоступен, edge-кластеры продолжают работать с последней версией реплицированных данных.Edge-3 (US)
Edge-3 (US): третья реплика. Fan-Out позволяет масштабировать число edge-кластеров без изменения Hub. Добавить Edge-4: просто добавить hub->edge4 в mm2.properties.Применение Fan-Out: Глобальные справочники (каталог товаров, таблицы тарифов, конфигурация сервисов), централизованная авторизация (права доступа распределяются в регионы), событийные уведомления от центрального оркестратора в региональные сервисы.
Сравнительная таблица топологий
| Топология | Направление MM2 | ReplicationPolicy | Конфликты | Основное применение |
|---|---|---|---|---|
| Active-Passive | dc1->dc2 | Default или Identity | Нет | Disaster Recovery |
| Active-Active | dc1->dc2 и dc2->dc1 | Identity (обязательно) | Требует стратегии | Гео-распределение, низкая задержка |
| Hub-and-Spoke | edge[1-N]->hub | Default | Нет | IoT-агрегация, CDC из нескольких источников |
| Fan-Out | hub->edge[1-N] | Default | Нет | Распределение конфигурации, справочников |
Ключевые выводы
- Active-Passive — стандарт для Disaster Recovery. Простая конфигурация, предсказуемый failover, RTO 5-20 минут, RPO в секунды. DefaultReplicationPolicy добавляет prefix к топикам.
- Active-Active — для геораспределённых систем с требованием низкой задержки. Обязателен IdentityReplicationPolicy. Требует явной стратегии разрешения конфликтов (partition affinity — самый безопасный выбор).
- Циклическая репликация в Active-Active предотвращается заголовком
__mm2.record.header.source.cluster.aliasи явным исключением служебных топиков черезtopics.exclude. - Hub-and-Spoke и Fan-Out — специализированные топологии для IoT и распределения конфигурации соответственно.