Kafka的副本机制
- 副本,简单来说就是另一个存储文件,比如mysql的只读库就可以看成一个副本,一般使用副本主要有三个方面的优势
- 能够提供数据的冗余,在宕机或者磁盘损坏的时候,及时恢复数据
- 能够提供横向的扩展,通过增加机器(类似于mysql的只读库)来提高读操作的吞吐量
- 改善数据的局部性,比如cdn的节点数据,将数据落到用户最近的节点上
- 但是kafka的副本的作用只是数据冗余的功能,其他的两个功能没有体现出来
- kafka 副本可以是一个分区下按照不同的topic进行副本的创建,但是副本只是进行异步的拉取同步数据,不对外提供数据,因此将副本分成领导副本和追随者副本,只有在领导副本挂掉滞后,会在追随者副本中选取领导副本,对外提供服务;
- 关于追随者副本不对外提供数据的情况,能够提供稳定的对外数据,所写即所得,不会因为读取了不同副本的数据,副本之间数据不统一导致结果不一致;
- 关于追随者副本是否和leader 副本保持一致的界限,并不是说必须是消息一致才能是一致,而是允许在一个同步间隙内的误差,用 replica.lag.time.max.ms来控制,比如设置的是10秒,kafka允许在追随者副本的消息落后leader副本的时间在10秒之内
- In-sync Replicas (ISR) 是记录与leader副本一致的副本集合,是一个动态的集合,因为追随者副本是通过异步拉取数据的,因此在某些情况下可能会不同步,而且落后时间较长,此时isr
记录中会将这个副本移除,在此之后如果副本同步到了一致性的要求的时候,会重新放到isr中 - ISR 不只是追随者副本集合,它必然包括 Leader 副本。甚至在某些情况下,ISR 只有 Leader 这一个副本
- 在leader副本挂掉的时候,isr集合中可能为空,此时如何选取leader呢,需要看参数设置情况,一般说来,如果配置了unclean.leader.election.enable 的话,kafka会在那些不同步的副本中选取副本作为leader副本,以便提供高可用的服务,但是由于这些副本落后很多数据,可能会造成其他业务上的问题,因此一般该参数设置为false,来保证数据的一致性;(在高可用和数据一致性的选择上,倾向于选择一致性,因为高可用的话可以通过其他措施来弥补)
Kafka 是如何处理请求的
- Kafka作为一个高性能的读写消息引擎,所有的连接都是tcp的socket连接,为了提高吞吐量使用了Reactor 模型来处理整个请求和响应,在kafka中将请求分为两大类,数据请求和控制类请求,其中控制类请求的优先级高于数据类请求,控制类请求可以使数据类请求立即失败。
-
kafka中数据类请求的请求模型
- 请求进来之后,首先会进入到网络线程池中,这一部分就是使用reactor模型处理的,但是并不是直接响应,而是将数据放到共享队列中,使用一个io线程池去异步的处理共享队列中的请求(将数据放到可以提高整个数据的吞吐量,如果不放到共享队列中的话,大部分读写请求是需要直接处理的,如果直接)
- reactor和线程池之间的区别是什么呢?
Kafka 的高水位和Epoch如何让各个副本异步保持同步状态
- 高水位 High WaterMark 是标记消息提交和未提交的分界线,在hw之前的数据都可以认为是已提交到kafka上了,否则还认为没有成功提交到kafka上;此处的提交是说认为某条消息全部同步到所有副本上的时候才认为是已提交;
-
但是hw是有延时的,为了等各个副本的进度,这个值是所有副本中落后的消息数记录,消息还是要不断的提交的leader中的,因此还需要一个缓冲空间来记录消息该写第几个位置,此时出现了LEO (log end Offset),消息可以先写着,kafka之间慢慢同步提交的位置
- 那kafka是如何移动自己的hw和leo呢?
- leader 是如何移动自己的leo,leader收到producer产生的消息的时候,leo会先位移标志位
- 其他副本是如何移动自己的leo,副本异步拉去leader中的数据,然后同步自己的leo,正常情况下,这时候leader和副本之间的leo都是一样的
- leader的hw是如何移动的,副本在拉取leader的leo的时候,会告诉leader自己现在的leo在哪,因此上一步的时候,告诉leader自己的leo是0,所以leader的hw不移动,如果一直没有producer产生消息,那么在下一次同步的时候,副本会告诉leader自己的leo是1,然后leader会将所有副本的信息进行一次保存,并获取最小的leo,作为自己的hw;也就是说副本已经将消息同步过去了,最少同步到某个地方了,因此hw是所有副本中的最小leo
- 副本的hw是怎么移动的呢,副本需要去找leader去同步的获取leader的hw,也就是它需要直到当前所有副本中的最小同步位置在哪,然后在定自己的同步位置应该在什么地方,如果获取到leader的hw的值大于自己的leo的话,那自己就是那个最落后的,因此自己的hw就是自己的leo,否则的话,副本的hw和leader的hw保持一致
- 副本如果宕机重新启动的时候,在0.1.1.0版本之前呢,启动之后,找不到宕机之前的leo,直接将hw作为leo的最后值,这个认为也是一个比较靠谱的策略,但是为什么不计leo呢,不知道,需要之后进行源码级别的讨论,那么这就会导致,如果副本宕机重启,恢复完之后,开始去拉leader的数据的时候,自己被选为leader了,但是自己的leo却不是最新的,由于leader天生具有权威性,因此会认为自己都是对的,之后原来的leader恢复的时候,要去同步leader的数据,然后将自己的数据更新,发现丢了一些消息,就是副本在恢复的时候副本当时的高水位和原始副本的leo之间的差值
- 为了改进这个措施,之后版本对这个操作进行了更新,在恢复的时候,首先去leader上获取先leo值,并和自己的进行对比,如果自己落后的话,可能需要更新否则的话不动,因此需要进行记录自己的leo,如果能对这个管理leo的数据进行管理的话,需要有一个确切的版本号来控制,小的版本号会被废弃,这就是epoch的设计原则,能够实时的直到自己到底是领先还是落后的标示,切换leader;具体是有一个单调增加的版本号。每当副本领导权发生变更时,都会增加该版本号。小版本号的 Leader 被认为是过期 Leader,不能再行使 Leader 权力。只要每次有变化,就+1,然后其他重启的数据都和这个保持了一致,所以数据会同步;
- 副本是否更新leo的值不再依赖hw,而是实实在在的记录值,类似于乐观锁,永远是最新的leo值,所以不会丢数据
- 当一个副本挂了,如何进行恢复呢?从日志中查询数据,然后逐渐恢复,如果没有标识可能会落后于原始的进度,导致消息的丢失!