因为简历上写了很多关于kafka的内容,所以在这里总结一下底层原理。
首先先说一下自己对Kafka整体的印象和理解
Kafka是一个吞吐效率很高的消息队列。一个kafka节点也可以叫做一个broker,一个broker里面有很多topic,我们存储消息的时候,会根据topic去存储,也会根据监听topic去消费。
topic内部,又有很多的partition,而partition从本质上来说,是一个append log文件,也就是说,我们存储消息的时候,默认是按顺序去写入到硬盘的。(按顺序写入硬盘比随机写内存要快,因为磁盘有预读机制)。
而存储进去到partition的这些消息,它们所在的位置,统称为偏移量offset,每一个offset对应一条消息,且唯一。
一个消息要经过broker的时候,会根据设定好的topic去存储,然后根据规则会存入partition,如果规则设置的好,能让消息平摊,就可以实现水平扩展。
而如果一个topic只对应一个partition,那么这个partition失去了平均分片消息的特点,磁盘IO会成为性能瓶颈。
Kafka的高可靠性存储分析
其实存储可靠性,还是基于partition去实现,也就是提供partition的副本(replication)。
先说一下kafka的底层文件存储机制,上面也说了,broker里面有很多topic,每个topic又对应很多partition,而这些partition又可以细分成segment,一个partition物理上有很多个segment组成。而之所以这么分配的原因,是因为如果生产者不断的去生产信息,那么就会导致partition无限的扩张,这对于消费数据的清理和消息文件的维护都是很麻烦的。
所以就有了Segment的概念,每一个Segment的大小都相同,他们主要作用就是提高消费数据清理和消息文件维护的性能。Segment文件由两部分文件组成,分别为.index文件和.log文件,分别代表的是索引文件和数据文件。
另外插一句,partition和Segment的命名规范分别是topic+有序序号(从0开始)和offset值。
那么kafka是如何复制和同步的呢?
刚才也说了,kafka中的每一个topic都有一堆的partition,这些partition的数据结构是以append log去存在的,虽然刚才也说partition下面有一些Segment了,但是实际上,上层应用都是直接将partition识别成一个最小单位去操作的。 上面展示的是一个partition的数据结构。 HW的意思是Consumer能够看到的partition所在的offset LEO的意思是partition实际最后一条message所在的位置
然后说正题,其实复制刚才也说了,我们有个副本复制策略,目的就是当broker宕机的时候,还能保证正常可用,其实也不好说是宕机,因为我们分布式都是通过zk去进行服务一致性把握的嘛,所以就会有心跳,timeout了,就会自动让副本上场了。
并且需要讲一个最重要的一点,这些副本在替换broker的时候,有一个副本是leader,其他都是follower,leader负责接收所有broker的读写请求,follower负责同步leader的消息。
那么我们再提,如果多副本策略中,leader出毛病了怎么办,其实kafka会内部在多副本中,重新选取一个leader接入客户端,因为leader本身也是需要维护和追踪副本同步队列的。
意思就是说,如果follower同步leader消息不及时,就会直接删除掉这个follower。
那么Kafka是如何保证数据可靠性和持久性的呢?
当producer向leader发送数据时,可以通过request.required.acks参数来设置数据可靠性的级别:
1(默认):只有当leader接收到数据并且return一个确认信息后,producer才会继续发送消息0:不管leader是否接收到消息,都玩命发-1:leader和follower都接收到消息后,才能继续发,但即使这样也不一定完全可靠,因为如果ISR只有一个副本,那就是纯leader,等效于默认的情况。这里需要补充一点的就是,如果follower1和follower2在分别同步了消息12和消息1的时候,follower2突然被选举为leader,那么follower1多出来的消息要怎么处理,这时候就需要HW的协同配合了。
这里还是基于一个短板效应的,如果这个时候去进行同步,是会同步到append log最短的follower为基准,然后再拉取其他的消息去进行同步。
如果follower恢复过来,会首先将自己的log文件截断到上次checkpointed的HW位置,也就是Consumer能识别的到的最低位置,然后同步leader的信息,进行重新选取,后面就是同理了。
**Kafka是如何去选举Leader的? **
一般情况下,都是少数服从多数去进行选取的操作,但是kafka是不一样的,少数服从多数有一个问题就是,为了保证leader的正常选举,能容忍失败的follower很少,都是采用的2f+1的公式,也就是说,如果你是3个节点,那挂的follower不能超过1个,5个节点就是2个,但是如果我们节点很多的话,副本就会也很多,副本多了,性能就下降了,kafka属于是数据共享类型,而不是配置共享,不是Hadoop HA那种,所以不适合这种,因为数据量太大了,负担也很大。
而kafka是这样的,如果我们有2f+1个副本,在commit之前只需要保证有f+1个副本复制完成,就可以进行选举了,优势在于,复制的成功与失败取决于延迟最低的节点。
实际上选举算法有很多,zk的paxos,raft的vr。。
kafka是如何保证可靠性传输策略的?
kafka自身定义了三种可靠性传输的策略:
At most once:消息可能会丢,但是不会重复传输.At least once:消息绝不会丢,但是可能会重复传输.Exactly once:每条信息肯定会被传输一次且仅传输一次.实际上kafka的设计十分简单,因为有副本在,所以消息绝对不会丢失,但是会有因为网络问题,导致producer传入broker的时候数据没传过去,但是我们可以进行retry,这就会导致有可能数据会重复发送,默认kafka就是用的At least once这种策略。
当consumer从broker中读取消息的时候,可以选择commit,commit之后会在zk中存下读取msg的offset,然后后面按照offset继续消费,毕竟他是append log嘛,但是如果出现网络问题的话,exactly once也是会变成at least once的。
但是如果consumer读完消息之后再commit消息,这时候consumer还没来得及处理就crash了,那么后面就会无法读到刚刚commit未处理的数据,也就达到了at most once了。
Kafka是如何进行消息去重的呢?
就像之前所说的,at least once会有数据重复的情况,实际上kafka自身有一个GUID的策略。
通过客户端生成算法得到每个消息的unique id,同时可映射至broker上存储的地址,即通过GUID便可查询提取消息内容,也便于发送方的幂等性保证,需要在broker上提供此去重处理模块,目前版本尚不支持。
针对GUID, 如果从客户端的角度去重,那么需要引入集中式缓存,必然会增加依赖复杂度,另外缓存的大小难以界定。
不只是Kafka, 类似RabbitMQ以及RocketMQ这类商业级中间件也只保障at least once, 且也无法从自身去进行消息去重。所以我们建议业务方根据自身的业务特点进行去重,比如业务消息本身具备幂等性,或者借助Redis等其他产品进行去重处理。
什么是kafka的消费者组
消费者组是kafka实现单播和广播的重要方法,指的是可以用一个topic,让消费者共同拿到这个topic的全量数据。