高可用系统通常会遇到下列问题
- 元数据维护。
- 数据持久化。
- 数据同步。
- 数据一致性。
- 故障恢复。
- 主备切换(某节点故障可自动切换为其他节点)。
- 扩容。
- 数据写入策略。
下面就从这些问题入手,去探索kafka如何保证高可用。
术语解释
AR
Assigned Repllicas
指派的副本集合。分区中的所有副本统称为AR。
ISR
In-Sync Replicas
同步副本集。所有与leader副本保持一定程度同步的副本(包括Leader)组成ISR(In-Sync Replicas),ISR集合是AR集合中的一个子集。
OSR
Out-Sync Relipcas
丢失同步的副本集。与leader副本同步滞后过多的副本(不包括leader)副本。
LEADER
Leader副本(分区,承担所有流量的读写)负责维护和跟踪ISR集合中所有的follower副本的滞后状态,当follower副本落后太多或者失效时,leader副本会吧它从ISR集合中剔除。如果OSR集合中follower副本“追上”了Leader副本,之后再ISR集合中的副本才有资格被选举为leader,而在OSR集合中的副本则没有机会(这个原则可以通过修改对应的参数配置来改变)。
FOLLOWER
类似于普通的kafka的消费者客户端,从leader上拉取消息,然后写入到自己的topic中。为了提高效率,是通过批量拉取然后聚合存储。
follower数量可能为0或者多个。
Broker
一个独立的kafka服务器被称为broker。broker处理客户端的请求,接受生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。为消费者提供服务,对提取的分区做出相应,返回提交到磁盘的消息。
Consumer
消息的消费者。
Producer
消息的生产者。
RAID-5
RAID5(分布式奇偶校验的独立磁盘结构)。从它的示意图上可以看到,它的奇偶校验码存在于所有磁盘上,其中的p0代表第0带区的奇偶校验值,其它的意思也相同。RAID5的读出效率很高,写入效率一般,块式的集体访问效率不错。因为奇偶校验码在不同的磁盘上,所以提高了可靠性。但是它对数据传输的并行性解决不好,而且控制器的设计也相当困难。
注意
文章中的leader副本
,leader分区
大部分时候是同一个意思,均表示某topic下的某partition的leader(master).follower
与’leader`类似表达.
元数据维护
ZooKeeper本身是个高可用强一致的分布式目录系统。
kafka
通过ZooKeeper来存储元数据,目录结构大致如下图:
/kafka01/brokers/topics/
这个目录下,存储每个topic
的分区信息。
/brokers/topics/[topic]/partitions/[partitionId]/state
目录下存储每个topic
的partition
的信息。
{
"controller_epoch": 表示kafka集群中的中央控制器选举次数,
"leader": 表示该partition选举leader的brokerId,
"version": 版本编号默认为1,
"leader_epoch": 该partition leader选举次数,
"isr": [同步副本组brokerId列表]
}
数据持久化
对于消息队列来说,因为待投放的数据量可能非常大,也可能需要重放的实现。
因此,纯内存存放数据通常是不够的。不仅费用高(冷数据存内存),而且可靠性较差(服务死亡造成的数据丢失)。当然,热数据这么做是值得的,如Redis纯内存提高数据的访问效率。
kafka作为一个日志系统,需要非常可靠的数据持久化功能。
由于不存在对已有数据的修改行为,kafka实现类似于Redis的AOF(Append only File),仅需将消息追加到文件末尾。
同时利用磁盘的顺序读写避免频繁寻道到来的开销,从而提升吞吐量,甚至超过内存随机读写的效率。
在实现方面利用操作系统的Zero Copy
避免了用户态和内核态的复制开销。
借助操作系统对写入的优化(PageCache),将多次小段写入聚合为单次大块写入。
而操作系统在I/O调度层进一步聚合读写请求,减少对磁盘的寻址操作。
IO调度层的作用是把内核请求的块设备写请求队列,会进行适当优化排序、合并相邻请求。由于磁盘寻址过程(寻道、旋转)较慢,不进行优化的写请求队列将导致磁盘性能大大降低。
应用 IO 流程
磁盘顺序写入性能
此处直接引用官方文档:
The key fact about disk performance is that the throughput of hard drives has been diverging from the latency of a disk seek for the last decade. As a result the performance of linear writes on a JBOD configuration with six 7200rpm SATA RAID-5 array is about 600MB/sec but the performance of random writes is only about 100k/sec—a difference of over 6000X. These linear reads and writes are the most predictable of all usage patterns, and are heavily optimized by the operating system. A modern operating system provides read-ahead and write-behind techniques that prefetch data in large block multiples and group smaller logical writes into large physical writes. A further discussion of this issue can be found in this ACM Queue article; they actually find that sequential disk access can in some cases be faster than random memory access!
在由6块7200转的SATA硬盘组成的RAID-5阵列中,顺序写入可达600MB/s,而随机读写仅有100KB/s,有近6000倍的差距。
这差距主要是磁盘寻道所带来的。
困惑
操作系统的多次缓冲聚合,不免存在一个问题,对于kafka来说,收到系统的返回就认定数据已经写入(只是写入buffer),但尚未flush到磁盘,如果此时掉电,数据依然会丢失。
这个可靠是如何保证的?
解:
其实这已经超出单机可靠性的范围了。
此处高可用依赖于多副本集群来解决。
数据同步
kafka使用了最简单的数据同步模型。
由单个leader分区负责数据的读写,并提供序列号保证顺序。
follower分区仅简单的复制来自leader的数据。
当follower分区落后leader分区数据太多时,会被移到OSR集合中,追上后重新加入到ISR。
当然这种简单的模型会存在单点压力。不过kafka的单个topic通常是由多个partition组成的,就topic而言,可以通过调整parition数量来提高吞吐量。
当然,这依然存在一个问题,当leader上最新被提交的数据还没来得及被follower分区拉取时就down了,那哪怕最新的follower也丢失了数据。
对于这种情况,需要由生产者客户端来做控制,通过设置acks
为all
或者-1
来要求所有的ISR响应表示写入成功。
ISR伸缩
ISR集合是通过leader所在的broker节点执行定时任务,周期性的去检查。时间就是replica.lag.time.max.ms
参数指定的。
当某follower分区超过replica.lag.time.max.ms
时间没有到leader分区拉取数据并且follower与leader的数据不相同,就被认为是落后分区,被移到OSR集合中。
反之,当在OSR集合中的follower追上leader,则会被加入ISR。
故障恢复
从故障状态恢复的broker会重新加入到OSR中,然后从leader分区同步数据,直到追上后,则被leader移到ISR中,完成故障恢复。
主备切换
在kafka中,主备的基本单位是分区,当replace factor
大于1时,就会有follower副本,也就是备份。
当leader分区发生故障,需要切换follower分区时,follower需要满足2个条件:
- 必须与ZK维持session。也就是证明该follower分区是alive。
- follower分区的数据要与leader分区的数据接近(根据
replica.lag.time.max.ms
配置控制允许最大落后时间)。
满足上述2个条件的就是ISR,不满足的就是OSR。
只有ISR会参与主备切换时,leader的竞选。
通过该方式实现故障分区的主备切换,并保证数据的可靠不丢失。
扩容
在一个broker上存在多个某topic的partition的副本是没有意义的,纯粹的浪费。
因此,当replace factor
的数量等同于broker
的数量时,需要先增加broker,才能继续扩大replace factor
数。
leader的分布
为了更好得平衡节点的压力,kafka通过简单的让各个broker上的leader分区数尽量相等,这样不仅提高了topic的容错,也让流量更加均匀,避免访问热点。
数据写入策略
待完善。。
参考
- apache kafka系列之在zookeeper中存储结构
- kafka中的ISR、AR又代表什么?ISR伸缩又是什么?
- 系统调用sync的一系列
- 为了追求极致的性能, Kafka 掌控这 11 项要领
- kafka 官方文档