目标
kafka的目标是成为一个能够处理所有实时数据流的统一平台,所以设计者考虑了很多方面,像高吞吐(海量实时日志聚合)、优雅存储海量数据(定期拉取离线系统数据)、低延迟(消息队列)、分布式实时流处理(这驱动了分区和消费模型)以及容错能力。最终kafka采用更类似数据库而不是消息系统的设计。
存储
kafka的存储使用的是磁盘,因为一个良好设计的磁盘通常比网络更快。现代操作系统往往使用内存来缓存磁盘(页缓存)。kafka使用页缓存来改善java中直接使用内存的对象开销、GC、服务重启和冷启动等问题。简单的说就是使用操作系统的页缓存,而不是直接使用内存、内存不足时刷盘。
消息系统多采用BTree结构,复杂度O(logN),通常来讲这与O(N)等同,但对于磁盘(寻道,并行受限)会更慢。kafka采用追加数据到文件的O(1)复杂度的方式,读写分开。在大量数据时,有更低的成本和更高的容量,近乎无限的磁盘空间使消息没有必要尽快删除。从而提供了多次消费,消息保留等其他消息系统没有的功能。
效率
Kafka在效率上做了很多努力。对于磁盘来说,优化磁盘的访问模式后,其他通用的问题是太多small I/O和过高的byte copying。small I/O会在C/S间或者是S持久化操作中产生,kafka对消息分组,使用消息块而不是单条消息来解决,将突发消息转化为线性写入。对于byte copying,低消息速率不是大问题,但对负载的影响却是重要的。kafka使用标准化的消息格式,生产者服务器和消费者共享,从而避免对数据分块的修改。
Kafka使用sendfile将页面缓存转化到socket。避免了内核空间与用户空间的拷贝。零拷贝让消费速率几乎只受到网络的影响。
另外Kafka使用数据压缩来解决网络带宽的问题。
生产者
生产者直接发送数据到leader所在的服务器是不需要任何中间路由的,kafka所有节点都可以答复生产者topic的leader在哪,从而帮助生产者直达消息。这使得客户端控制发送到那哪里,比如基于hash的存储。kafka使用消息累积,批量发送的异步机制。用少量的延迟换取更好的吞吐。
消费者
消费者通过fetch告知服务器消费行为,每次请求告知它的offset,然后从offset处拿到一块消息,从而可以倒回重复消费。在push和pull的消费模式上(推拉本身有利有弊),kafka按照经验采用拉取模式。kafka支持长轮训参数,解决pull模式下的无休止拉取数据问题。kafka不同于传统的ack模式,用offset标记消费状态。可伸缩的持久化可以实现定期将数据批量加载到Hadoop等离线系统。
消息投递语义
最多一次(kafka消费者保证)、最少一次(kafka生产者,消费者提供)、明确一次(kafka streams提供),对于消费者的消费语义,与提交offset的时机有关。kafka在发送消息时有committed概念。生产者在得不到committed信息时,可以重发,但如果之前的其实已经成功了,那么就会重复,从kafka的0.11.0.0起,服务器可以通过生产者ID和发送序列号进行去重。并且还支持类事务的语义。然而在对延迟敏感的场景,大多数采用异步而不需要强保证。对于消费者,如果先提交再处理,就是最多一次,先处理再提交就是最少一次了。明确一次的语义需要使用事务,让消费者的offset存储和消费者的输出存储之间实现一个两段式的提交。
复制
kafka通过复制因子配置topic分区复制。kafka不同于其他系统,默认就是使用复制的,通过只有一个副本的topic实现不复制的topic。topic复制的单元是分区。读写去往leader,follower像普通消费者一样从leader消费,并保存在自己的log上。kafka判断节点存活需要心跳保持+较少的lag。同步的节点被成为in-sync节点。之前的committed是所有的in-sync节点都收到消息之后,但生产者可以设置minimum number(acks = 0 ,1 or all)。只要有一个in-sync活着,消息就不会丢失。kafka使用ISR机制,而不是多数票仲裁等,可以让f+1个副本的topic容忍f次失败。如果节点都宕了,需要在可用性和一致性之间简单折衷。要么停机等待同步副本要么允许不同步的副本成为leader。kafka试图平衡分区和leader,避免少数节点上集中高容量的分区。
日志压缩算法
限流
领取专属 10元无门槛券
私享最新 技术干货