kafka 笔记五 kafka消费者 分区副本

分区副本的分配
副本分配的三个目标:
1. 均衡地将副本分散于各个 broker
2. 对于某个 broker 上分配的分区,它的其他副本在其他 broker
3. 如果所有的 broker 都有机架信息,尽量将分区的各个副本分配到不同机架上的 broker
在不考虑机架信息的情况下:
1. 第一个副本分区通过轮询的方式挑选一个 broker ,进行分配。该轮询从 broker 列表的随机位置进行轮询。
2. 其余副本通过增加偏移进行分配
分配案例:
broker-0 broker-1 broker-2 broker-3 broker-4
    p0         p1         p2         p3             p4 (1st replica)
    p5         p6         p7         p8             p9 (1st replica)
    p4         p0         p1         p2             p3 (2nd replica)
    p8         p9         p5         p6             p7 (2nd replica)
    p3         p4         p0         p1             p2 (3nd replica)
    p7         p8         p9         p5             p6 (3nd replica)

 

 副本机制

Kafka 在一定数量的服务器上对主题分区进行复制。
当集群中的一个 broker 宕机后系统可以自动故障转移到其他可用的副本上,不会造成数据丢失。
--replication-factor 3 1leader+2follower
1. 将复制因子为 1 的未复制主题称为复制主题。
2. 主题的分区是复制的最小单元。
3. 在非故障情况下, Kafka 中的每个分区都有一个 Leader 副本和零个或多个 Follower 副本。
4. 包括 Leader 副本在内的副本总数构成复制因子。
5. 所有读取和写入都由 Leader 副本负责。
6. 通常,分区比 broker 多,并且 Leader 分区在 broker 之间平均分配。
Follower 分区像 普通的 Kafka 消费者 一样,消费来自 Leader 分区的消息,并将其持久化到自己的 日志中。
允许 Follower 对日志条目拉取进行 批处理
 
同步节点定义:
1. 节点必须能够维持与 ZooKeeper 的会话(通过 ZooKeeper 的心跳机制)
2. 对于 Follower 副本分区,它复制在 Leader 分区上的写入,并且不要延迟太多
Kafka 提供的保证是,只要有至少一个同步副本处于活动状态,提交的消息就不会丢失。
 
失效副本
失效副本的判定
replica.lag.time.max.ms 默认大小为 10000。
ISR 中的一个 Follower 副本滞后 Leader 副本的时间超过参数 replica.lag.time.max.ms 指定的值时即判定为副本失效,需要将此Follower 副本剔出除 ISR
具体实现原理:当 Follower 副本将 Leader 副本的 LEO 之前的日志全部同步时,则认为该 Follower 副本已经追赶上Leader 副本,此时更新该副本的 lastCaughtUpTimeMs标识。
Kafka 的副本管理器( ReplicaManager )启动时会启动一个副本过期检测的定时任务,而这个定时任务会定时检查当前时间与副本的lastCaughtUpTimeMs 差值是否大于参数replica.lag.time.max.ms 指定的值。
Kafka 源码注释中说明了一般有两种情况会导致副本失效:
1. Follower 副本进程卡住,在一段时间内没有向 Leader 副本发起同步请求,比如频繁的 FullGC。
2. Follower 副本进程同步过慢,在一段时间内都无法追赶上 Leader 副本,比如 IO开销过大
如果通过工具增加了副本因子,那么新增加的副本在赶上 Leader 副本之前也都是处于失效状态的。
如果一个 Follower 副本由于某些原因(比如宕机)而下线,之后又上线,在追赶上 Leader 副本之前也是出于失效状态
 

一致性保证

1. 水位标记
水位或水印( watermark )一词,表示位置信息,即位移( offset )。 Kafka 源码中使用的名字是高水位,HW high watermark )。
2. LEO HW
每个分区副本对象都有两个重要的属性: LEO HW。
LEO :即日志末端位移 (log end offset) ,记录了该副本日志中下一条消息的位移值。如果LEO=10,那么表示该副本保存了 10 条消息,位移值范围是 [0, 9] 。另外, Leader LEO 和 Follower LEO的更新是有区别的。
HW :即上面提到的水位值。对于同一个副本对象而言,其 HW 值不会大于 LEO 值。小于等于HW值的所有消息都被认为是 已备份 的( replicated )。 Leader 副本和 Follower 副本的 HW 更新不同。
Follower 副本何时更新LEO
Follower 副本不停地向 Leader 副本所在的 broker 发送 FETCH 请求,一旦获取消息后写入自己的日志中进行备份。那么Follower 副本的 LEO 是何时更新的呢?首先我必须言明, Kafka 有两套 Follower 副本
LEO
1. 一套 LEO 保存在 Follower 副本所在 Broker 副本管理机 中;
2. 另一套 LEO 保存在 Leader 副本所在 Broker 的副本管理机中。 Leader 副本机器上保存了所有的follower副本的 LEO
 
1. Follower 副本的本地 LEO 何时更新? Follower 副本的 LEO 值就是日志的 LEO 值,每当新写入一条消息,LEO 值就会被更新。当 Follower 发送 FETCH 请求后, Leader 将数据返回给Follower,此时 Follower 开始 Log 写数据,从而自动更新 LEO 值。
2. Leader Follower LEO 何时更新? Leader 端的 Follower LEO 更新发生在 Leader 在处理Follower FETCH请求时。一旦 Leader 接收到 Follower 发送的 FETCH 请求,它先从 Log 中读取相应的数据,给Follower 返回数据前,先更新 Follower LEO
 
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 的四种情况:
1. Follower 副本成为 Leader 副本时: Kafka 会尝试去更新分区 HW
2. Broker 崩溃导致副本被踢出 ISR 时:检查下分区 HW 值是否需要更新是有必要的。
3. 生产者向 Leader 副本写消息时:因为写入消息会更新 Leader LEO ,有必要检查 HW 值是否需要更新
4. Leader 处理 Follower FETCH 请求时:首先从 Log 读取数据,之后尝试更新分区 HW
结论:
Kafka broker 都正常工作时,分区 HW 值的更新时机有两个:
1. Leader 处理 PRODUCE 请求时
2. Leader 处理 FETCH 请求时
Leader 如何更新自己的 HW 值? Leader broker 上保存了一套 Follower 副本的 LEO 以及自己的 LEO
当尝试确定分区 HW 时,它会选出所有 满足条件的副本 ,比较它们的 LEO( 包括 Leader LEO) ,并 选择最 小的 LEO 值作为 HW
需要满足的条件,(二选一):
1. 处于 ISR
2. 副本 LEO 落后于 Leader LEO 的时长不大于 replica.lag.time.max.ms 参数值 ( 默认是 10s)
如果 Kafka 只判断第一个条件的话,确定分区 HW 值时就不会考虑这些未在 ISR 中的副本,但这些副本已经具备了“ 立刻进入 ISR” 的资格,因此就可能出现分区 HW 值越过 ISR 中副本 LEO 的情况 —— 不允许。
因为分区HW定义就是ISR中所有副本LEO的最小值。
 
HW LEO 正常更新案例
 
我们假设有一个 topic ,单分区,副本因子是 2 ,即一个 Leader 副本和一个 Follower 副本。我们看下当producer 发送一条消息时, broker 端的副本到底会发生什么事情以及分区 HW 是如何被更新的。
1. 初始状态
初始时 Leader Follower HW LEO 都是 0( 严格来说源代码会初始化 LEO -1 ,不过这不影响之后的讨论) Leader 中的 Remote LEO 指的就是 Leader 端保存的 Follower LEO ,也被初始化成 0 。此时,生产者没有发送任何消息给Leader ,而 Follower 已经开始不断地给 Leader 发送 FETCH 请求了,但因为没有数据因此什么都不会发生。值得一提的是,Follower 发送过来的 FETCH 请求因为无数据而暂时会被寄存到Leader 端的 purgatory (炼狱)中,待 500ms ( replica.fetch.wait.max.ms 参数 ) 超时后会强制完成。倘若在寄存期间生产者发来数据,则Kafka 会自动唤醒该 FETCH 请求,让 Leader 继续处理。
 
2. Follower 发送 FETCH 请求在 Leader 处理完 PRODUCE 请求之后
 
如上图所示, Leader 接收到 PRODUCE 请求主要做两件事情:
1. 把消息写入 Log ,同时自动更新 Leader 自己的 LEO
2. 尝试更新 Leader HW 值。假设此时 Follower 尚未发送 FETCH 请求, Leader 端保存的 Remote LEO依然是 0 ,因此 Leader 会比较它自己的 LEO 值和 Remote LEO 值,发现最小值是 0 ,与当前HW值相同,故不会更新分区 HW 值(仍为 0
PRODUCE 请求处理完成后各值如下, Leader 端的 HW 值依然是 0 ,而 LEO 1 Remote LEO 也是 0
 
假设此时 follower 发送了 FETCH 请求 ,则状态变更如下:
 
本例中当 follower 发送 FETCH 请求时, Leader 端的处理依次是:
1. 读取 Log 数据
2. 更新 remote LEO = 0 (为什么是 0 ? 因为此时 Follower 还没有写入这条消息。 Leader 如何确认Follower 还未写入呢?这是通过 Follower 发来的 FETCH 请求中的 Fetch offset 来确定的)
3. 尝试更新分区 HW :此时 Leader LEO = 1 Remote LEO = 0 ,故分区 HW = min(Leader LEO, Follower Remote LEO) = 0
4. 把数据和当前分区 HW 值(依然是 0 )发送给 Follower 副本
 
Follower 副本接收到 FETCH Response 后依次执行下列操作
1. 写入本地 Log ,同时更新 Follower 自己管理的 LEO 1
2. 更新 Follower HW :比较本地 LEO FETCH Response 中的当前 Leader HW 值,取较小者, Follower HW = 0
此时,第一轮 FETCH RPC 结束,我们会发现虽然 Leader Follower 都已经在 Log 中保存了这条消息,但分区HW 值尚未被更新,仍为 0
 
Follower 第二轮 FETCH
分区 HW 是在第二轮 FETCH RPC 中被更新的,如下图所示:
 
Follower 发来了第二轮 FETCH 请求, Leader 端接收到后仍然会依次执行下列操作:
1. 读取 Log 数据
2. 更新 Remote LEO = 1 (这次为什么是 1 了? 因为这轮 FETCH RPC 携带的 fetch offset 1 ,那么为什么这轮携带的就是1 了呢,因为上一轮结束后 Follower LEO 被更新为 1 了)
3. 尝试更新分区 HW :此时 leader LEO = 1 Remote LEO = 1 ,故分区 HW = min(Leader LEO, Follower Remote LEO) = 1
4. 把数据(实际上没有数据)和当前分区 HW 值(已更新为 1 )发送给 Follower 副本作为 Respons
 
同样地, Follower 副本接收到 FETCH response 后依次执行下列操作:
1. 写入本地 Log ,当然没东西可写, Follower LEO 也不会变化,依然是 1
2. 更新 Follower HW :比较本地 LEO 和当前 LeaderHW 取小者。由于都是 1 ,故更新 follower HW = 1
此时消息已经成功地被复制到 Leader Follower Log 中且分区 HW 1 ,表明消费者能够消费 offset = 0的消息。
 
3. FETCH 请求保存在 purgatory 中, PRODUCE 请求到来。
Leader 无法立即满足 FECTH 返回要求的时候 ( 比如没有数据 ) ,那么该 FETCH 请求被暂存到 Leader 端的purgatory 中(炼狱),待时机成熟尝试再次处理。 Kafka 不会无限期缓存,默认有个超时时间 (500ms ),一旦超时时间已过,则这个请求会被强制完成。当寄存期间还没超时,生产者发送 PRODUCE请求从而使之满足了条件以致被唤醒。此时, Leader 端处理流程如下:
1. Leader Log (自动更新 Leader LEO
2. 尝试唤醒在 purgatory 中寄存的 FETCH 请求
3. 尝试更新分区 HW
 
HW LEO 异常案例
 
Kafka 使用 HW 值来决定副本备份的进度,而 HW 值的更新通常需要额外一轮 FETCH RPC 才能完成。 但这种设计是有问题的,可能引起的问题包括:
1. 备份数据丢失
2. 备份数据不一致
1. 数据丢失
使用 HW 值来确定备份进度时其值的更新是在 下一轮 RPC 中完成的。如果 Follower 副本在标记上方的第一步与第二步之间发生崩溃,那么就有可能造成数据的丢失。
 
上图中有两个副本: A B 。开始状态是 A Leader
假设生产者 min.insync.replicas 1 ,那么当生产者发送两条消息给 A 后, A 写入 Log ,此时Kafka会通知生产者这两条消息写入成功。
     
但是在 broker 端, Leader Follower Log 虽都写入了 2 条消息且分区 HW 已经被更新到 2 ,但Follower HW尚未被更新还是 1 ,也就是上面标记的第二步尚未执行,表中最后一条未执行。
倘若此时副本 B 所在的 broker 宕机, 那么重启后B会自动把LEO调整到之前的HW值1 ,故副本 B 会做日志截断(log truncation) ,将 offset = 1 的那条消息从 log 中删除,并调整 LEO = 1 。此时 follower 副本底层log 中就只有一条消息,即 offset = 0 的消息!
B 重启之后需要给 A FETCH 请求,但若 A 所在 broker 机器在此时宕机,那么 Kafka 会令 B 成为新的Leader,而 当A重启回来后也会执行日志截断,将HW调整回1 。这样, offset=1 的消息就从两个副本的log中被删除,也就是说这条已经被生产者认为发送成功的数据丢失。
丢失数据的前提是 min.insync.replicas=1
时,一旦消息被写入 Leader Log 即被认为是committed 。 延迟一轮 FETCH RPC 更新 HW 值的设计使 follower HW 值是异步延迟更新 ,若在这个过程中Leader 发生变更,那么成为新 Leader Follower HW 值就有可能是过期的,导致生产者本是成功提交的消息被删除。
 
2. Leader Follower数据离散
除了可能造成的数据丢失以外,该设计还会造成 Leader Log Follower Log 数据不一致。 如Leader 端记录序列: m1,m2,m3,m4,m5,… Follower 端序列可能是m1,m3,m4,m5,…。 看图:
假设: A Leader A Log 写入了 2 条消息,但 B Log 只写了 1 条消息。分区 HW 更新到 2 ,但 B 的HW还是 1 ,同时生产者 min.insync.replicas 仍然为 1
假设 A B 所在 Broker 同时宕机, B 先重启回来,因此 B 成为 Leader ,分区 HW = 1 。假设此时生产者发送了第3 条消息 ( 红色表示 ) B ,于是 B log offset = 1 的消息变成了红框表示的消息,同时分区 HW更新到2 A 还没有回来,就 B 一个副本,故可以直接更新 HW 而不用理会 A )之后 A重启回来,需要执行日志截断,但发现此时分区HW=2而A之前的HW值也是2,故不做任何调整 。此后 A B 将以这种状态继续正常工作。
显然,这种场景下, A B Log 中保存在 offset = 1 的消息是不同的记录,从而引发不一致的情形出现。
 
Leader Epoch 使用
Kafka 解决方案
造成上述两个问题的根本原因在于
1. HW 值被用于衡量副本备份的成功与否。
2. 在出现失败重启时作为日志截断的依据。
HW 值的更新是异步延迟的,特别是需要额外的 FETCH 请求处理流程才能更新,故这中间发生的任何崩溃都可能导致HW 值的过期。
Kafka 0.11 引入了 leader epoch 来取代 HW 值。 Leader 端使用 内存 保存 Leader epoch 信息,即使出现上面的两个场景也能规避这些问题。
所谓Leader epoch实际上是一对值:<epoch, offset>:
1. epoch 表示 Leader 的版本号,从 0 开始, Leader 变更过 1 次, epoch+1
2. offset 对应于该 epoch 版本的 Leader 写入第一条消息的 offset 。因此假设有两对值:
<0, 0> 
<1, 120>
表示第一个 Leader 从位移 0 开始写入消息;共写了 120 [0, 119] ;而第二个 Leader 版本号是 1 , 从位移120 处开始写入消息。
1. Leader broker 中会保存这样的一个缓存,并定期地写入到一个 checkpoint 文件中。
2. Leader Log 时它会尝试更新整个缓存:如果这个 Leader 首次写消息,则会在缓存中增加一个条目;否则就不做更新。
3. 每次副本变为 Leader 时会查询这部分缓存,获取出对应 Leader 版本的位移,则不会发生数据不一致和丢失的情况。
1. 规避数据丢失
只需要知道每个副本都引入了新的状态来保存自己当 leader 时开始写入的第一条消息的 offset 以及leader版本。这样在恢复的时候完全使用这些信息而非 HW 来判断是否需要截断日志。
 
2. 规避数据不一致
 
 
依靠 Leader epoch 的信息可以有效地规避数据不一致的问题。
对于使用 unclean.leader.election.enable = true 设置的群集,该方案不能保证消息的一致性
 
 
宕机如何恢复
1 )少部分副本宕机
leader 宕机了,会从 follower 选择一个作为 leader 。当宕机的重新恢复时,会把之前 commit 的数据清空,重新从leader pull 数据。
2 )全部副本宕机
当全部副本宕机了有两种恢复方式
1 、等待 ISR 中的一个恢复后,并选它作为 leader 。(等待时间较长,降低可用性)
2 、选择第一个恢复的副本作为新的 leader ,无论是否在 ISR 中。(并未包含之前 leader commit 的数据,因此造成数据丢失)
 
Leader选举
如果某个分区所在的服务器出了问题,不可用, kafka 会从该分区的其他的副本中选择一个作为新的Leader 。之后所有的读写就会转移到这个新的 Leader 上。现在的问题是应当选择哪个作为新的Leader。
只有那些跟 Leader 保持同步的 Follower 才应该被选作新的 Leader
Kafka 会在 Zookeeper 上针对每个 Topic 维护一个称为 ISR( in-sync replica ,已同步的副本)的集合,该集合中是一些分区的副本。
只有当这些副本都跟 Leader 中的副本同步了之后, kafka 才会认为消息已提交,并反馈给消息的生产者。
如果这个集合有增减, kafka 会更新 zookeeper 上的记录。
如果某个分区的 Leader 不可用, Kafka就会从ISR集合中选择一个副本作为新的Leader。(从AR列表中找到第一个存活的副本而且此副本在ISR列表中)
 
 
如果所有的 ISR副本都失败了怎么办?
此时有两种方法可选,
1. 等待 ISR 集合中的副本复活,
2. 选择任何一个立即可用的副本,而这个副本不一定是在 ISR 集合中。 需要设置 unclean.leader.election.enable=true
这两种方法各有利弊,实际生产中按需选择。 如果要等待 ISR 副本复活,虽然可以保证一致性,但可能需要很长时间。而如果选择立即可用的副本,则很可能该副本并不一致。
 
总结:
Kafka Leader 分区选举,通过维护一个动态变化的 ISR 集合来实现,一旦 Leader 分区丢掉,则从ISR中随机挑选一个副本做新的 Leader 分区。
如果 ISR 中的副本都丢失了,则:
1. 可以等待 ISR 中的副本任何一个恢复,接着对外提供服务,需要时间等待。
2. OSR 中选出一个副本做 Leader 副本,此时会造成数据丢失
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值