Controller
除了具有一般broker功能外,还负责分区leader选举
成为controller
•在所有broker中第一个成功在zk中创建/controller临时节点,或创建/controller的broker离开后争抢成功创建/controller的broker叫做Controller,并获得新的更大数值的epoch(争抢失败的都会收到节点已存在)
•其他节点会忽略来自低版本的epoch消息
选举Leader
•当一个broker离开集群,controller知道哪些分区是否需要新leader,controller遍历这些分区的follower并确定谁成为新leader(简单来说是follower同步副本列表里的下一个),然后向新leader们、follower们发送谁是新leader、谁是follower的信息。随后新leader开始处理producer、consumer的请求,follower开始从leader复制消息
Leader
首领副本。每个分区都有一个首领副本。为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
Leader的另一个任务是要搞清楚哪些follower的状态跟自己是一致的;如果follower无法跟leader保持一致,就不能成为leader
Follower
跟随者副本。Leader以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,他们唯一的任务就是从Leader那里复制消息,保持与Leader一致的状态。如果Leader发生崩溃,其中的一个Follower就会提升为新的Leader。
处理请求
处理客户端、分区副本、控制器请求。
•Producer、Consumer请求都必须发送给分区的Leader,如果发送的不是Leader,则会收到‘非分区首领’异常;客户端要自己负责发送到的是Leader。
处理Producer的请求
•acks
0 通过网络发出去就认为成功,如果集群不可用、发送到了旧leader等也无法感知;
1 Leader收到消息并写入系统文件缓存就响应成功;如果有3个副本,此时消息还没被同步副本复制过去,恰好leader不可用,新的leader产生了(判断一个副本不同步是需要一小段时间的),则消息会丢失;如果follower都是不同步的,此时leader的操作系统挂了(数据还没来得及被刷盘),消息丢失
all需要足够的同步副本复制成功才响应成功;如果follower都是不同步的,此时leader的操作系统挂了(数据还没来得及被刷盘),消息丢失
•消息何时被刷入磁盘
默认异步刷盘,先写入到系统页缓存,然后由操作系统在合适的时间再刷新到日志文件即磁盘。页缓存的数据在操作系统不可用时可能会丢失
处理Consumer的请求
•客户端发送获取topic分区的消息,指定从某个偏移量开始获取及获取量
•broker零拷贝-broker使用令拷贝技术向客户端发送消息,kafka直接把消息从文件(linux文件系统缓存)发送到网络通道,不需要经过中间缓冲区,避免复制和内存开销;producer、副本同步也一样
•不是所有保持在leader上的数据都可以被读取,只有已经被写入所有同步副本的消息才能被读取,否则kafka认为是不安全的;leader知道每个消息会被复制到哪些副本上;因此如果消息复制变慢,则消息达到consumer的时间也会增加,延迟时间可以通过replica.lag.time.max.ms配置复制消息时可被允许的最大延迟时间
存储-分区分配
•环境:6个broker、1个由10个分区的topic,复制系数3;即共30个分区副本分给6个broker
•规则:broker间平均分布副本;确保每个分区的副本在不同的broker上;如果broker指定了机架,分区副本尽可能分配到不同的机架
•分配过程:先分配leader,分区0leader在broker4,分区1leader在broker5,分区2leader在broker0…;再分配follower,因分区0leader在broker4,那它的第一个follower在broker5,第二个follower在broker0,以此类推…;如果配置了机架,则按机架交替方式选择broker
存储-文件管理
存储-文件格式
Pagecache
•是操作系统实现的一种主要的磁盘缓存,以此用来减少对磁盘I/O的操作。具体来说,就是把磁盘中的数据缓存到内存中,把对磁盘的访问变成对内存的访问当一个进程准备读取磁盘上的文件内容时,操作系统会先查看待读取的数据所在的页是否在页缓存中,如果存在则直接返回数据,从而避免了对物理磁盘的I/O操作;如果没有命中,则操作系统会向磁盘发起读取请求并将读取的数据页存入页缓存,之后再将数据返回给进程。同样,如果一个进程需要将数据写入磁盘,那么操作系统也会检测数据对应的页是否在页缓存中,如果不存在,则会先在页缓存中添加相应的页,最后将数据写入对应的页。被修改过后的页就变成了脏页,操作系统会在合适的时间把脏页中的数据写入磁盘,以保持数据的一致性Kafka中大量使用了页缓存,这是Kafka实现高吞吐的重要因素之一。虽然消息都是先被写入页缓存,然后由操作系统负责具体的刷盘任务的,但在Kafka中同样提供了同步刷盘及间断性强制刷盘的功能,这些功能可以通过log.flush.interval.message、log.flush.interval.ms等参数来控制。同步刷盘可以提高消息的可靠性,防止由于机器掉电等异常造成处于页缓存而没有及时写入磁盘的消息丢失。消息的可靠性应该由多副本机制来保障,而不是由同步刷盘这种严重影响性能的行为来保障
零拷贝
Note:如果producer发送时进行了压缩,那么解压工作也是consumer执行,不影响零拷贝
存储-page cache详解
Page Cache的落地问题
起因
前几天讨论到一个问题:Linux 下文件 close成功,会不会触发 “刷盘”?
其实这个问题根本不用讨论,查一下就知道。
man 2 close 的 NOTES 一节里有这么一段话:
A successful close does not guarantee that the data has been successfully saved to disk, as the kernel defers writes. It is not common for a filesystem to flush the buffers when the stream is closed. If you need to be sure that the data is physically stored use fsync(2). (It will depend on the disk hardware at this point.)
所以,一个文件的描述符被 close 成功,并不会触发操作系统刷盘。
那么,问题来了,除了主动调用fsync(或相关函数)之外,Linux 什么时候会“刷盘”呢?
一次正常的写流程
一次写数据的典型流程(不考虑异常和其它特殊情况):
1、数据在用户态的 buffer 中,调用 write 将数据传给内核;
2、数据在 Page Cache 中,返回写入的字节数(成功返回);
3、内核将数据刷新到磁盘。
第二步如果返回成功,说明数据已经到达操作系统的Page Cache,可以保证的是如果进程挂了,但是操作系统没挂,数据不会丢失。
如果调用 fsync 将数据刷新到磁盘上,返回成功,说明数据已经刷新到硬件上了——我们一般认为如果 fsync 返回成功,则表示数据持久化成功。
Page Cache 的异步刷新
那么,如果不调用fsync或其它类似功能的接口,Page Cache 是什么时候刷回磁盘的呢?
简单总结一下,有两种情况:
1、脏页太多。
2、脏页太久。
这些都由 Linux 内核的后台线程执行。相关的控制参数有:
说明: centy 中文意思是“百分之十”。
$ sysctl -a | grep dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 5
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 10
vm.dirty_writeback_centisecs = 500
dirty_writeback_centisecs 表示多久唤醒一次刷新脏页的后台线程。这里的500表示5秒唤醒一次。
dirty_expire_centisecs 表示脏数据多久会被刷新到磁盘上。这里的3000表示 30秒。
dirty_background_ratio 表示当脏页占总内存的的百分比超过这个值时,后台线程开始刷新脏页。这个值如果设置得太小,可能不能很好地利用内存加速文件操作。如果设置得太大,则会周期性地出现一个写 IO 的峰值。
dirty_ratio 当脏页占用的内存百分比超过此值时,内核会阻塞掉写操作,并开始刷新脏页。
dirty_background_bytes、dirty_bytes 是和 dirty_background_ratio、dirty_ratio 表示同样意义的不同单位的表示。两者不会同时生效。
总结
触发刷新脏页的条件:
1、调用fsync等。
2、脏页太多(相关参数:dirty_background_ratio与dirty_ratio)。
3、脏页太久(相关参数:dirty_expire_centisecs)。
参考资料
Linux Man Page
Better Linux Disk Caching & Performance with vm.dirty_ratio & vm.dirty_background_ratio
存储-索引
复制-同步副本
Broker配置-复制系数
•Kafka默认复制系数3
•topic级别的配置参数是replication.factor,而在broker级别则可以通过default.replication.factor来配置自动创建的topic
•更高的系数带来更高的可靠性,但需要的broker、存储副本数也会是N倍,同步副本的复制慢也会带来性能的下降
Broker配置-不完全leader选举
•unclean.leader.election.enable是否允许不完全选举,默认false
•如果leader不可用时,其他的副本都是不同步的,怎么处理?
•方式1:分区在旧leader恢复前不可用,因为旧leader才是唯一的同步副本;
•方式2:允许其他不同步的副本成为leader,但会导致该broker成为不同步副本后,producer写入旧leader的数据全部丢失,以上配置就是是否允许不同步的副本成为leader(也就是不完全的选举)
Broker配置-最少同步副本数
•在topic级别和broker级别上,这个参数都叫min.insync.replicas
•如果复制系数为3,最少同步副本数为2,那么当出现2个不同步副本时,leader将会停止接受producer的请求,收到NotEnoughReplicasException,而consumer可以继续消费
•这项配置对消息的可靠性保证有意义,假设当produceracks=all,但此时副本都不是同步的,broker也会响应成功,此时若leader不可用,数据可能还未刷入磁盘,导致丢失;而如果配置了最少同步副本为2,那么procuder将会收到异常,而不是只写到leader上
可靠使用producer
•合理使用acks
•合理使用重试次数:异常分为可重试和不可重试,适用于可重试异常
•合理处理异常
可靠使用consumer
•重要配置:
•group.id
•auto.offset.reset 在没有偏移量可提交(比如第一次启动)或请求的偏移量在broker上不存在时,有2种配置earliest,latest
•显式提交偏移量
•总是在处理完后再提交偏移量
•提交频率是性能和重复消息数量之间的权衡
•确保对提交的偏移量心里有数
•再均衡,注意处理消费者的再均衡问题
•消费者可能需要重试
•消费者可能需要维护状态-kafkaStreams
•长时间处理
•仅一次传递– 幂等处理;数据库约束分区、偏移量唯一性等方式来处理