【生产级 Kafka (KRaft) 双中心容灾演练:MirrorMaker 2.0 (MM2) 核心参数配置与回切踩坑指南】

文章目录

    • 概要
    • 一、核心技术栈
    • 二、集群搭建
      • [1. server.properties配置](#1. server.properties配置)
      • [2. 基础配置,无 SASL 认证](#2. 基础配置,无 SASL 认证)
      • [3. 创建用户](#3. 创建用户)
      • [实战小 Tips](#实战小 Tips)
        • [1. KRaft 节点通信权限排坑](#1. KRaft 节点通信权限排坑)
          • [1.1 【现象描述】](#1.1 【现象描述】)
          • [1.2 【原因分析】](#1.2 【原因分析】)
          • [1.3 【填坑指南】](#1.3 【填坑指南】)
    • 三、双集群同步
      • [1. connect-mirror-maker.properties配置](#1. connect-mirror-maker.properties配置)
      • [2. 配置解析和踩坑合集](#2. 配置解析和踩坑合集)
        • [2.1 多节点 MM2 集群协同](#2.1 多节点 MM2 集群协同)
        • [2.2 调优同步时长](#2.2 调优同步时长)
        • [2.3 元数据"跨省办公" (Timeout 超时)](#2.3 元数据“跨省办公” (Timeout 超时))
        • [2.4 MM2 的"僵尸大脑" (旧配置残留)](#2.4 MM2 的“僵尸大脑” (旧配置残留))
    • [四、"同步进度仪" - 脚本](#四、“同步进度仪” - 脚本)
      • [1. kafka_failback_check.sh](#1. kafka_failback_check.sh)
    • 小结
    • 说明

概要

为什么要做双中心容灾?在金融、政务等对数据可靠性极高的场景中,单机房故障是不可接受的。本文记录了基于 Kafka (KRaft)MirrorMaker 2.0 (MM2) 实现"主备(Active-Passive)"容灾的完整历程。我们的核心目标是: DNS 切换后业务无感、Topic 名字不变、消费位点精准衔接。


一、核心技术栈

  • 版本 : Kafka 3.9 (KRaft Mode, 无 ZooKeeper )
  • 同步引擎 : MirrorMaker 2.0 (MM2)
  • 安全认证 : SASL/SCRAM-SHA-512
  • 同步策略 : IdentityReplicationPolicy (保持 Topic 名称跨集群一致)

二、集群搭建

(具体服务器安装步骤不在此赘述,本文仅做配置分享)

1. server.properties配置

以每个集群三台节点为例, kraft 下的 server.properties 配置

bash 复制代码
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# This configuration file is intended for use in KRaft mode, where
# Apache ZooKeeper is not present.
#

############################# Server Basics #############################

# The role of this server. Setting this puts us in KRaft mode
process.roles=broker,controller

# The node id associated with this instance's roles
# 三台分别不同node.id
node.id=1

# The connect string for the controller quorum
controller.quorum.voters=1@A1节点_ip:9093,2@A2节点_ip:9093,3@A3节点_ip:9093

############################# Socket Server Settings #############################

# The address the socket server listens on.
listeners=SASL_PLAINTEXT://:9092,CONTROLLER://:9093,PLAINTEXT://:9091

# Name of listener used for communication between brokers.
inter.broker.listener.name=PLAINTEXT

# Listener name, hostname and port the broker or the controller will advertise to clients.
# 注意:在不同节点部署时,替换为当前节点的真实 IP
advertised.listeners=SASL_PLAINTEXT://A1节点_ip:9092,PLAINTEXT://A1节点_ip:9091

# A comma-separated list of the names of the listeners used by the controller.
controller.listener.names=CONTROLLER

# Maps listener names to security protocols
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL

sasl.enabled.mechanisms=SCRAM-SHA-512
sasl.mechanism.inter.broker.protocol=PLAINTEXT

# 启用 KRaft 模式的 ACL 授权器
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
# 超级用户
super.users=User:admin
# 无 ACL 时是否允许所有用户访问(生产建议 false,确保安全)
allow.everyone.if.no.acl.found=false

# 线程配置优化(应对高并发)
num.network.threads=8
num.io.threads=16

# 缓冲区配置
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600


############################# Log Basics #############################

# 数据存储目录
log.dirs=/data/kafka-logs

# 默认分区数(建议设为 3,提高并行度)
num.partitions=3

# 日志恢复线程
num.recovery.threads.per.data.dir=2

############################# Internal Topic Settings  #############################
# 内部 Topic 副本数全部设为 3,确保高可用
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2

# 全局默认副本数(确保以后新建 Topic 默认就是高可用的)
default.replication.factor=3

############################# Log Flush Policy #############################

# 刷盘策略(保持默认或根据 IO 压力微调)
log.flush.interval.messages=10000
log.flush.interval.ms=1000

############################# Log Retention Policy #############################

# 日志保留策略
# 保留时间:168 小时(7 天)
log.retention.hours=168
# 单个 Segment 大小:1GB
log.segment.bytes=1073741824
# 检查周期
log.retention.check.interval.ms=300000

注意点 :(A、B集群都可参照当前配置)

1️⃣ 集群内node.id 区分

2️⃣ controller.quorum.voters 替换为B集群的各节点ip

3️⃣ 可以先基础配置,把集群运行起来再相应创建用户,增加SASL_PLAINTEXT认证

2. 基础配置,无 SASL 认证

bash 复制代码
# --- 节点基本角色 ---
process.roles=broker,controller
node.id=1
controller.quorum.voters=1@A1节点_ip:9093,2@A2节点_ip:9093,3@A3节点_ip:9093

# --- 监听协议配置 (纯 PLAINTEXT) ---
# 9092 用于业务数据,9093 用于控制器内部通信
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
inter.broker.listener.name=PLAINTEXT
advertised.listeners=PLAINTEXT://A1节点_ip:9092  # 换成你的真实 IP
controller.listener.names=CONTROLLER

# --- 权限配置 (针对 ANONYMOUS 提权) ---
# 允许匿名用户拥有所有权限,确保测试阶段不报权限错
allow.everyone.if.no.acl.found=true
# 关键 Tips:将匿名用户设为超级管理员,防止元数据同步卡死
super.users=User:ANONYMOUS

# --- 日志存储 ---
log.dirs=/data/kafka-logs

# --- 默认分区与副本 ---
num.partitions=3
default.replication.factor=3
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2

# --- 性能优化建议 ---
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
group.initial.rebalance.delay.ms=0

3. 创建用户

命令:

bash 复制代码
# 在 A 集群或 B 集群的任意一个节点执行
./bin/kafka-configs.sh --bootstrap-server 当前ip:9092 \
  --alter --add-config 'SCRAM-SHA-512=[password=你的密码]' \
  --entity-type users --entity-name admin

为了让 kafka-topics.shkafka-console-consumer.sh 等运维脚本能够连上开启了安全认证的集群,需要创建一个专门的客户端配置文件。文件路径 : ${KAFKA_HOME}/config/client.properties

新增 client.properties 配置文件

bash 复制代码
# 安全协议:由于我们没配 SSL,所以使用明文 SASL
security.protocol=SASL_PLAINTEXT
# 认证机制
sasl.mechanism=SCRAM-SHA-512
# 具体的用户名和密码配置
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
    username="admin" \
    password="你的密码";

实战小 Tips

1. KRaft 节点通信权限排坑
1.1 【现象描述】

Kafka (KRaft) 搭建初期,如果还没来得及配置 SASL 认证(即使用 PLAINTEXT 协议),启动集群后可能会发现节点之间无法同步元数据,甚至报错 TopicAuthorizationException 。查看日志会发现, Kafka 识别到的操作者身份是 User:ANONYMOUS

1.2 【原因分析】

Kafka 的安全模型中, User:ANONYMOUS 是所有未认证连接的默认身份。如果在 server.properties 中开启了权限严格模式( allow.everyone.if.no.acl.found=false ),那么即便是集群内部节点之间的"自己人说话",也会因为没有权限而被拦截,导致集群处于"半死不活"的状态。

1.3 【填坑指南】

在测试阶段,为了快速跑通集群,可以将这个"匿名访客"临时提拔为"超级管理员",在 server.properties 中临时增加:
super.users=User:admin;User:ANONYMOUS


三、双集群同步

仅在B(备)集群启动MM2

1. connect-mirror-maker.properties配置

bash 复制代码
# Licensed to the Apache Software Foundation (ASF) under A or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
# 
#    http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# specify any number of cluster aliases
clusters = B, A

# connection information for each cluster
A.bootstrap.servers = A1节点_ip:9092,A2节点_ip:9092,A3节点_ip:9092
B.bootstrap.servers = B1节点_ip:9092,B2节点_ip:9092,B3节点_ip:9092

# A Cluster Security Settings
A.security.protocol=SASL_PLAINTEXT
A.sasl.mechanism=SCRAM-SHA-512
A.sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="admin" password="你的密码";

# B Cluster Security Settings
B.security.protocol=SASL_PLAINTEXT
B.sasl.mechanism=SCRAM-SHA-512
B.sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="admin" password="你的密码";

# enable and configure individual replication flows
A->B.enabled = true

# regex which defines which topics gets replicated.
A->B.topics = .*
A->B.topics.exclude = __.*, _.*
A->B.groups = .*

# 显式指定 A->B 的同步频率
A->B.sync.group.offsets.enabled = true
A->B.sync.group.offsets.interval.seconds = 10
A->B.emit.checkpoints.enabled = true
A->B.emit.checkpoints.interval.seconds = 10
A->B.sync.topic.configs.enabled = true
A->B.sync.topic.acls.enabled = true
A->B.refresh.topics.interval.seconds = 30
A->B.refresh.groups.interval.seconds = 30

# 关键:将位点同步 Topic 强制保存在目标集群 (B)
A->B.offset-syncs.topic.location = target

# 核心策略(DNS 切换的关键:保持 Topic 名字一致)
replication.policy.class = org.apache.kafka.connect.mirror.IdentityReplicationPolicy

# 位点与配置同步
emit.checkpoints.enabled = true
sync.topic.configs.enabled = true
sync.topic.acls.enabled = true
emit.heartbeats.enabled = true

# --- 生产增强配置 ---
# 开启消费位移同步(实现业务平滑切换的核心)
sync.group.offsets.enabled = true
sync.group.offsets.interval.seconds = 10
emit.checkpoints.interval.seconds = 10
# 缩短新 Topic 和新消费组发现间隔(默认 10 分钟太长)
refresh.topics.interval.seconds = 30
refresh.groups.interval.seconds = 30
# 提高并行度,应对千万级数据量
tasks.max = 4

# 开启内部 REST 通信(解决多节点 MM2 集群协同报错)
dedicated.mode.enable.internal.rest = true

# 故障回切流(平时注释掉,仅在从 B 回切 A 时开启)
#B->A.enabled = true
#B->A.topics = .*
#B->A.topics.exclude = __.*, _.*
#B->A.groups = .*
# 显式指定 B->A 的同步频率
#B->A.sync.group.offsets.enabled = true
#B->A.sync.group.offsets.interval.seconds = 10
#B->A.emit.checkpoints.enabled = true
#B->A.emit.checkpoints.interval.seconds = 10
#B->A.sync.topic.configs.enabled = true
#B->A.sync.topic.acls.enabled = true
#B->A.refresh.topics.interval.seconds = 30
#B->A.refresh.groups.interval.seconds = 30

# Setting replication factor of newly created remote topics
replication.factor = 3

############################# Internal Topic Settings  #############################
# 内部元数据 Topic 全部设为 3 副本,确保高可用
checkpoints.topic.replication.factor = 3
heartbeats.topic.replication.factor = 3
offset-syncs.topic.replication.factor = 3

# Connect 内部存储 Topic 也要设为 3
offset.storage.replication.factor = 3
status.storage.replication.factor = 3
config.storage.replication.factor = 3

# 强制 MM2 把自己的"大脑"存在 B 集群,不要去连 A 集群存配置
config.storage.topic = mm2-configs.internal
offset.storage.topic = mm2-offsets.internal
status.storage.topic = mm2-status.internal


# customize as needed
replication.policy.separator = 
sync.topic.acls.enabled = true
emit.heartbeats.interval.seconds = 5

2. 配置解析和踩坑合集

2.1 多节点 MM2 集群协同

【现象】 :当你部署了 3 个节点的 MM2 想要实现高可用时,发现日志里频繁出现 NotLeaderException ,且同步任务在 3 台机器间不停地"漂移",位点同步极不稳定。
【踩坑点】MM2 默认的协同机制完全依赖 Kafka Topic,在高并发同步场景下极易产生元数据视图不一致。
【解法】 :必须显式开启内部 REST 通信:
dedicated.mode.enable.internal.rest = true

2.2 调优同步时长

【现象】 :A集群的topicgroup的位点数据很久没有同步到B集群里
【踩坑点】 :默认MM2同步到时间是10分钟

【解法】

bash 复制代码
# 开启消费位移同步(实现业务平滑切换的核心)
sync.group.offsets.enabled = true
sync.group.offsets.interval.seconds = 10
emit.checkpoints.interval.seconds = 10
# 缩短新 Topic 和新消费组发现间隔(默认 10 分钟太长)
refresh.topics.interval.seconds = 30
refresh.groups.interval.seconds = 30
# 提高并行度,应对千万级数据量
tasks.max = 4
2.3 元数据"跨省办公" (Timeout 超时)

【现象】 :位点同步半小时不动,日志里全是 Timeout while loading consumer groups
【踩坑点】 :默认 MM2 在 A 集群存配置。部署在 B 的 MM2 每次都要跨网络连 A 读写配置,网络稍有延迟就卡死。(默认逻辑 :MM2 会选择 clusters 定义中的 第一个名字 作为其元数据的 主存储集群 。)
【解法】"去 A 依赖"架构 。通过 clusters = B, A 交换顺序,并钉死 config.storage.topic 到 B 集群本地。

bash 复制代码
clusters = B, A

# 强制 MM2 把自己的"大脑"存在 B 集群,不要去连 A 集群存配置
config.storage.topic = mm2-configs.internal
offset.storage.topic = mm2-offsets.internal
status.storage.topic = mm2-status.internal
2.4 MM2 的"僵尸大脑" (旧配置残留)

【现象】 :当A集群恢复,需要把B集群的数据同步到A集群时,已经在.properties 里注释掉了 B->A ,但重启 MM2 后,日志里依然疯狂报错 clientId=B->A
【踩坑点】MM2 (Connect) 会把配置存在 A 集群的 mm2-configs 内部 Topic 里,配置文件的注释无法覆盖"历史记忆"。
【解法】 :物理清场大法 。停止 MM2,在 A 和 B 两端手动删除 所有 mm2- 开头的元数据 Topic

命令:

bash 复制代码
#A集群执行
./bin/kafka-topics.sh --bootstrap-server A节点_ip:9092 --delete --topic mm2-configs.internal --command-config config/client.properties
./bin/kafka-topics.sh --bootstrap-server A节点_ip:9092 --delete --topic mm2-offsets.internal --command-config config/client.properties
./bin/kafka-topics.sh --bootstrap-server A节点_ip:9092 --delete --topic mm2-status.internal --command-config config/client.properties

#B集群执行
./bin/kafka-topics.sh --bootstrap-server B节点_ip:9092 --delete --topic mm2-configs.internal --command-config config/client.properties
./bin/kafka-topics.sh --bootstrap-server B节点_ip:9092 --delete --topic mm2-offsets.internal --command-config config/client.properties
./bin/kafka-topics.sh --bootstrap-server B节点_ip:9092 --delete --topic mm2-status.internal --command-config config/client.properties
./bin/kafka-topics.sh --bootstrap-server B节点_ip:9092 --delete --topic mm2-offset-syncs.internal --command-config config/client.properties
./bin/kafka-topics.sh --bootstrap-server B节点_ip:9092 --delete --topic A.checkpoints.internal --command-config config/client.properties

注意 :当同步完,切回 A->B ,也需要遵循,停MM2 -> 物理清场 -> 改配置 -> 重启


四、"同步进度仪" - 脚本

1. kafka_failback_check.sh

javascript 复制代码
#!/bin/bash
# Kafka 双中心回切辅助脚本 (v1.0)
# 作用:实时检查 B -> A 的同步延迟 (LAG)

# --- 配置区域 ---
# 自动获取脚本所在目录的上级目录作为 KAFKA_HOME
KAFKA_HOME=$(cd "$(dirname "$0")"/..; pwd)
BOOTSTRAP_SERVER_B="B1节点_ip:9092,B2节点_ip:9092,B3节点_ip:9092"
CLIENT_CONFIG="${KAFKA_HOME}/config/client.properties"
# MM2 内部用于同步 B 到 A 的消费组名(MirrorSourceConnector 默认命名规则)
GROUP_NAME="mm2-B->A" 

echo "--------------------------------------------------"
echo "Kafka 双中心回切监控工具 v1.0"
echo "正在检测 B -> A 同步状态..."
echo "按 Ctrl+C 退出监控"
echo "--------------------------------------------------"

# 检查配置文件是否存在
if [ ! -f "$CLIENT_CONFIG" ]; then
    echo "错误: 找不到配置文件 $CLIENT_CONFIG"
    exit 1
fi

while true; do
    # 获取消费组详细信息
    # 使用 --describe 获取位点信息
    RAW_RESULT=$(${KAFKA_HOME}/bin/kafka-consumer-groups.sh --bootstrap-server $BOOTSTRAP_SERVER_B --describe --group $GROUP_NAME --command-config $CLIENT_CONFIG 2>/dev/null)
    
    # 检查命令是否执行成功(如果组还没创建,说明同步流还没跑起来)
    if [ $? -ne 0 ] || [ -z "$RAW_RESULT" ]; then
        echo "$(date "+%Y-%m-%d %H:%M:%S") [WAIT] 尚未发现同步组 $GROUP_NAME,请确保已取消 B->A 注释并重启 MM2"
        sleep 5
        continue
    fi

    # 提取 LAG 列(第6列)并进行求和计算
    # NR>1 过滤表头,NF>0 过滤空行
    TOTAL_LAG=$(echo "$RAW_RESULT" | awk 'NR>1 && NF>0 {sum+=$6} END {print sum}')
    
    # 获取当前时间
    TIME=$(date "+%Y-%m-%d %H:%M:%S")
    
    # 根据 LAG 大小输出不同的提示
    if [ -z "$TOTAL_LAG" ]; then
        echo "[$TIME] [WARN] 无法获取 LAG 信息,请检查同步流状态。"
    elif [ "$TOTAL_LAG" -eq 0 ]; then
        echo "[$TIME] [SUCCESS] 延迟已追平 (LAG=0),数据完全一致,可以安全回切 A 集群!"
    elif [ "$TOTAL_LAG" -lt 100 ]; then
        echo "[$TIME] [INFO] 延迟极低 (LAG=$TOTAL_LAG),已进入可切换窗口期。"
    else
        echo "[$TIME] [SYNCING] 正在同步数据... 剩余待同步消息: $TOTAL_LAG"
    fi
    
    sleep 5
done

脚本检查的是 B 集群 :

  • 原因 :在 MM2 执行 B -> A 同步时,MM2 实际上是作为 B 集群的一个消费者 存在的。它从 B 集群读取数据,然后写入 A 集群。
  • 逻辑 :脚本通过查询 B 集群上的同步任务消费组( mm2-B->A )来获取位点信息。
    • LOG-END-OFFSET :B 集群当前最新的数据量。
    • CURRENT-OFFSET :MM2 已经从 B 读取并成功发送到 A 的数据量。
    • LAG :B 集群里还有多少消息 没来得及 传给 A。

小结

双中心容灾需要的"三层境界"

第一层:数据能通 (Connectivity)

第二层:位点精准 (Consistency)

第三层:架构去中心化 (Resilience)

结论 :真正做到了"主中心 A 哪怕物理毁灭,备中心 B 依然能凭本地元数据独立存活"。


说明

文中如有疑问欢迎讨论、指正,互相学习,感谢关注💡。

相关推荐
softshow10264 小时前
SpringCloud Redis与分布式
redis·分布式·spring cloud
学渣y4 小时前
git分布式版本控制系统
分布式·git·elasticsearch
天天进步20155 小时前
源码级优化:Graphiti 的并发处理与分布式记忆存储架构
人工智能·分布式·架构
BPM_宏天低代码7 小时前
宏天CRM系统的消息中心:基于RabbitMQ的实践
分布式·rabbitmq
zs宝来了7 小时前
Kafka 消费者组原理:Rebalance 与消息分配策略
kafka·消费者组·rebalance·消息分配
二宝1528 小时前
互联网大厂Java面试实战演练:谢飞机的三轮提问与深入解析
java·spring boot·redis·微服务·面试·kafka·oauth2
qq_297574678 小时前
【Kafka系列·入门第四篇】Kafka实操入门:环境部署(Windows/Linux)+ 简单消息收发
linux·windows·kafka
2501_933329558 小时前
企业级舆情监测系统技术选型指南:Infoseek AI中台架构解析与实践评估
人工智能·分布式·重构·架构
chunyublog8 小时前
HBase 2.4.18 分布式集群搭建教程(适配 Hadoop 3.3.4 + ZooKeeper 3.5.6)
hadoop·分布式·hbase