当前位置: 首页 > news >正文

kafka生产端和消费端的僵尸实例以及解决办法

一 生产端僵尸

1.1 原因

一个生产者实例(producer)在发送消息过程中发生故障(如进程崩溃、网络隔离),但可能未被外部系统及时检测到。过一段时间后,该生产的新实例可能被重启(例如,在容器化环境中被调度器重启,或者运维手动重启)

1.2 问题

问题1:如果故障前的生产者实例在崩溃前可能已经成功发送了部分消息(但未完成事务提交),而新实例并不知道,它可能会重新发送这些消息,导致重复数据

问题2:脑裂问题: 在短暂的网络分区期间,可能出现两个都认为自己是“活动”的生产者实例同时向同一个分区写入数据,造成数据混乱。

1.3解决办法

1.kafka使用transaction id和producer epoch来解决生产者僵尸问题;epoch它本质上是该transactional.id的一个单调递增的序列号

2.Epoch比较只在相同 transactional.id 内有效:只有配置了相同 transactional.id 的生产者实例之间,它们的 Epoch 值才有比较的意义。Transaction Coordinator 会为新实例分配更高的 Epoch,并通知 Broker 拒绝任何携带旧 Epoch 的写入请求(针对该 transactional.id)。

也即:新启动的实例必须使用与旧实例完全相同的 transactional.id。这是 Epoch 比较和隔离机制生效的前提条件。

3.不同 transactional.id = 完全独立: 如果两个生产者实例使用不同的 transactional.id,无论它们的 Epoch 值是多少(即使其中一个为 1,另一个为 100),它们都会被 Kafka 视为两个完全独立、互不相干的生产者。Broker 不会对它们进行Epoch比较或相互隔离。它们可以同时向同一个分区发送消息(尽管可能因 Leader 处理顺序导致消息交叉),但 Kafka 不保证它们之间的顺序或原子性。

1.4 案例

1.4.1 案例1:生产者崩溃后重启 (同一 transactional.id)

旧实例崩溃,事务可能处于中间状态(消息已发送但未提交)。

新实例启动,向 Transaction Coordinator 初始化事务。

Transaction Coordinator 分配一个更高的新 Epoch (e.g., old=1, new=2),并隔离旧Epoch=1。

Transaction Coordinator 中止由旧实例启动的未完成事务(标记为 ABORT),确保那些部分写入的消息不会被消费者读取(在 read_committed 隔离级别下)。

新实例(Epoch=2)开始新的事务。它不会重复发送旧实例可能已发送过的消息,因为应用逻辑知道事务未提交,需要重新处理业务逻辑并发送新消息。

如果旧实例“僵尸复活”并尝试发送消息,Broker 会检查其 Epoch=1 < 当前最新 Epoch=2,拒绝写入。

1.4.2 案例2:短暂网络分区导致的脑裂

两个实例(可能是由于网络分区误判)都认为自己是活跃的,使用同一个 transactional.id。

其中一个实例(假设是重启后的新实例)成功联系到 Transaction Coordinator,获得了更高的 Epoch。

Transaction Coordinator 隔离了旧 Epoch。

当网络恢复时,持有旧 Epoch 的实例的任何写入请求都会被 Broker 拒绝(InvalidProducerEpoch)。

只有持有最新 Epoch 的实例的写入有效,避免了数据冲突。

1.4.3 案例3:正确 - 解决僵尸

生产者 A (Instance 1): transactional.id = "prod-app-1", Epoch = 1 (由 TC 分配)。

生产者 A 崩溃。

生产者 A (Instance 2 - 新实例) 启动,配置 transactional.id = "prod-app-1"。

Transaction Coordinator (TC) 检测到 "prod-app-1" 的新会话。

TC 为 "prod-app-1" 分配 新的 Epoch = 2。

TC 通知相关 Broker:对于 "prod-app-1",最新 Epoch 是 2,拒绝 Epoch <= 1 的请求。

如果旧实例 (Instance 1) 的进程“僵尸复活”并尝试发送消息 (携带 Epoch=1),Broker 会检查:"prod-app-1" 的当前最新 Epoch=2 > 请求中的 Epoch=1 → 拒绝写入 (InvalidProducerEpoch)。

新实例 (Instance 2) 使用 Epoch=2 可以正常写入。

1.4.4 案例4:错误 - 无法解决僵尸

生产者 A (Instance 1): transactional.id = "prod-app-1", Epoch = 1。

生产者 A 崩溃。

生产者 A (Instance 2 - 新实例) 启动,但错误地配置了 transactional.id = "prod-app-1-backup" (一个不同的 ID)。

TC 将 "prod-app-1-backup" 视为一个全新的、独立的生产者。

TC 为 "prod-app-1-backup" 分配 新的 Epoch = 1 (或其他初始值,与 "prod-app-1" 的 Epoch 无关)。

Broker 记录:

对于 "prod-app-1": 最新 Epoch = 1 (对应旧僵尸实例)。

对于 "prod-app-1-backup": 最新 Epoch = 1 (对应新实例)。

如果旧实例 (Instance 1) 的进程“僵尸复活”并尝试发送消息 (携带 transactional.id="prod-app-1", Epoch=1),Broker 检查:"prod-app-1" 的最新 Epoch=1 == 请求中的 Epoch=1 → 允许写入。

新实例 (Instance 2) 使用 "prod-app-1-backup", Epoch=1 也可以写入。

结果:两个实例同时写入相同的分区,造成数据混乱。Fencing 机制完全失效,因为新实例没有使用相同的 transactional.id 来“声明接管”。其实本质上是两个不同的transaction id,是两个独立的事务,并不相关,至于写入相同分区的相同内容可以使用去重,幂等机制来解决。

1.5 结论

transactional.id, Producer Epoch, 和 Kafka 事务协议共同构成了 Kafka 保障生产者高可用、防止僵尸实例破坏数据一致性的基石。

DeepSeek

http://www.dtcms.com/a/288893.html

相关文章:

  • Vue开发前端报错:‘vue-cli-service‘ 不是内部或外部命令解决方案
  • 【Java学习|黑马笔记|Day18】Stream流|获取、中间方法、终结方法、收集方法
  • 【Linux】2. Linux下的C/C++开发环境
  • 【UE5医学影像可视化】读取dicom数据生成2D纹理并显示
  • pytest-log
  • 嵌入式Linux:获取线程ID
  • 数组算法之【合并两个有序数组】
  • 137. Java 泛型 - 泛型与通配符:子类型化关系
  • 【Linux】权限详解 权限本质、权限属性、su、sudo提权、chmod\chown\chgrp、文件类别
  • RxSwift-事件属性
  • Vuex 核心知识详解:Vue2Vue3 状态管理指南
  • Servlet快速入门
  • Docker在NAS部署MoonTV+OrionTV
  • 44.sentinel授权规则
  • tidyverse-数据读入
  • 基于智慧经营系统的学校住宿登记报表分析与应用探究-毕业论文—仙盟创梦IDE
  • 如何防止任务长期处于“等待”状态
  • Python基础和高级【抽取复习】
  • 基于单片机的自动条幅悬挂机
  • Leetcode 06 java
  • SpringBoot的配置文件
  • 【micro:bit】从入门到放弃(四):高级指令:函数、数组、文本、高级LED、引脚配置
  • UE蒙太奇和动画序列有什么区别?
  • 早期SD模型控制生成方法
  • Dev-C++——winAPI贪吃蛇小游戏
  • 智能制造之物料详解
  • 物联网安装调试-温湿度传感器
  • 数学专业转行做大数据容易吗?需要补什么?
  • 高性能熔断限流实现:Spring Cloud Gateway 在电商系统的实战优化
  • 本地部署 Claude 大语言模型的完整实践指南