Kafka详解原理及问题处理
1、Kafka简介
Kafka是一个由Scala和Java编写的企业级的消息发布和订阅系统,最早是由Linkedin公司开发,最终开源到Apache软件基金会的项目。Kafka是一个分布式的,支持分区的,多副本的和多订阅者的高吞吐量的消息系统,被广泛应用在应用解耦、异步处理、限流削峰和消息驱动等场景。
2、Kafka的整体架构
- Producer:消息的生产者,往Kafka中写数据
- Kafka Cluster:Kafka集群,包含多个Broker
- Broker:Kafka服务器,每一个kafka服务器就是一个Broker
- Topic:消息主题,可以理解为消息队列,Kafka的数据就保存在Topic中。在每个 Broker 上都可以创建多个 Topic
- Partition:Topic的分区,每个 Topic 可以有多个分区,分区的作用是做负载,提高 Kafka 的吞吐量。同一个 Topic 在不同的分区的数据是不重复的
- Replication:每一个分区都有多个副本,副本的作用是做备胎,主分区(Leader)会将数据同步到从分区(Follower)。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为
Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本- Consumer:消费者,即消息的消费方,是消息的出口。
- Consumer Group:我们可以将多个消费组组成一个消费者组,在 kafka 的设计中同一个分区的数据只能被消费者组中的某一个消费者消费。同一个消费者组的消费者可以消费同一个topic的不同分区的数据,这也是为了提高kafka的吞吐量!
- Zookeeper:kafka 集群依赖 zookeeper 来保存集群的的元信息,来保证系统的可用性。
2.1 Producer架构
消息在通过send()方法发往broker的过程中,有可能需要经过拦截器、序列化器、和分区器的一系列作用之后才能被真正地发往broker。
2.1.1 Producer的分区策略
1. 指定partition时,使用指定的partition
2. 没有指定partition,有key时,使用key的hash值与partition数取余,得到partition
3. 没有指定partition,没有指定key时,采用黏性分区器,会随机选择一个分区,当分区的batch已满或已完成,再随机选择一个分区
2.1.2 asks的配置
- 1:生产者发送消息,等待leader同步之后返回ask,也有可能丢失数据(follower还没有同步数据,leader就宕机了),默认配置
- 0:生产者发送消息就返回,容易丢失数据
- -1:生产者发送消息,等待所有的follower同步之后返回ask
2.1.3 压缩方式对比
可以看出:
- 吞吐量方面:LZ4 > Snappy > zstd 和 GZIP
- 压缩比(压缩前大小/压缩后大小)方面:zstd > LZ4 > GZIP > Snappy
- 具体到物理资源,Snappy占用的网络带宽最多,zstd 最少。在 CPU 使用率方面,各个算法表现得差不多,只是在压缩时 Snappy 算法使用的 CPU 较多一些,而在解压缩时 GZIP 算法则可能使用更多的 CPU。
2.2 Consumer
2.2.1 Consumer、Consumer Group关系图
这个图可以清晰的说明Consumer 、Consumer Group 、Partition的关系。
- 同一个分区的消息,可以被不同组的消费者消费。但同一个组内的消费,对于同一个分区,只能有一个消费来消费。
- 不同分区可以被同一个消费者消费
- 同一个Consumer Group中,消费者和分区的关系是 1对多。
2.2.2 Consumer分区策略
(1) Range(默认)
将分区按照序号排序,消费者按照字母排序,用分区数除以消费者数(partition/consumer),来决定每个消费者消费几个分区,如果除不尽,前几个消费者每个多消费一个分区。
优缺点:
- 每次都针对同一主题进行轮询
- 可能导致消费者消费的分区数不一致
(2) RoundRobin
优缺点:
- 由于采用整体轮巡的方法,使得各个消费者之间的消费量差异并不大。
- 如果消费者组面对多个主题,可能出现不符合业务逻辑的情况。例如:如果业务逻辑是让消费者A只获取T1的数据,那么整体轮巡的方法将让A也能获得T2的数据。同理,B也能获得T1的数据。因此,RoundRobin的适用场景是 消费者订阅了所有主题,每个消费者之间所消费的数据量差异较小。
3、Kafka的高效读写
3.1 页缓存技术 + 磁盘顺序写
具体流程如下:
- 发送数据到kafka中
- Kafka将数据写入到Page Cache中
- 由操作系统来决定什么时候将数据刷入到磁盘文件中
磁盘顺序写说明:
关于磁盘性能的关键事实是,磁盘的吞吐量和过去十年里磁盘的寻址延迟不同。因此,使用6个7200rpm、SATA接口、RAID-5的磁盘阵列在JBOD配置下的顺序写入的性能约为600MB/秒,但随机写入的性能仅约为100k/秒,相差6000倍以上。因为线性的读取和写入是磁盘使用模式中最有规律的,并且由操作系统进行了大量的优化。
基于上面两点,kafka就实现了写入数据的超高性能。
3.2 零拷贝技术
假设要是kafka什么优化都不做,就是很简单的从磁盘读数据发送给下游的消费者,那么大概过程如下所示:
- 先看看要读的数据在不在os cache里,如果不在的话就从磁盘文件里读取数据后放入os cache。
- 接着从操作系统的os cache里拷贝数据到应用程序进程的缓存里,再从应用程序进程的缓存里拷贝数据到操作系统层面的Socket缓存里,最后从Socket缓存里提取数据后发送到网卡,最后发送出去给下游消费。
很明显可以看到有两次没必要的拷贝。一次是从操作系统的cache里拷贝到应用进程的缓存里,接着又从应用程序缓存里拷贝回操作系统的Socket缓存里。而且为了进行这两次拷贝,中间还发生了好几次上下文切换,一会儿是应用程序在执行,一会儿上下文切换到操作系统来执行。所以这种方式来读取数据是比较消耗性能的。
Kafka为了解决这个问题,在读数据的时候是引入零拷贝技术。也就是说,直接让操作系统的cache中的数据发送到网卡后传输给下游的消费者,中间跳过了两次拷贝数据的步骤,Socket缓存中仅仅会拷贝一个描述符过去,不会拷贝数据到Socket缓存。
通过零拷贝技术,就不需要把os cache里的数据拷贝到应用缓存,再从应用缓存拷贝到Socket缓存了,两次拷贝都省略了,所以叫做零拷贝。对Socket缓存仅仅就是拷贝数据的描述符过去,然后数据就直接从os cache中发送到网卡上去了,这个过程大大的提升了数据消费时读取文件数据的性能。而且大家会注意到,在从磁盘读数据的时候,会先看看os cache内存中是否有,如果有的话,其实读数据都是直接读内存的。
4、Kafka相关问题
4.1 分布式的情况下,如何保证消息的顺序消费
- 将相同的消息放入同一个partition,一个partition只能被同一个消费者消费
- 将消息指定相同的key,相同的key的消息会放入同一个partition中
4.2 如何保证消息不被重复消费
- 生产者在发送消息时,可以携带一个全局的唯一id,消费者在消费时,查询是否已经消费过,然后做不同处理
- 如果只是插入操作,根据数据特点,数据库可以设置唯一索引
4.3 消息积压了如何处理
- 如果是Kafka消费能力不足,则可以考虑增加 topic 的 partition 的个数,
同时提升消费者组的消费者数量,消费数 = 分区数 (二者缺一不可) - 增加消费者数量
- 可以使用多线程消费
- 消费者消费改为批量拉取数据
- kafka单机升级成了集群
- 避免消费者消费消息时间过长,导致超时
- 使Kafka分区之间的数据均匀分布
4.4 Kafka数据丢失处理
4.4.1 生产者丢失数据(网络原因)
设置ack=-1(等所有的副本同步完成之后再提交)
4.4.2 Broker丢失数据(先将数据同步到PageCache中,在异步刷盘,在没有执行刷盘操作时Broker宕机)
(1)采用多分区多副本方式可以尽可能减少Broker数据丢失情况
(2)禁止非 ISR 中的副本选举为 Leader
(3)消息写入 ISR 个数
4.4.3 消费者丢失数据(自动提交数据)
设置为手动提交数据
5、Kafka的选举机制
5.1 Broker的选举
- 集群中每个broker都会尝试在zookeeper上创建/controller临时节点
- Zookeeper会保证有且仅有一个broker会创建成功,这个broker就是整个集群broker的controller,其余broker监听这个controller
- 当controller宕机时,临时节点消失,其余的broker会再次去竞选新的controller
5.2 Partition的选举
Controller感知到分区的leader宕机时,会从ISR列表中挑出第一个broker作为leader
参考文章:https://blog.csdn.net/weixin_43118938/article/details/120653207