文章目录
-
- 概要
- 一、核心技术栈
- 二、集群搭建
-
- [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集群的各节点ip3️⃣ 可以先基础配置,把集群运行起来再相应创建用户,增加
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.sh 或 kafka-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集群的
topic和group的位点数据很久没有同步到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 依然能凭本地元数据独立存活"。
说明
文中如有疑问欢迎讨论、指正,互相学习,感谢关注💡。