前言
Kafka最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统、低延迟的实时系统、storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目。
1.简介
1.1 特性
1. 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒。
2. 可扩展性:kafka集群支持热扩展。
3. 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失。
4. 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
5. 高并发:支持数千个客户端同时读写
6. 不可靠性:为了高性能,降低了部分可靠性,消息存在丢失和重复的情况。
1.2 使用场景
基于Kafka的特性,一般应用在日志收集,消息系统,流式处理等对吞吐量要求较高,但对可靠性要求较低的场景下。
与Kafka相比,RabbitMQ/RocketMQ更侧重于消息的可靠性,一般用于金融或电商订单业务。
2.架构图
3.消息存储
3.1 消息格式
每个partition分区在broker上保存为一个文件目录,命名为<topic_name>_<partition_id>。
每个partition目录下包含多个相同大小的segment文件,并以文件内首个消息的offset命名,扩展名为.log。
segment文件内消息存储格式为:<offset> <message_size> <message>,每个partition分区的offset都是独立并递增的。
每个segment文件维护一个索引,扩展名.index,支持针对offset的二分查找。
3.2 消息删除
无论消息是否被消费,Kafka 都会保存所有的消息。那对于旧数据有什么删除策略呢?
基于时间,默认配置是 168 小时(7 天)。
基于大小,默认配置是 1073741824。
需要注意的是,Kafka 读取特定消息的时间复杂度是 O(1),所以这里删除过期的文件并不会提高 Kafka 的性能!
4.生产者设计
4.1 producer写
写入时需要指定topic,key和partition可选,如果partition没有指定,则根据key做hash取模得到partition,如果key也没有设置,则用轮询。
每个topic-partition有一个发送队列,业务将消息写入队列,后台线程根据batch_size和linger.ms最大等待时间执行批量发送,这样会造成消息的延迟,但是却减少网络IO,提高了吞吐量。
4.2 集群写
producer从broker集群中获取当前分区对应的leader,并将消息发送给leader。
leader负责将消息写入log,并等待其他副本更新。
其他从leader同步消息,并写入log,返回给leader ack。
leader收到所有副本返回的ack,判定消息写入成功,返回给producer成功。
为提高吞吐量,默认配置为当leader写入成功,就返回成功,此时如果leader服务挂掉,会造成数据丢失。
5.消费者设计
5.1 消费流程
由于partition分区只对应一个consumer,所以推荐consumer的个数和分区个数一样,这样能形成点对点,处理效率是最高的,如果consumer个数小于分区个数,则每个consumer会被分配多个partition,如果大于分区个数,则超出的consumer节点不会分配到partition,消费不到数据。
consumer从broker集群获取当前分区的leader,并从leader批量pull消息,并提交消息的offset,为提高吞吐量,默认为5秒自动提交一次offset,但这样会造成消息的丢失和重复。
5.2 已消费offset
Kafka集群在每个partition上为每个消费组维护一个已消费offset,每次consumer消费完成并提交后,集群都会更新这个offset。
在历史版本中,这个offset信息是维护在zookeeper中,新版本维护在 __consumer_offsets 这个 Topic 中。
5.3 rebalanced机制
Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 consumer 如何达成一致,来分配订阅 Topic 的每个分区。
Rebalance 的触发条件主要有2个:
1.组成员个数发生变化,增加组员或者减少组员。
2.订阅的partition分区数发生变化。
5.4 心跳监测
集群通过消费端的两个线程来监测状态,一个是心跳线程,一个是用户poll线程。
心跳线程根据heartbeat.interval.ms参数(默认3s),定时向集群发送心跳包,心跳线程用于快速监测消费端的故障,尽早rebalance。
用户poll线程从集群循环拉取消息,如果两次poll的时间间隔超过了max.poll.interval.ms(默认300s),则认定消费端故障,执行rebalance。
Kafka 0.10版本之前心跳包是放在poll线程去发的,这样导致为了满足业务处理时间,heartbeat.interval.ms时间要设置的很大才行,如果消费端出了故障,心跳监测不能马上检查到。
6. 高可用设计
Kafka在0.8以前的版本中,并不提供High Availablity机制,一旦一个或多个Broker宕机,则宕机期间其上所有Partition都无法继续提供服务。
6.1 副本机制
同一个Partition会有多个Replication,并选出一个Leader,Producer和Consumer只与这个Leader交互,其它Replica作为Follower从Leader中复制数据。
6.2 zookeeper管理
引入zookeeper来管理broker的动态加入和离开,实现故障发现和leader选举。
zookeeper同时也会管理consumer的动态加入与离开,Producer不需要管理,随便一台计算机都可以作为Producer向Kakfa Broker发消息。
7. 高吞吐设计
Kafka基于页缓存计算+磁盘顺序写,实现了写入数据的超高性能。
基于零拷贝技术,提高了读取数据的性能。
7.1 页缓存技术
文件读写并不是直接访问磁盘,而是利用到了操作系统的page-cache(页缓存),所以写磁盘文件其实就是在写内存。
7.2 磁盘顺序写
普通的机械磁盘随机写的性能极差,也就是随便找到文件的某个位置来写数据。
如果是追加文件末尾按照顺序来写数据的话,和写内存的性能是差不多的。
7.3 零拷贝技术
正常的数据发送流程:将数据从page-cache拷贝到应用程序的进程缓存中,然后调用write方法,将数据再拷贝到内核socket发送缓冲区中,再经过网卡发送出去。
零拷贝发送流程是:仅仅拷贝socket的描述符,然后数据就直接从page-cache中发送到网卡,节省了两次数据的拷贝。
零拷贝的好处有:
1.避免操作系统内核缓冲区之间进行数据拷贝操作。
2. 避免操作系统内核和用户应用程序地址空间这两者之间进行数据拷贝操作。
3. 减少内核和用户进程的上下文切换。
4. 数据传输尽量让 DMA 来做,解放了cpu。
8. 不可靠特性
Kafka是为了高吞吐量设计的,在满足性能的前提下,不可避免的会带来一些不可靠问题。
8.1 消息丢失
生产者丢失
生产者采用定时批量发送数据,如果期间生产者进程挂掉,消息来不及发送出去,则消息丢失,解决办法是减少消息发送的最大等待时间,比如可以配置为5ms,从而减少消息丢失的数量和几率。
集群丢失
Kafka默认是同步写入,只要leader写入成功就返回成功,此时如果leader挂掉,其他副本还没来得及同步消息,则消息丢失,解决办法是配置为等待所有副本写入成功后,才返回成功,此时会降低写入的性能,影响吞吐量。
消费者丢失
消费者设置为自动提交时,如果消息被提交后,还没来得及处理,进程挂掉,此时消息丢失,解决办法是改为手动提交,牺牲性能。
8.2 重复消费
重复消费问题无法完全避免,如果业务系统不能容忍消息重复,需要自己实现幂等性。
生产者重复
生产者发送完消息,因为网络问题没有收到response,此时会重发消息,造成消息重复。
消费者重复
消费者设置为自动提交时,如果业务层消息处理时间太久,超过了max.poll.interval.ms(默认300s),则判定消费端故障产生rebalance,再次poll时仍获取到之前的消息,导致重复。解决办法是减少max.poll.records(poll的消息个数),尽量保证消息处理的够快。
在自动提交模式下,只要集群产生rebalance,已处理过但来不及提交的消息都会被再消费一次,导致重复。
8.3 同分区消息乱序
生产者发送消息时,如果前一个消息未响应,可以继续发送消息,如果前一个消息最终超时导致重发,则会出现消息乱序。
配置max.in.flight.requests.per.connection:限制客户端在单个连接上能够发送的未响应请求的个数。设置此值是1表示kafka broker在响应请求之前client不能再向同一个broker发送请求,但吞吐量会下降