MirrorMaker 2 架构与配置
MirrorMaker 2(简称 MM2)是 Apache Kafka 官方跨集群复制工具,通过 KIP-382 在 Kafka 2.4 引入,替换了 MM1 的 consumer-producer 直连方案。旧版 MM1 已在 Kafka 4.0 被移除,本文只讲 MM2。
1. 为什么不是 MM1
MM1 是一对 consumer + producer 在同一 JVM 里跑:
[source cluster] ──▶ Consumer ──▶ Producer ──▶ [target cluster]它主要有 3 点局限,KIP-382 的动机段列得很清楚:
- 不同步 offset。MM1 只复制消息,不提供消费组 offset 的检查点与翻译机制,consumer 侧的消费进度停留在源集群的
__consumer_offsets里。灾备切换后,目标集群的 consumer 不能直接依赖源集群的消费进度,需要另行确定起始 offset。若没有预先设计迁移方案,只能earliest从头或latest从尾,两者都可能造成重复消费或漏处理。 - 不同步 topic 配置和 ACL。分区数、保留时间、副本因子、ACL 都要在目标集群手工重建。
- 不支持 Active-Active。缺少循环防护机制。
MM2 基于 Kafka Connect 框架重写,实现了消息复制、offset 检查点、topic 元数据同步和心跳等能力,但这些机制仍需结合目标集群的权限、消费者状态和切换流程配置才能生效。
2. 三个 Connector
MM2 本质上是一组 Kafka Connect Connector。启动 connect-mirror-maker.sh 会自动部署三个 Connector(每个 replication flow 一套):
| Connector | 职责 | 输出的内部 Topic |
|---|---|---|
| MirrorSourceConnector | 复制消息数据;同步 topic 配置与 ACL;向 offset-syncs 写入 offset 映射 | <source>.<topic>、mm2-offset-syncs.<target>.internal(默认位于源集群) |
| MirrorCheckpointConnector | 定期读取源集群 consumer group offset,翻译成目标集群 offset 后写入 checkpoint topic | <source>.checkpoints.internal |
| MirrorHeartbeatConnector | 向源集群写心跳消息,随消息流被 SourceConnector 复制到目标,用于端到端复制延迟监控 | heartbeats、<source>.heartbeats |
三者的关系:
source cluster target cluster
┌──────────────────┐ ┌──────────────────┐
producer ────▶ │ topic: orders │ ──── MirrorSource ──▶ │ src.orders │
│ │ │ │
│ __consumer_off │ ── MirrorCheckpoint ▶ │ src.checkpoints. │
│ │ translate & write │ internal │
│ heartbeats │ ◀── MirrorHeartbeat ─┐│ │
│ │ write hb ││ src.heartbeats │
│ │ ──── MirrorSource ───┴│ (via replication)│
└──────────────────┘ └──────────────────┘MirrorHeartbeatConnector 是三个 Connector 中专门向源集群写心跳消息的连接器(MirrorSourceConnector 也会向源集群的 offset-syncs topic 写入 offset 映射,但那是内部状态,不是业务数据)。它写的 heartbeats topic 会被 MirrorSourceConnector 当成普通消息复制到目标,形成一条端到端"心跳链路"——目标集群看到的 heartbeat 与心跳时间戳的差值可用于观测端到端复制延迟。心跳反映的是心跳消息自身的传播情况,不能单独证明所有业务 topic 都已追平,生产上还需结合 SourceConnector 的复制延迟指标和分区复制进度一起判断。
数据来源:Kafka MirrorMaker 2 — Multi-Cluster Replication、KIP-382、Red Hat: MirrorMaker components。
3. Remote Topic 命名:循环防护的关键
MM2 复制 topic 时,默认给 topic 加上源集群 alias 作为前缀。这是 DefaultReplicationPolicy 的核心行为。
Source cluster alias: us-east
Source topic: orders
Target cluster stored: us-east.orders为什么这么设计?Active-Active 拓扑下,如果不改名字:
us-east.orders消息复制到us-west的ordersus-west.orders又被 MM2 反向复制回us-east.orders- 无限循环,消息量指数级增长
有了前缀,Connector 会解析 topic 名的前缀判断其来源集群,遇到来自目标集群的 remote topic 就跳过,循环由此被切断。
代价:目标集群消费者要订阅 us-east.orders,而不是 orders——应用代码要感知 topic 命名规范。
另一种选择是 IdentityReplicationPolicy,不加前缀。它只适合 Active-Standby 单向复制场景;Active-Active 用它会导致消息在集群间无限循环复制,因此不能用于未经额外设计的双向复制。
# 不加前缀(仅 Active-Standby 可用)
replication.policy.class=org.apache.kafka.connect.mirror.IdentityReplicationPolicy4. Active-Standby 配置示例
以下配置假设两个 Kafka 集群已可从 MM2 所在环境访问,且运行账户具备创建、读取、写入 topic 及管理 ACL 的权限。示例展示复制方向与常用参数,不含 TLS、SASL 和 Connect Worker 的全部部署设置。
一份典型的 Active-Standby 配置:
# ── 集群 alias ─────────────────────────────
clusters = primary, backup
# ── Bootstrap ──────────────────────────────
primary.bootstrap.servers = kafka-primary-1:9092,kafka-primary-2:9092,kafka-primary-3:9092
backup.bootstrap.servers = kafka-backup-1:9092,kafka-backup-2:9092,kafka-backup-3:9092
# ── 复制方向:primary → backup ──────────────
primary->backup.enabled = true
backup->primary.enabled = false
# ── 复制哪些 topic 和 consumer group ────────
primary->backup.topics = orders,payments,inventory-.*
primary->backup.topics.exclude = .*\.internal,__.*
primary->backup.groups = .*
primary->backup.groups.exclude = console-consumer-.*
# ── 副本因子 ───────────────────────────────
replication.factor = 3
checkpoints.topic.replication.factor = 3
heartbeats.topic.replication.factor = 3
offset-syncs.topic.replication.factor = 3
# ── 复制策略 ───────────────────────────────
replication.policy.class = org.apache.kafka.connect.mirror.DefaultReplicationPolicy
# ── 同步频率 ───────────────────────────────
emit.heartbeats.interval.seconds = 1
emit.checkpoints.interval.seconds = 10
sync.topic.configs.enabled = true
sync.topic.acls.enabled = true
refresh.topics.interval.seconds = 30
# ── 并行度 ─────────────────────────────────
tasks.max = 8启动:
bin/connect-mirror-maker.sh config/mm2.propertiesActive-Active 需要把反向也 enable:
primary->backup.enabled = true
backup->primary.enabled = true但这只是建立复制链路。上线前还需确认 topic 命名策略、应用订阅规则、双端写入边界和冲突处理方式。若两个集群会同时写入同一业务数据,必须在应用层设计数据归属或冲突解决规则,不能把 MM2 的循环防护机制当作数据一致性机制。
5. 关键配置详解
5.1 sync.topic.configs.enabled
默认 true。开启后 SourceConnector 会周期性地把源 topic 的配置(retention.ms、cleanup.policy、max.message.bytes 等)同步到目标 topic。
注意:replication.factor 不会被同步。目标 topic 的副本因子由 MM2 配置里的 replication.factor 决定,默认 2(生产环境应显式设为 3)。
5.2 sync.topic.acls.enabled
默认 true。同步 topic 级别的 ACL,但同步范围很窄:仅同步 topic 字面(LITERAL)类型的规则,WRITE 权限会被丢弃,ALL 权限会被降级为 READ。其它资源(group、cluster、transactional-id 等)的 ACL 也不同步,需要另行维护(GitOps 是常见做法)。
5.3 emit.checkpoints.interval.seconds
默认 60 秒。MirrorCheckpointConnector 每隔多久把最新的 offset 映射写入 checkpoints topic。灾备切换时消费者能恢复到的 offset 精度受这个间隔影响。示例配置将其设为 10 秒以换取更高的恢复精度,但实际恢复位置还取决于 offset 映射是否及时、复制进度以及切换时的状态。
5.4 sync.group.offsets.enabled(Kafka 2.7+)
默认 false。开启后 MirrorCheckpointConnector 会直接把翻译后的 offset 提交到目标集群的 __consumer_offsets,而不仅仅写 checkpoints topic。灾备切换时 consumer 可以直接以原 group.id 启动,无需再用 RemoteClusterUtils 手工翻译。
这一同步只对目标集群中无活跃消费者的 consumer group 生效。若目标集群已有同名 group 的活跃消费者,同步不会执行(官方文档记载此时会返回 UNKNOWN_MEMBER_ID 错误)。
只在灾备场景开启
sync.group.offsets.enabled=true 会让目标集群的 __consumer_offsets 被源集群覆盖。如果目标集群同时有独立的消费者在跑(Active-Active 场景),会互相踩到。只在 Active-Standby 单向复制时开启。
5.5 refresh.topics.interval.seconds
默认 600 秒(10 分钟)。周期性检查源集群是否有新 topic 出现。新 topic 会被自动创建对应的 remote topic 并开始复制。示例配置将其设为 30 秒以缩短新 topic 的发现延迟,但更频繁的检查会给 broker 带来额外的 metadata 请求压力,应结合新 topic 的发现延迟要求和 broker 负载权衡。
5.6 内部 topic 的副本因子
checkpoints.topic.replication.factor / heartbeats.topic.replication.factor / offset-syncs.topic.replication.factor 默认都是 3(KIP-382 定义),但很多示例配置里写的是 1——生产环境应显式设为 3,确保内部 topic 具备与业务 topic 相同的容错能力。其中 offset-syncs 保存源端与目标端 offset 的映射关系,映射数据无法写入或读取时,新的 checkpoint 可能无法正确生成,进而影响灾备恢复位置的准确性。
6. 部署形态
MM2 有三种部署方式:
1. Dedicated MM2 Cluster:connect-mirror-maker.sh 启动一个专用 Connect 集群。最常见,运维简单。
2. 复用现有 Kafka Connect 集群:把三个 Connector 手工注册到已有 Connect 集群。适合已经有大量 Connector 的场景。
3. Kubernetes Operator:Strimzi 提供 KafkaMirrorMaker2 CRD,通过声明式方式管理复制方向与 Connector 配置。已使用 Strimzi 管理 Kafka 集群的环境可用它减少手工维护,本质上仍是前两种运行架构之一,只是管理方式不同。
7. 常见坑
坑一:目标 topic 副本因子只有 2。默认值 replication.factor=2,生产环境应显式 replication.factor=3。
坑二:__consumer_offsets 也被复制。默认排除规则 mm2.*\.internal,.*\.replica,__.* 会挡住内部 topic。但排除规则优先于包含规则,用 primary->backup.topics = .* 全量包含时,如果同时用自定义的 primary->backup.topics.exclude 覆盖了默认值且未保留 __.*、.*\.internal,内部 topic 就会被复制。务必确认排除规则里仍包含对 __.* 和 .*\.internal 的排除。
坑三:Active-Active 忘记双向 enable。只 enable 一个方向就是单向复制(Active-Standby),另一方向的消息不会被复制到对端。切换后还需调整复制方向,并确认新主集群的消息与消费者状态。
坑四:延迟只看 record-count。衡量 MM2 复制链路延迟的指标是 replication-latency-ms(KIP-382 定义),基于消息 timestamp 与到达 target 的时间差。它反映复制链路自身的延迟估计,不等于业务端到端处理延迟,也不能替代消费者 lag。
坑五:MirrorHeartbeatConnector 部署位置。它向源集群写心跳,如果 MM2 部署在离目标近的位置(推荐做法,减少复制延迟),HeartbeatConnector 会跨机房写。Red Hat 官方文档建议灾备场景关掉 HeartbeatConnector,仅在需要监控端到端延迟时开启。
8. 与本目录其他章节的关系
- offset 到底怎么翻译、翻译精度如何 → §3 Offset 翻译
- 真正切换时的执行流程 → §4 灾备演练