用kafka也有一年多了,可以说,kafka是我设计系统时最重要的组件(没有之一)。略过基础概念不谈,总结一下论文里kafka的基本原理,相对比较零碎,主要是我本人之前不太了解的部分。
存储:
kafka有非常简单的存储层。每个topic的partitio对应一个逻辑log,一个逻辑log由若干segments 文件组成。每一次producer要往partition里写msg,broker就将msg追加到最后一个segment 文件。为了有更好的性能,只有当积累够某一数量的文件或一段时间过去后,才会将msg flush到文件中。一个msg只有被flush到文件中,才对consumer可见。
offset是递增的,但是不保证连续,如果一个consumer ack了一个message offset,这个意味着offset之前的消息都消费过了。
高效的数据传输:
producer可以一次提交多个msg。consumer的api是迭代访问message,一次一个。但是每一次拉消息是多个msg的。这个参数可以配置。
kafka层,内存中不缓存消息。而是依赖于操作系统的页缓存,这样避免了double buffering,因为kafka不在进程中缓存msg。所以jvm垃圾回收时的性能消耗很小。
网络层。一个典型的从本地文件到remote socket包括以下步骤:
- 从磁盘中读取数据到page cache
- 从page cache拷贝数据到应用缓冲区
- 从应用缓冲区拷贝数据到另一个kernel socket buffer
- 将kernel socket buffer 送到socket
这包括了4次数据拷贝和2次系统调用。linux存在直接将数据从文件写到socket的API,这可以省略2、3步骤,进而提高consumer从broker拉取消息的性能。
无状态的broker:
broker不保存consumer消费的位置。而是由consumer自己保存。
分布式
kafka和zookeeper:
- 检测broker和consumer的增加和减少
- 当"某个事件"发生时,触发rebalance
- 记录consumer消费partition的offset
当broker或consumer启动时,broker将自己的hostname和port注册到zookeeper,以及它存储的topic和partition。consumer注册自己属于的group以及自己订阅了哪些topic。zookeeper保存的broker和consumer信息都是ttl(超过过期时间即自动删除)。
当consumer启动时或consumer发现新的同组的consumer时,consumer执行一个reblance的算法。
当consumer检测到新的consumer启动时(通过zookeeper),会释放掉所有属于自己的partition重新执行reblance。
性能:
磁盘的顺序读写比Ram的随机读写性能还高,所以kafka得到了非常高的性能。另外对性能有影响的就是太多samll IO,以及额外的bytes拷贝,这两项都容易处理,一个用批量读写来解决,另一个是用Linux的sendFile API。
消息压缩:
在初始化producer时,可以指定压缩算法。