Kafka 跨数据中心日志同步实战:MirrorMaker 2 选型对比 + Flink 中继任务落地(双中心互备与消费者组隔离避坑)

做日志平台迟早会遇到一个需求:两个数据中心(或两地三中心的"两地")都要有全量日志。监管审计要求容灾侧数据完整、故障切换时查询不中断、或者干脆是"总行一套、灾备一套"的硬性架构约束------这时候单中心自产自销的 Kafka + Elasticsearch 就不够用了,跨中心的日志同步必须安排上。

跨机房的 Kafka 数据同步本身是个经典命题,社区标准答案是 MirrorMaker 2。但日志平台场景下,对端 Kafka 里躺着的是未解析的原始日志,本中心真正需要的是解析、富化之后落进 ES 的结果数据 ------这个差别决定了照抄 MM2 会走弯路。本文记录一套生产上真实落地的方案:双中心互备 + Flink 中继任务 ,重点讲清楚三件事:为什么不用 MirrorMaker 2、中继任务怎么配、以及两个不踩不知道的坑(消费者组隔离 和副作用开关)。

📚 相关系列:《Kafka 集群开启 SASL 认证实战(一):内置 ZooKeeper 场景》《Kafka 磁盘空间治理实战:retention、log.dirs 扩容与不停机换盘》《云原生日志采集与检索架构实战:Kafka + Elasticsearch》------本文专注跨中心同步维度,单集群认证、磁盘治理、整体采集架构看前几篇。

一、需求画像:什么情况需要跨中心同步日志

先把需求说具体,方便对照你自己的场景:

需求点 具体含义
双中心数据对等 任一中心都能查到两个中心产生的全部日志,不是主备冷拷贝
容灾切换 中心 A 整体故障时,中心 B 的日志平台独立可用,不丢已采集数据
复用解析管道 对端同步过来的是原始日志,本中心要重新走一遍解析、富化、入库,而不是直接搬 ES 快照
副作用不能翻倍 解析管道里挂着统计、告警、数仓推送等旁路,同步链路只准入库,不准重复触发旁路

其中第 3、4 点是日志平台特有的:如果是纯粹的 Kafka 消息镜像场景(比如双活业务消息),MirrorMaker 2 就是标准答案;但一旦"同步的单位不是消息、而是解析结果",选型就要重新算账。

2.1 三条路线对比

维度 MirrorMaker 2 Confluent Replicator 自研 Flink 中继
本质 Kafka Connect 搬运工,字节级镜像 商业版 Connect 搬运工,功能更全 复用现有解析任务,消费→处理→再写入
能否边同步边解析 ❌ 只搬原始字节,schema/格式原样 ❌ 同上 ✅ 全链路解析、富化、字段路由
偏移量管理 内置 checkpoint 连接器做 offset 换算 内置,且支持跨版本 自己的消费组 offset,语义清晰
ACL/配置同步 支持同步 topic 配置 支持 不涉及
许可成本 开源(Kafka 2.4+ 自带) 商业许可 已有 Flink 平台则近乎零增量
运维面 多一套 Connect 集群 同上 复用现有 Flink 任务运维体系

2.2 为什么日志平台选了自研中继

决定性的是两个理由:

  1. 搬过去之后还得再解析一遍。 MM2 把 DC1 的 log_raw 原样搬到 DC2 的 Kafka,DC2 仍然要起一套消费任务解析入库------等于多维护一条"镜像 + 二次消费"链路;而中继任务直接复用解析任务本体,在消费侧把源头指向对端集群即可,一条链路干完。
  2. 旁路开关天然可控。 解析任务里挂着行数统计、CK 推送、算法平台上报、告警推送四条旁路(详见《Flink 日志解析任务性能优化实战》里的任务画像),中继任务可以通过配置开关只保留 ES 写入,避免"DC1 的任务告警了一遍、DC2 的同步任务又告警一遍"。

一句话总结选型:要"数据对等 + 结果复用"选自研中继,要"纯消息镜像 + 多租户自服务"选 MM2。两者不互斥,业务消息镜像和日志中继在我们整套架构里是并存的。

三、双中心互备架构

架构的核心是一句话:每个中心各跑一个中继任务,读对端的原始日志 topic,解析后写本中心的 ES。

复制代码
        ┌─────────────── DC1(中心 1)───────────────┐      ┌─────────────── DC2(中心 2)───────────────┐
        │                                            │      │                                            │
采集 ──▶│ Kafka(log_raw) ──┐                         │      │                         ┌── Kafka(log_raw) │◀── 采集
        │                  │                         │      │                         │                  │
        │           DC1 本地解析任务(组: log-parse)    │      │    DC2 本地解析任务(组: log-parse)           │
        │                  │                         │      │                         │                  │
        │                  ▼                         │      │                         ▼                  │
        │             ES(logs_app)                   │      │                   ES(logs_app)            │
        │                  ▲                         │      │                         ▲                  │
        │  DC2→DC1 中继任务(组: relay-dc2-dc1)        │      │      DC1→DC2 中继任务(组: relay-dc1-dc2)      │
        │  读 DC2 log_raw + 查 DC2 Redis 规则          │      │      读 DC1 log_raw + 查 DC1 Redis 规则       │
        │                  │                         │      │                         │                  │
        └──────────────────│─────────────────────────┘      └─────────────────────────│──────────────────┘
                           └────────────── 专线 ───────────────────────────────────────┘

几个设计要点:

  • 中继任务部署在"写入方":同步 DC1 数据的任务跑在 DC2,读 DC1 的 Kafka;反之亦然。这样解析结果、ES 写入、失败重试都发生在数据最终落地的一侧,排查问题不用跨中心看日志。
  • 两中心任务职责对称 :DC1 启动的任务负责读 DC2 的日志 topic,DC2 启动的任务负责读 DC1 的日志 topic,互为镜像,配置上只差连接信息和对端 Redis 地址。
  • 解析规则跟着源头走 :每条日志按 logId 路由到不同的解析规则,规则缓存在 Redis(键形如 parse_rule_<logId>)。DC1 产生的日志,规则在 DC1 的 Redis 里 ------所以中继任务必须跨中心读对端 Redis,而不是读本中心自己的。这是下面配置里 redisCfg 指向对端的原因。
  • ES 按中心独立索引 :同步过来的日志写入本中心 ES 的 logs_app 索引,索引粒度与容量规划沿用《Elasticsearch 日增 30TB 日志架构实战》的口径,本文不展开。

四、中继任务配置详解

中继任务复用解析任务的主类(com.example.logsync.LogParseJob),通过一份 JSON 任务配置驱动。下面这份配置是部署在 DC2、用来同步 DC1 日志数据的任务,逐段注释:

json 复制代码
{
    "mainClass": "com.example.logsync.LogParseJob",
    "sourceKafkaCfg": {
        "dbSource": "192.168.10.11:9092,192.168.10.12:9092,192.168.10.13:9092",
        "dbSasl": "security.protocol=SASL_PLAINTEXT\nsasl.mechanism=PLAIN\nsasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required  username=\"sync_user\" password=\"<你的密码>\";",
        "topicName": "log_raw",
        "consumeGroup": "relay-dc1-dc2",
        "consumeLocation": "earliest"
    },
    "sinkEsCfg": {
        "dbSource": "192.168.20.11:9200",
        "dbAccount": "elastic",
        "dbPassword": "<你的密码>",
        "indexName": "logs_app",
        "bulkFlushInterval": 1000,
        "bulkFlushMaxActions": 30000
    },
    "redisCfg": {
        "dbSource": "192.168.10.15:6379",
        "dbAccount": "",
        "dbPassword": "<你的密码>"
    },
    "logDataDWS": {
        "dataCenter": "DC1",
        "appCode": "APP01",
        "servicCode": "LOGSVC"
    },
    "switchCfg": {
        "isFilterTime": true,
        "saveToStorage": false,
        "countLineStats": false,
        "pushToCk": false,
        "pushToAlgorithm": false,
        "pushAlarm": false,
        "writeToEs": true
    },
    "taskName": "log-relay-dc1-to-dc2",
    "parallelism": 8
}

逐段拆开讲:

sourceKafkaCfg------源头是对端集群。 dbSource 填的是 DC1 (对端)的 broker 地址;跨中心网络走专线,Kafka 大版本一致时客户端协议兼容性没问题。SASL 认证参数的逐项含义和 JAAS 配置细节见《Kafka 集群开启 SASL 认证实战》,这里不重复。两个参数是跨中心同步的命门:

  • consumeGroup:必须与对端本地任务(以及一切消费该 topic 的任务)不同组,原因见第五节踩坑一;
  • consumeLocation:earliest 表示消费组第一次 启动时从最早 offset 拉取(全量回补),之后重启就从已提交 offset 续传------日志同步场景一般要全量,选 latest 会漏掉积压的历史数据。

sinkEsCfg------写本中心 ES。 地址是 DC2 自己的 ES;bulkFlushInterval=1000 + bulkFlushMaxActions=30000 是批量写入的吞吐与延迟折中(1 秒或攒满 3 万条触发 flush)。这两个值的调优思路在《Flink 日志处理吞吐量实测》里详细算过账。

redisCfg------解析规则在对端。 再强调一次:这个 Redis 是 DC1 的 (对端),不是 DC2 本地的。中继任务解析 DC1 日志时,要按 logId 查 DC1 Redis 里的 parse_rule_<logId> 拿解析规则。跨中心 Redis 读取的 RTT 会比本地高,好在规则有本地缓存兜底,只有缓存未命中才回源,实测对吞吐影响可忽略(缓存机制正是《Flink 日志解析任务性能优化实战》里优化的重点之一)。

switchCfg------中继任务的灵魂。 原则只有一条:除了 ES 写入,旁路全关。每个开关关掉的是什么:

开关 关掉后的效果 如果不关会怎样
writeToEs: true 唯一保留:解析结果写本中心 ES ------
saveToStorage: false 不再写独立存储 对端已经写了一份,重复落盘,磁盘双倍消耗
countLineStats: false 不做日志行数统计 行数指标翻倍,大屏统计失真
pushToCk: false 不往 ClickHouse 数仓主题推数 数仓侧双份录入,聚合指标全错
pushToAlgorithm: false 不向算法平台 HTTP 上报 同一批日志被算法平台分析两遍,浪费算力且结果重复
pushAlarm: false 不往告警中间主题推送 同一故障告警两遍,值班直接被轰炸
isFilterTime: true 保留时间过滤(过滤窗口外的迟到数据) ------

五、两个必踩的坑

坑一:消费者组不隔离 = 数据被"截胡"

这是整个方案里最反直觉、也最容易出事故的点。

Kafka 的消费语义是消费组内分摊分区 :同一个消费组里,一个分区同一时刻只会被一个消费者消费。中继任务虽然跑在 DC2,但它连的是 DC1 的 broker 、消费的是 DC1 的 topic ------如果它的 consumeGroup 与 DC1 本地解析任务撞了名,在 DC1 集群看来这就是"同一个组多了几个成员",于是触发再均衡:topic 的分区被 DC1 本地任务和 DC2 中继任务瓜分。

后果是双向的:DC1 本地任务只拿到一部分分区(解析结果缺数),DC2 中继也只拿到另一部分(同步结果也缺数)------两边各缺一半,而且没有任何报错,lag 监控看起来还都正常消费着,直到有人发现 ES 里的数据对不上账。

所以配置铁律:中继任务的消费组命名必须全局唯一且可辨识 ,比如按方向命名 relay-dc1-dc2、relay-dc2-dc1,一眼能看出这个组是哪个方向的中继。命名规范本身就是防御------半年后新同事起任务时,看到已有组的命名就知道该避开什么。

坑二:副作用开关漏关 = 统计翻倍、告警轰炸

第二节表格里"如果不关会怎样"一列,每一条都是真实业务后果。这里补充一个更容易漏的场景:中继任务是从本地解析任务的配置复制出来的,复制过来的配置里所有开关都是开的------忘了关,任务一启动,告警、统计、数仓推送全在同步链路上重放一遍。

推荐的做法是把中继配置模板化:switchCfg 里除了 writeToEs 一律写死 false,模板里其他字段(连接信息、消费组、任务名)用占位符,按方向填充。这样配置评审时只需要看占位符填得对不对,不用每次都人肉核对开关清单。

坑二点五:网络开通清单别想当然

中继任务要跨中心访问的资源比想象中多,上线前照着这个清单找网络组开防火墙:

方向 资源 端口 用途
DC2 → DC1 Kafka broker ×3 9092 消费原始日志
DC2 → DC1 Redis 6379 查解析规则(缓存未命中时回源)
DC2 → DC2 本中心 ES 9200 写入解析结果(本地,一般已开)

漏开 Redis 是高频事故:任务能起来、能消费,但所有日志都解析失败进了死信------因为规则查不到。而且缓存未命中的回源是运行中才发生的,联调时如果恰好命中了缓存(或者测试环境规则在本中心 Redis 里有副本),问题会被掩盖到生产才爆出来。

六、监控与对账

跨中心链路的双保险:

Lag 监控要盯两套集群。 中继任务的 lag 是在对端 集群上积累的(消费组注册在 DC1),所以监控大盘必须把 DC1 上 relay-dc1-dc2 组的 lag 和 DC2 上 relay-dc2-dc1 组的 lag 都画出来。跨中心专线抖动时,两个方向的 lag 会不对称地上涨,这是定位"专线问题还是任务问题"的第一现场。lag 告警阈值建议按峰值流量×可容忍延迟折算,而不是拍一个固定值(吞吐基准见《Flink 日志处理吞吐量实测》)。

对账用 ES doc count。 同步是否完整,最终以两端 ES 的文档数为准:按天对 logs_app 索引做 count 聚合,DC2 的"DC1 来源数据"应等于 DC1 本地的量(字段上靠 dataCenter 标记区分来源)。对账脚本每天跑一次,差异超过阈值(比如 0.1%)就告警------这是发现坑一那种"静默缺数"的唯一可靠手段。

容量联动。 同步链路会让对端 Kafka 的 log_raw 多一个全量消费者,retention 期间的数据被读两遍本身不增加磁盘压力,但如果为了中继回补把 retention 拉长,就要重新算磁盘账,方法见《Kafka 磁盘空间治理实战》。

七、避坑清单(拿走不谢)

  1. 中继任务消费组必须与一切现有消费组隔离,按方向命名(relay-dc1-dc2);
  2. switchCfg 除 writeToEs 外全部关掉,配置模板化防止复制遗漏;
  3. redisCfg 指向对端 Redis------解析规则跟着日志源头走;
  4. consumeLocation=earliest 全量回补,重启续传靠已提交 offset,别选 latest;
  5. 防火墙清单:对端 9092(Kafka)+ 对端 6379(Redis),Redis 漏开只会在缓存失效后爆雷;
  6. lag 监控画在对端集群上,两个方向分开告警;
  7. 每天用 ES doc count 对账,静默缺数靠对账发现,不靠报错;
  8. 选型先想清楚:要"解析结果对等"用中继,要"消息镜像"用 MirrorMaker 2,两者可并存。

你们的跨中心(跨机房)日志是靠 MirrorMaker 2、商业工具还是自研中继?消费者组隔离这个坑踩过的举个手,评论区聊聊你们的双中心架构。


方案来自生产环境落地的双中心日志平台(Flink + Kafka + Elasticsearch);文中 IP、密码、任务名均为示例。

相关推荐
djjjx.1 小时前
Linux进程篇(十二):从零构建属于你的自定义 Shell 解释器
linux·运维·服务器
北风toto2 小时前
Elasticsearch 8.2.3 快照迁移实战:从备份到恢复的完整指南
大数据·elasticsearch·jenkins
云川之下2 小时前
[ensp] 交换机入门配置例子(带vlan)
运维·服务器·网络
roman_日积跬步-终至千里2 小时前
数仓方法论(二):指标设计与规范化——让经营目标成为共同的衡量规则
大数据
CS_Zero2 小时前
安卓开发:Linux的android工具命令
android·linux·运维
Kari112 小时前
Dify多租户架构:从单团队到SaaS化服务
大数据·人工智能·架构
何中应3 小时前
Grafana如何安装插件
运维·grafana·可观测
女神下凡3 小时前
芯参谋(84):Linux eMMC 驱动开发指南
linux·运维·arm开发·驱动开发·单片机·嵌入式硬件