企业级应用中,任何开发人员都不希望系统出现bug,Kafka作为消息队列,更希望它能够实现精准一次处理的语义。
kafka的消息交付语义:
- 最多一次 消息可能被丢失也可能被处理,但是消息最多只能被处理一次。
- 至少一次 消息不会丢失,但是可能被多次重复消费
- 精确一次 消息被处理一次。
对于Kafka的使用者而言,不论是生产者还是消费者精确一次尤为重要。消费端的消费可以通过开发人员的编码去控制是否重复消费,对于生产者而言,Kafka提供了幂等性和事务来解决消息的重复生产。
幂等性:很多大厂的面试过程中都可能被问到如何保证接口的幂等性,幂等性其实就是同样的情况下多次请求得到的结果相同。kafka的幂等性就是同一消息被生产者端发送多次,broker端只写入一次日志,保证了消息的唯一性。
kafka是如何实现消息的唯一性的呢?
kafka会为每个producer实例分配一个produce id。producer在初始化的时候也必须被分配一个PID,这样消息被发送到每个分区的时候都有对应的序列化值,从0开始递增。
每个producer实例每次发往broker的消息都会被赋予一个序列号,这个序列号会保存到日志中。
这样可以构成一个键值对。key的值为 value是序列号。假如发送的消息小于等于broker端保存的序列号,broker就会拒绝消息的写入。即使生产者发生重试操作也可以保证写入的消息只有一条。
![8d188b430b3b24f05e7604eed92c24d2.png](https://i-blog.csdnimg.cn/blog_migrate/28670c53b33e5147d761098e2b6510ac.jpeg)
kafka的事务主要是解决幂等性无法处理的问题,比如同时往3个分区发送数据,2个成功,1个失败如何处理?
kafka的事务允许应用可以把消费和生产的batch处理(设计多个Partition)放在一个原子单元内完成,操作要么完全失败、完全成功。通常事务的应用在生产者发送数据的过程中。
![c357cd742f596418e0bbbe88b036e38b.png](https://i-blog.csdnimg.cn/blog_migrate/2e52cde08c54d4a1cebc97139af19302.jpeg)
Kafka为了实现事务,要求应用程序提供一个唯一 id来代表事务id,这个id可以理解为:TransactionnalId。
kafka事务的工作原理:
![4a317a7c8b7ed64f2e322de36afcd26e.png](https://i-blog.csdnimg.cn/blog_migrate/fc5b12a91ac9c067717d7c035fd4c3bd.jpeg)
Coordinator事务协调器,对分布式事务有了解的对它应该不陌生。事务协调器在每个broker中都有运行,事务日志是kafka中的某一个topic,每个TransactionnalId都通过哈希函数映射到事务日志的特定分区内。
执行事务的时候,producer会先向协调者发送请求注册TransactionnalId,并且为事务id分配分区,协调者的关于当前事务的状态保存在事务日志中。当应用程序调用commitTransaction或者abortTransaction的时候,再次向协调者发送请求,以两阶段的方式提交协议。
第一阶段,协调者将内部的状态更新成prepare_commit,事务日志中也同步更新。
第二阶段,事务提交的时候会把事务标记写入作为事务的一部分的Topic分区中,事务标记是进行回滚或者过滤未提交数据的依据,事务标记并不会暴露给应用程序,只是在read_committed的模式下Consumer端会过滤未提交的数据。标记写入topic分区,则表示事务完成。
Kafka日志的存储设计
学习kafka的时候,Kafka高效的原因之一就是日志分段、日志追加记录。kafka为每个topic的分区都设置了单独的日志文件,每个分区日志都是多个日志段文件+索引文件组成。日志段的文件大小可以通过log.segment.bytes配置。
![fa8fb54484681f84a85d4a28f3aa9699.png](https://i-blog.csdnimg.cn/blog_migrate/27af0e38f0b382077f44894b59fc874c.jpeg)
索引文件有存在时间戳文件和位移索引文件,存在索引文件的目的是可以通过二分法查找降低时间复杂度,位移文件的索引项的格式是:
![3f524ba24407a98de372a2821957994b.png](https://i-blog.csdnimg.cn/blog_migrate/61d2e321e522c4463ada462ffc5fe9f6.jpeg)
时间戳索引文件的格式:
![593c59a21fe70b7c56454cd749895fb0.png](https://i-blog.csdnimg.cn/blog_migrate/b20463eea6683aa19036a807c86ae986.jpeg)
kafka会一步默认清楚7天前的日志段文件。
kafka Controller设计思想
kafka Controller 用来管理和协调Kafka的集群,管理集群内的所有分区的状态并且执行相关的管理操作。集群中只存在一个kafka Controller。
![bce09a6d3fa0e8720fa80572c6e53c2a.png](https://i-blog.csdnimg.cn/blog_migrate/622dd90f5932869e037b146212f36d42.jpeg)
kafka Controller管理分区副本,在新建副本的时候,Controller随机选择一个副本作为分区的leader副本并设置副本进入ISR,Zookeeper存储这些信息。Kafka Controller管理分区的状态管理。当用户需要删除topic或者关闭broker的时候,controller会让受到影响的分区进入到下线状态,不对外提供工作。kafka Controller负责更新集群的元数据。当分区的信息发送变化的时候,controller进行广播,通知每个broker进行更新元数据。kafka Controller负责新增删除topic。当新建topic的时候,是在zookeeper中新增一个数据,controller监听zookeeper并对broker进行广播。
kafka Controller和broker通过Socket连接,存在一个broker则建立一个Socket。熟悉Netty的同事应该了解Reactor模式,Client与broker之间的通信就是采用的Reactor模式(分发器会把入站请求根据多路复用的方式转发到对应的请求处理器中)。
客户端和broker的连接一般都会保持长连接,broker端的请求处理流程图:
![6e5dbd288812260f9556337893de2a93.png](https://i-blog.csdnimg.cn/blog_migrate/87ebc3049907a1d44c5fcccb7551e558.jpeg)
kafka Controller的组件:
![54772edb13c4e4f71ffa35564910bc77.png](https://i-blog.csdnimg.cn/blog_migrate/9f4493b64aabf16dfd00affe84f55ef5.jpeg)
ControllerContext:Controller的上下文,汇总了zookeeper上关于Kafka集群的所有元数据信息。
Kafka副本和ISR设计
Kafka的副本可以理解为备份日志,多副本就是利用多份相同的备份共同提升冗余机制来维护Kafka的高可用。Kafka的副本会均匀地分布到多个broker上,然后从副本中挑选一个leader副本,剩下的均为follower副本,follower只能被动的从leader中请求数据获取同步。
当leader副本宕机后,follower副本就会争抢成为leader。此时有资格参与竞争的只有存在于ISR中的副本。
ISR是一组同步副本集合,每个topic分区都有自己的ISR列表,ISR中的所有副本和leader副本保持同步,leader副本也在ISR列表中。注意:producer端写入的消息只有被写入ISR副本的时候才会被视为“已提交”,这种操作可以防止消息不丢失。
有兴趣的同事可以去查看下副本之间是如何进行同步的:
https://blog.csdn.net/lizhitao/article/details/51718185
kafka消息的实现实际上采用的是JAVA NIO的ByteBuffer来保存消息的,同时依赖文件系统提供的页缓存机制来实现持久化操作。
![cc54ef9d51268764ef4bac6e413bed7d.png](https://i-blog.csdnimg.cn/blog_migrate/4e0480e149458c5012d069c545c691b2.jpeg)
我是凯腾凯,互联网浪潮下一枚苟且偷生的程序员