4. Kafka 高可用集群:从 Broker 部署到跨机房复制
Kafka 的高可用,说白了就是保证「数据不丢、服务不断」。我在多个企业的 CDC 项目中见过各种 Kafka 翻车现场——有的因为副本数配少了,一台 Broker 宕机直接丢分区;有的因为 ISR 机制没吃透,生产环境频繁「缩圈」导致写入超时。这一章,我把 Broker 集群部署、副本因子、ISR、Leader 选举、日志压缩和跨机房复制这几个核心点串起来讲,全是实战里踩过的坑。
4.1 Broker 集群部署:别在第一步就埋雷
部署 Kafka 集群,我个人的习惯是「先定规模,再定配置」。你想想看,如果一开始 Broker 数量就拍脑袋定,后面扩缩容会非常痛苦。
4.1.1 节点数量怎么定?
- 最小集群:3 台 Broker。这是能容忍 1 台宕机的最小规模(配合副本因子 3)。
- 生产推荐:5~7 台。既能保证多数派选举,又能承受滚动升级时的短暂失联。
- 超大规模:超过 20 台时,建议按数据中心或业务域拆分集群,避免 Zookeeper 压力过大。
4.1.2 关键配置清单
| 配置项 | 推荐值 | 说明 |
|---|---|---|
broker.id |
0, 1, 2 ... | 必须唯一,建议用数字序列,不要用 IP 最后一段 |
log.dirs |
/data1/kafka,/data2/kafka | 多磁盘挂载,提升 IO 吞吐;我习惯至少 2 块盘 |
num.partitions |
3~6(按吞吐量调) | 默认值,创建 Topic 时可覆盖 |
default.replication.factor |
3 | 生产环境不要低于 3 |
zookeeper.connect |
zk1:2181,zk2:2181,zk3:2181 | 至少 3 个 ZK 节点,避免单点 |
4.2 副本因子与 ISR 机制:数据安全的最后防线
副本因子决定了每个分区有多少份数据。ISR(In-Sync Replicas)则是「当前跟 Leader 保持同步的副本集合」。这两个概念必须一起理解。
4.2.1 副本因子怎么选?
- 副本因子 = 1:数据无冗余,Broker 宕机即丢数据。仅用于开发测试。
- 副本因子 = 2:能容忍 1 台宕机,但如果那台刚好是 Leader,且 Follower 落后太多,仍有丢数据风险。
- 副本因子 = 3:生产标配。容忍 1 台宕机,且多数派选举时不会阻塞。
- 副本因子 > 3:写入放大严重,一般用于金融、监管等极端场景。
4.2.2 ISR 的「缩圈」逻辑
ISR 不是一成不变的。Follower 如果长时间没有向 Leader 拉取数据(由 replica.lag.time.max.ms 控制,默认 30 秒),就会被踢出 ISR。为什么会这样?因为 Leader 要保证「写入了 ISR 中所有副本的数据才是已提交的」。如果某个 Follower 太慢,Leader 不能等它,否则写入延迟会飙升。
我在项目中遇到过一个问题:某个 Follower 因为网络抖动频繁进出 ISR,导致 Leader 不断调整 highWatermark,消费者端出现大量重复消费。后来把 replica.lag.time.max.ms 从 30 秒调到 60 秒,问题缓解了很多。嗯,这里要注意——调大这个值会降低数据可靠性,需要权衡。
4.2.3 min.insync.replicas 的陷阱
这个配置决定了「至少要有多少个 ISR 副本确认写入,才算成功」。如果 min.insync.replicas = 2,而当前 ISR 只有 1 个副本,写入就会报 NotEnoughReplicasException。
min.insync.replicas 设成 3,但副本因子也是 3。结果一台 Broker 宕机后,ISR 只剩 2 个,所有写入全部失败。正确的做法是:min.insync.replicas 应该小于副本因子,比如副本因子 3 时,设成 2。
4.3 Leader 选举:谁说了算?
Kafka 的 Leader 选举依赖 Zookeeper 的临时节点和 Watch 机制。当一个 Leader 宕机时,Controller(由 ZK 选举产生)会从 ISR 中选出一个新 Leader。
4.3.1 选举流程
- Leader 宕机,ZK 上的临时节点消失。
- Controller 感知到变化,查询该分区的 ISR 列表。
- 从 ISR 中选出第一个副本作为新 Leader(按 broker.id 排序)。
- 新 Leader 开始接收读写请求,并更新 ZK 上的 Leader 信息。
4.3.2 如果 ISR 为空怎么办?
这是最危险的情况。如果所有副本都宕机了,Kafka 有两种策略:
- 等待 ISR 中任意副本恢复:默认行为,保证数据一致性,但服务不可用。
- 允许从非 ISR 副本选举:通过
unclean.leader.election.enable=true开启。这会丢失数据,但能快速恢复服务。
unclean.leader.election.enable=false,宁可等几分钟,也不丢数据。但如果你的业务可以容忍少量数据丢失,比如日志采集,可以开启。
4.4 数据持久化与日志压缩
Kafka 的数据持久化不是简单的写文件。它用「分段日志 + 索引」的方式,实现了高效的读写和清理。
4.4.1 日志分段与清理策略
| 策略 | 配置 | 适用场景 |
|---|---|---|
| 基于时间 | log.retention.hours |
默认 7 天,适合大部分 CDC 场景 |
| 基于大小 | log.retention.bytes |
按磁盘容量控制,防止写满 |
| 日志压缩 | cleanup.policy=compact |
保留每个 Key 的最新值,适合状态同步 |
4.4.2 日志压缩的工作原理
日志压缩不是实时发生的。Kafka 后台有一个 Cleaner 线程,它会扫描日志段,对相同 Key 只保留最新的一条记录。你想想看,如果 CDC 同步的是数据库的变更日志,每条记录都有主键作为 Key,压缩后只保留每个主键的最新状态,这样消费者就能拿到「当前快照」,而不需要重放所有历史变更。
log.cleaner.min.cleanable.ratio(默认 0.5)的脏数据比例。如果你的 Topic 写入量很大,压缩可能跟不上。
4.5 跨机房复制:MirrorMaker 2 实战
跨机房复制是 CDC 高可用的最后一块拼图。我参与过的一个金融项目,要求两个数据中心之间延迟不超过 5 秒,且数据不能丢。我们最终选了 MirrorMaker 2(MM2)。
4.5.1 MM2 的核心架构
MM2 本质上是一个 Kafka Connect 集群。它从源集群消费数据,然后写入目标集群。和旧版 MirrorMaker 相比,MM2 最大的改进是:
- 自动创建 Topic:目标集群会自动创建与源集群同名的 Topic,并保持分区数一致。
- 偏移量同步:通过内部 Topic
mm2-offsets记录消费进度,支持断点续传。 - 双向复制:支持 Active-Active 模式,但需要处理数据冲突。
4.5.2 配置示例
# mm2.properties
clusters = source, target
source.bootstrap.servers = src-kafka1:9092,src-kafka2:9092
target.bootstrap.servers = tgt-kafka1:9092,tgt-kafka2:9092
source->target.enabled = true
source->target.topics = .* # 复制所有 Topic
# 偏移量同步
sync.topic.acls.enabled = false
emit.checkpoints.enabled = true
checkpoints.topic.replication.factor = 3
source->target.tasks.max=10 来增加并行度。但要注意,目标集群的写入压力也会随之增大。
4.5.3 跨机房复制的避坑指南
- 网络延迟:跨机房带宽和延迟是硬约束。我曾经遇到过因为专线抖动,MM2 频繁重连,导致数据积压。后来加了重试和告警,才稳定下来。
- 数据冲突:Active-Active 模式下,两个机房同时修改同一条数据,会导致冲突。MM2 没有内置冲突解决机制,需要业务层处理。
- 监控:一定要监控 MM2 的 consumer lag 和 connector 状态。我习惯用 Prometheus + Grafana 做可视化,一旦 lag 超过阈值就告警。
4.6 本章知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到,从 Broker 部署到跨机房复制,每一层都在解决「高可用」的不同维度问题。