🚀 AI篇持续更新中!(长期更新)
AI炼丹日志-31- 千呼万唤始出来 GPT-5 发布!"快的模型 + 深度思考模型 + 实时路由",持续打造实用AI工具指南!📐🤖
💻 Java篇正式开启!(300篇)
目前2025年08月18日更新到: Java-100 深入浅出 MySQL事务隔离级别:读未提交、已提交、可重复读与串行化 MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

章节内容
上节我们完成了如下内容:
- Kafka 控制器中的 Broker 选举
- Kafka可靠性中的 副本复制、失效副本、副本滞后 等问题

一致性保证
基本概念
水位标记
水位或者水印(Watermark)一次,表示位置信息,即位移(offset)。Kafka源码中使用的名字是高水位,HW(high Watermark)。
副本角色
Kafka分区使用多个副本replica提供高可用。
LEO和HW
每个分区副本对象都有两个重要的属性:LEO和HW
- LEO:记日志末端位移(log and offset),记录了该副本日志中下一条消息的位移值。如果LEO=10,那么表示该副本保存了10条消息,位移值范围是【0,9】。
- Leader LEO 和 Follower LEO的更新是有区别的
- HW:即上面提到的水位值,对于同一个副本对象而言,其HW值不会大于LEO值,小于等于HW值的所有消息都认为是"已备份"的replicated。Leader副本和Follower副本的HW更新不同。
上图中,HW值是7,表示位移的0
7的所有消息都已经处于"已提交状态"(COMMITED)。而LEO值是14,813的消息就是未完全备份(fully replicated)。
- 为什么没有14?LEF是指向下一条消息到来时的位移。
- 消费者无法消费分区下Leader副本中位移大于分区HW的消息
Follower副本何时更新LEO
在Kafka的副本同步机制中,Follower副本的LEO(Log End Offset)更新涉及两个关键环节:
1. Follower本地LEO更新机制
当Follower副本向Leader发送fetch请求并成功获取消息后,会经历以下完整过程:
- 首先将消息写入本地日志文件
- 然后立即更新本地Broker副本管理中保存的LEO值
- 最后在响应Leader的fetch请求时,会将当前最新的LEO值一并返回给Leader
示例场景:
- 假设Follower当前LEO=100
- 从Leader获取了10条消息(offset 101-110)
- 写入本地日志后,立即将LEO更新为110
- 在响应中会告知Leader:"我的LEO现在是110"
2. Leader端维护的Follower LEO
Leader端会维护所有Follower副本的LEO信息,其更新机制如下:
- 当收到Follower的fetch响应时
- 解析响应中包含的LEO值
- 更新Leader副本管理器中该Follower对应的LEO记录
- 基于所有Follower的LEO计算新的HW(High Watermark)
特殊处理情况:
- 如果网络延迟导致长时间未收到Follower响应,Leader会保留该Follower最后上报的LEO
- 当Follower重新连接时,会通过完整的同步过程重新建立准确的LEO记录
3. 两套LEO的协同工作机制
这两套LEO系统通过以下方式协同工作:
-
Follower端LEO的作用:
- 用于确定本地日志的写入位置
- 作为计算本地HW的基础
- 在副本恢复时确定需要截断的位置
-
Leader端维护的Follower LEO作用:
- 监控所有Follower的同步进度
- 计算分区级别的HW
- 决定消息何时对消费者可见
- 在Leader选举时评估副本的同步状态
维护这两套LEO的关键原因在于:
- 本地LEO确保Follower能独立运作不受网络影响
- Leader端维护的LEO提供了全局视角,确保一致性
- 这种设计解耦了副本同步和HW计算的过程
注意:虽然两套LEO理论上应该一致,但在网络分区等异常情况下可能会出现短暂不一致,Kafka的副本同步机制会最终确保它们达成一致。
Follower副本的本地LEO何时更新
Follower副本的本地LEO(Log End Offset)更新机制如下:
-
基本更新原理
- Follower副本的LEO值直接反映其本地日志的最新写入位置
- 该值会随着新消息的持续写入而动态更新
-
更新触发场景
- 当Broker收到新消息写入请求时:
- 消息首先被追加到日志文件
- LEO值立即递增更新
- 例如:原LEO=100,写入一条消息后变为101
- 当Broker收到新消息写入请求时:
-
Fetch请求处理流程
- Follower定期向Leader发送fetch请求(默认每500ms)
- Leader返回的数据包含:
- 起始offset
- 消息批次
- 其他控制信息
- 典型fetch请求处理过程:
- Follower发送fetch(offset=100)
- Leader返回offset 100-120的消息
- Follower接收后写入本地日志
- LEO自动从100更新为120
-
特殊情况处理
- 当出现消息压缩时:
- 物理LEO可能减小
- 但逻辑LEO继续保持递增
- 网络异常情况下:
- 可能触发重试机制
- LEO只在消息持久化后更新
- 当出现消息压缩时:
-
与其他偏移量的关系
- LEO总是 >= HW(高水位线)
- 与Leader的LEO可能存在滞后
- 在ISR列表中时会尽量保持同步
-
监控与调优
- 可通过kafka-topics.sh查看LEO状态
- replica.lag.time.max.ms参数影响更新频率
- 监控LEO差值可发现副本同步问题
注意:LEO更新是异步操作,实际更新时机可能受磁盘I/O性能影响。在配置较高的集群中,这个更新过程通常在毫秒级完成。
Leader端Follower的LEO更新机制详解
在Kafka的副本同步机制中,Leader端维护的Follower的LEO(Log End Offset)更新遵循以下详细流程:
-
Fetch请求触发时机:
- Follower会按照
replica.fetch.wait.max.ms
配置的时间间隔定期向Leader发送fetch请求 - 或者当有新的消息到达Leader时,Follower会立即触发fetch请求
- Follower会按照
-
请求处理阶段:
- Leader收到fetch请求后,首先解析请求中包含的Follower当前的LEO信息
- Leader根据请求中的offset从本地日志中读取相应的消息数据
- 在读取数据时,Leader会检查该Follower是否有读取权限(ACL校验)
-
LEO更新阶段:
- 在准备响应数据前,Leader会先更新内存中维护的该Follower的LEO值
- 更新的LEO值 = 本次fetch请求的起始offset + 本次返回的消息数量
- 这个更新操作会记录在
LeaderEndPoint
的元数据中
-
特殊场景处理:
- 如果Follower请求的offset不在Leader日志范围内,Leader会触发截断或同步操作
- 对于新加入ISR的Follower,Leader会初始化其LEO为当前日志的起始offset
- 当Follower落后过多时,Leader会限制返回数据量以避免网络拥塞
-
更新后的操作:
- 更新完成后,Leader会检查该Follower是否满足ISR条件
- 如果满足且原先不在ISR中,会触发ISR集合变更通知
- Leader会将更新后的LEO信息持久化到内存元数据中
示例场景: 假设Follower当前的LEO是100,发送fetch请求获取offset=100开始的50条消息。Leader处理流程:
- 接收fetch(offset=100, max_bytes=...)
- 从本地日志读取offset 100-149的消息
- 更新该Follower的LEO为150
- 将消息100-149和更新后的LEO信息一并返回给Follower
Follower副本何时更新HW
Follower更新HW发生在其更新LEO之后,一旦Follower向Log写完数据,尝试更新自己的HW值。 比较当前LEO值域fetch响应中Leader的HW值,取两者的小者作为新的HW值。
即:如果Follower的LEO大于Leader的HW,Follower HW值不会大于Leader的HW值。
Leader副本何时更新LEO
和Follower更新LEO相同,Leader写Log时自动更新自己的LEO值
Leader副本何时更新HW
Leader的HW值就是分区HW值,直接影响分区数据对消费者的可见性
Leader会【尝试】去更新分区HW的四种情况:
- Follower副本成为Leader副本时,Kafka会尝试去更新分区HW
- Broker奔溃导致副本被踢出ISR时,检查下分区HW值是否需要更新是有必要的
- 生产者向Leader副本写消息时,因为写入消息会更新Leader的LEO,有必要检查HW值是否需要更新
- Leader处理Follower fetch请求时,首先从Log读取数据,之后尝试更新分区HW值
当 Kafka Broker 都正常工作时,分区HW值的更新时机有两个:
- Leader处理produce请求时
- Leader处理fetch请求时
Leader如何更新自己的HW值?详细解析
1. 数据存储机制
Leader Broker维护两个关键数据集:
- 所有Follower副本的LEO(Log End Offset)列表
- 自身的LEO值
这些数据会持久化到磁盘,并通过ZooKeeper进行集群同步,确保故障恢复时数据一致性。
2. HW确定流程(分步骤说明)
当需要更新分区HW值时,Leader会执行以下步骤:
-
副本筛选阶段:
- 扫描所有Follower副本
- 对每个副本检查两个条件(满足任意一个即可):
- 条件A:当前位于ISR(In-Sync Replicas)列表中
- 条件B:同时满足:
- 不在ISR列表中
- 该副本LEO落后于Leader LEO的时长 ≤ replica.lag.time.max.ms(默认10,000ms)
-
最小值比对阶段:
- 收集所有通过筛选的副本LEO
- 加入Leader自身的LEO值
- 取这些LEO值中的最小值作为新HW
3. 条件设计的必要性
双条件机制解决的核心问题:
- 避免HW越界 :如果只考虑ISR中的副本,当某个副本刚好满足replica.lag.time.max.ms条件(即将进入ISR),但尚未被加入ISR时:
- 该副本可能已经catch up了大部分数据(LEO接近Leader)
- 若忽略这类副本,HW可能越过其实际存储位置
- 这违反了HW的核心定义(所有ISR副本LEO的最小值)
4. 配置参数影响
replica.lag.time.max.ms的取值直接影响HW更新行为:
- 设置过小(如1s):
- 可能导致频繁的ISR变动
- 增加网络抖动时的HW波动
- 设置过大(如30s):
- 延长故障检测时间
- 可能保留过多"准ISR"副本
5. 实际场景示例
假设一个分区有如下状态:
- Leader LEO: 150
- ISR副本A LEO: 148
- 非ISR副本B LEO: 149(落后Leader时长8s)
- 非ISR副本C LEO: 130(落后Leader时长15s)
HW计算过程:
- 副本A(ISR)→ 入选
- 副本B(非ISR但落后8s < 10s)→ 入选
- 副本C(非ISR且落后15s > 10s)→ 排除
- 比较值集合:[148(A), 149(B), 150(Leader)]
- 最终HW = min(148,149,150) = 148
6. 异常处理机制
当出现以下情况时:
- 所有副本都不满足条件
- 网络分区导致无法获取Follower状态
Leader会:
- 维持当前HW不变
- 触发Controller进行副本重分配
- 记录WARN级别日志:"No eligible replicas found for HW update"