上篇文章说了,broker的消息设计,采用紧凑的byteBuffer,存储设计主要包含attribute后三个表示压缩类型,还有crc效验,以及key和value,后面新增了时间戳。
一、副本与ISR设计
首先kafka本质就是个备份日志,利用多份相同的数据来提供冗余机制保证高可用。这些备份在kafka中就被称为副本(replica)。Kafka把这些副本均匀分配到broker上,并从这些副本挑选一个作为leader对外提供服务,而其他副本被称为follower副本,只能被动向leader副本请求数据,保证数据同步。
假设leader一直正常工作,是不需要follower副本,但现实残酷,一旦发生这种情况,follower副本就会竞争称为leader,但不是所有副本都有资格竞选,如果落后太多的副本竞选成功,则会失去资格,不然导致数据丢失,鉴于这种情况,kafka引入ISR机制。
ISR(in-sync replicas),就是kafka动态维护副本的机制,每个topic分区都有自己的ISR列表,isr中所有副本都和leader保持数据同步,也包含leade,只有isr中副本才有选举资格。由此可见,如果由n个副本,该分区可以忍受n-1个副本宕机。
在0.9.0.0版本之前,kafka有个参数replica.lag.max.message,用于控制follower副本落后leader副本的数量,一旦超过这个消息数,则被视为‘不同步’状态,把这个follower提出副本。
另外还有一个参数是replica.lag.time.max.ms用于监测超时时间,若设置500ms,follower在500ms内没有向leader请求数据,那么则视为不同步。
在0.9.0.0版本之后,kafka去掉了replica.lag.max.message,只保留了replica.lag.time.max.ms,默认是10s,对于请求速度追不上的follower,监测机制也发生了变化:如果一个follower落后leader的时间超过这个值,那么follower就是不同步的。
水印watermark和leader epoch
水印也被称为高水印或高水位,通常用在流式处理领域,与时间有关。而在kafka里面,水印反而与时间无关,与offset有关。一个kafka分区下通常有多个副本来实现冗余,主要三大类:
Leader副本:响应clients端读写请求副本。
Follower副本:被动备份leader数据,不想赢client读写。
ISR副本集合:包含leader副本和所有follower副本集合。
每个kafka副本对象都持有两个重要属性:日志末端位移(log end offset,简称LEO)和高水位(highwatermark,简称HW)。(注意是所有副本,不止leader副本)
LEO:日志末端位移,记录了该副本对象底层日志文件中下一条消息的位移值。举个例子,LEO=10,那么表示该副本日志上已经保存10条消息,位移范围是[0,9]。(另外LEO的leader和follower更新机制是不同的)
HW:高水印值。任何一个副本的HW值都大于LEO值,而小于或者等于HW值得所有消息都被认为是“已提交的”或“已备份的”(replicated)。(kafka对leader和follower的更新HW机制也是不同的)
如果把LEO和HW看做两个指针,那么它们定位机制是不同的,任何时刻,HW指的是实实在在的消息,而LEO指向下一条特写,也就是说LEO指向的位子是没有消息的。
从图上可以看到HW值是7,这表示前8条,从0开始,已经是备份状态,而LEO是12,表示当前日志写入了11条数据,而8到11属于未备份,也就是未提交数据。
之前说过消费者不能消费未提交的消息。也可以说是消费者无法消费分区leader副本上大于HW的消息。
LEO更新机制
分为Leader副本更新LEO机制 和 follower副本更新LEO机制
Follower副本只是被动向leader副本请求数据,具体表现为follower副本不停向leader副本所在broker发送fetch请求,一旦获取到消息,则写入自己日志进行备份。
那么follower副本的LEO是何时更新的?有两套,一套LEO值保存在follower副本所在broker的缓存上,另一套LEO值保存在leader副本所在broker的缓存上,后者可以暂且称为remoteLEO。换句话说,leader副本所在机器的缓存上保存了该分区下所有follower副本的LEO属性值,包括他自己。
为什么保存两套值呢?因为kafka要利用前者帮助follower副本自身更新HW值,而同时还需要使用后者来确定leader副本HW值,即分区HW。
Follower副本端的follower副本LEO何时更新?
指的就是当前follower副本底层日志的LEO值,也就是说每当写入一条消息,其LEO值就会加1。所以当follower发起fetch请求leader,leader返回数据后,就开始写入底层日志。
Leader副本端的follower副本LEO何时更新?
Leader在处理follower发起fetch请求时候更新。一旦leader接受到follower发起的fetch请求,它会首先从log中读取相应的数据,但是在给follower返回数据前,他先去更新follower的LEO。
Leader更新LEO的时机也是写log时候更新他的LEO。
HW更新机制
首先看看follower副本HW属性更新机制,follower更新HW在他更新完LEO之后,一旦follower向log写完数据,则更新HW值。
比如follower的HW值,我们更关心leaderHW值,因为他直接影响分区数据的对于consumer可见性。在以下四种情况,leader会尝试更新自己的hw值。
1、副本称为leader时:当某个副本成为分区的leader副本,kafka会尝试更新分区hw值。
2、Broker崩溃导致副本被踢出ISR。
3、Leader处理follower的fetch请求时。
4、Producer向leader副本写入消息时候。
注意最后两条是在kafka正常工作的时候,他的更新时间有两个,第一个就是producer发送数据成功,另一个是follower副本发起fetch同步数据时候。
那么leader是何时更新他的hw值?满足两个条件才可以更新:
1、处于ISR中。
2、副本LEO落后leader LEO的时长不大于replica.lag.time.ms(默认10s)。
基于水印备份缺陷
可能存在两个问题,
1、数据丢失
2、数据不一致/数据离散:leader日志和follower日志可能不一致。
基于上面两个问题,leader epoch值彻底解决了基于水印备份机制的两个弊端。