目录
11 为什么不让一个partition被同组的多个consumer消费
1 介绍下kafka?
kafka是一款分布式流处理平台,用于实时构件流处理应用。它有一个核心的功能广为人知,即作为企业级的消息引擎被广泛使用。
2 Kafka是怎么设计的?
-
Broker:也就是我们的一个kafka服务,我们的kafka是以集群的方式运行的,其中的每一个节点就称为一个broker
-
Topic:主题,就是我们实际用来承载消息的容器,在实际使用中多用来区分我们的业务
-
Partition:分区,一个有序不变的消息序列,一个主题可以有多个分区
-
Replica:副本,一个分区可以有多个副本,冗余使用
-
Record:消息,我们kafka生产和消费的基本对象
-
offset:消费者位移,指向消费者下一条要消费的消息,消费者位移前的消息都认为已经被消费
-
Producer:生产者,生产消息的对象
-
Consumer:消费者,消费消息的对象
-
Consumer Group:多个消费者实例构成一个消费者组,共同消费多个分区以提高吞吐量
3 为什么要用消息系统?
-
解耦:允许我们独立修改消息队列两边的代码而互相不收影响
-
冗余:消息队列会将数据进行持久化直到他们已经被完全处理
-
削峰填谷:不会因为上流突发的流量而导致下游系统崩溃。消息队列能够使服务顶住突发的访问压力,有有助于解决生产者消费者速度不一致的问题
-
异步通信:允许用户把消息放入消息队列后不处理它,等待后续进行消费处理
4 Consumer Group机制?
我们的kafka是一款高吞吐量,低延迟,高并发,高可扩展性的消息队列产品,如果某个topic拥有数以百万计的数据量,仅仅依靠consumer进程来消费,消费速度可想而知非常之慢,所以需要一个扩展性较好的机制来保障消费进度,这个时候Consumer Group应运而生。consumer group就是kafka提供的可扩展且具有容错性的消费者机制。有以下特点:
-
每个consumer group有一个或多个consumer
-
每个consumer group有一个唯一标识id
-
consumer group在消费topic时,每个topic的每个分区只能交由一个组内的consumer消费,只要被组内任何consumer消费一次,就认为这条数据被当前consumer group消费成功
5 Consumer的分区分配策略有哪些?
-
RangeAssignor
-
对于每个topic按照分区id进行排序
-
对订阅这个topic的consumer进行排序
-
尽量均衡按照范围区段将分区分配给consumer
存在的问题:若有多个topic则首先被分配分区的consumer可能会任务过重,因为可能出现分区数/订阅的消费者数不能整除的情况
-
-
RoundRobinAssignor
-
将consumer group订阅的所有topic的分区进行排序
-
对所有consumer进行排序
-
然后将订阅同一个topic的consumer按序挨个分配
当consumer订阅的topic都相同时能保证尽量均衡,但是若consumer订阅的topic不同时就不能保证尽量均衡
-
-
StickyAssignor
与RoundRobinAssignor差不多,只不过在进行Reblance时后者会完全重新分配,前者会在原先的基础上进行再分配
6 Reblance机制?
什么是reblance
当我们consumer group中有consumer的加入或者退出的时候,那么consumer列表就会发生变化,从而引起partition的重新分配。该过程需要借助broker端的协调者组件
触发时机
-
当consumer group组成员数量发生变化的时候
-
当consumer group订阅的主题数发生变化的时候
-
当consumer group订阅的分区数发生变化的时候
reblance如何通知其他consumer
consumer端的心跳线程会定期的发送心跳请求个broker端的协调者,当发生reblance时,协调者会将相应的心跳响应发送给consumer告知其即将进行Reblance
Reblance过程分析
举个例子,现在consumer group中有消费者c1,现在c2请求入组
-
c2向协调者发送join group请求,告诉协调者自己要加入消费者组,并给出自己的订阅列表
-
协调者收到请求会通过和c1之间的heartbeat响应告诉c1要进行Reblance了,请重新入组
-
c1受到reblance通知后据发送joingroup请求给协调者,并给出自己的订阅列表
-
由于c2的joingroup请求是先到的,所以在给c2的joingroup响应中会告诉c2他是leader并给出消费者组的订阅信息
-
c2受到joingroup响应后就开始进行分区重分配,然后发送syncgroup请求给协调者并说明分组情况
-
协调者在通过syncgroup回应告诉c1和c2他们的分区情况,至此reblance结束
重新分区的策略
-
range策略
该策略就是按照范围将分区进行平均分配,比如3个消费者,6个分区:
1 : 1, 2
2 : 3, 4
3 : 5, 6
-
round-robin策略
分区顺序循环分配给不同的消费者:
1 : 1 4
2 : 2 5
3 : 3 6
-
sticky策略
尽量保证原来消费者持有的分区不发生变化
比如原先:
1 : 1 4
2 : 2 5
3 : 3 6
消费者3挂掉了,这是分区分配为:
1:1 4 +3
2: 2 5 +6
7 位移提交?
什么是消费者位移
消费者位移表示的是我们consumer下一条要消费的消息。consumer offset以前的消息我们都认为是已经消费了,consumer offset及其之后的消息还没有被消费,这样当我们消费者发生重启时能够保证不重复消费已经消费的消息。
位移提交方式
-
自动提交
kafka后台帮助我们进行位移提交,默认是每5s一次,存在问题时若该时间间隔内发生了reblance,由于此时的offset还没有提交,所以可能发生消息的重复消费
-
手动提交
-
同步提交:一直阻塞直到位移提交成功
-
异步提交:直接返回,不会阻塞,但提交失败是不会重试的
-
混合提交模式
-
位移提交主题
我们的位移提交数据最后都是保存在一个叫做__consumer_offsets的主题中,该主题由kafka自己创建(第一个consumer启动时创建)。内部消息可以认为是一个Map<key<groupid, topic, partition>, value(offset值)>
8 kafka 如何保证消费者不会重复消费数据?
这个问题的话我打算从producer和consumer端两个方面来说。
-
Producer
要保证消费者不重复消费数据,那么首先得生产者提交重复的数据,kafka保证生产者不会保存重复数据有两种方法:
-
幂等性producer
所谓幂等性Producer我们broker端在保存producer的数据的时候多加一些数据,用来标识消息,当我们producer端来重复消息时,由于这些数据时相同的我们的broker端就会将该消息丢弃
-
事务型producer
我们的幂等性producer只能保证一次会话幂等,也就是如果producer进程挂掉重启则不能保证幂等性。这时就有了事务型producer,事务型producer是不惧重启的,事务型producer保证一批消息要么全部提交成功,要么全部提交失败,且保证精确一次处理。
在开启事务型producer时我们也许要将consumer设置为read committed级别
-
-
Consumer
我们consumer端的consumer offset保证了消息不会被重复消费,消费者偏移量以前的消息认为已经被消费,消费者偏移量及以后的消息认为没有被消费,当服务重启后,消费者会从消费者偏移量处继续进行消费从而保证不会重复消费被认为已经消费过的数据。
9 Kafka 在处理海量数据为什么这么快?
要谈这个问题的化,得从两个方面来说,分别是kafka写数据和读数,kafka处理海量数据快也是因为kafka读写数据快
写入数据
我们kafka中的数据是要写到磁盘中去的,数据写入磁盘的过程一般来说是用户进程通过系统调用将用户数据写到内核缓冲区中然后再将内核缓冲区中的数据写到磁盘中。
-
对于将内存中的数据写到磁盘的过程,采用顺序IO的方式可以提高写入磁盘的效率
提到顺序IO就不得不说一下随机IO,我们的将数据写入磁盘时需要磁头先寻址,再写入,随机IO的情况下我们的磁头需要进行大量的移动,而采用顺序IO磁头就不需要大量的移动,从而提高了磁盘的写入效率
-
mmap技术
所谓的mmap技术就是将内核缓冲区中的数据映射到用户空间,这样我们就没必要再进行将用户空间中的数据拷贝到内核缓冲区中这一步了。这样我们的数据拷贝就只有将内核缓冲区中的数据拷贝到磁盘这一步,这也是提高我们kafka写数据效率的原因
读取数据
首先看一下一般的读取数据并上传至网络的过程:
-
将磁盘中的数据拷贝到内核缓冲区中
-
将内核缓冲区中的数据拷贝到用户空间中
-
将数据从用户空间再拷贝到socket缓冲区中
-
将socket中的数据拷贝到网卡中
这个过程将数据进行了多次拷贝,可以说是非常繁琐耗时的。我们的kafka采用了零拷贝的技术来对上述过程进行了优化,所谓的零拷贝就是通过sendfile()调用后,我们的操作系统直接将磁盘中的数据DMA拷贝到内核缓冲区中,然后将缓冲区描述符和数据长度传给socket缓冲区,最后通过SG-DMA拷贝讲数据拷贝到我们的网卡中,最后再返回系统调用。可以看到采用零拷贝技术后我们的数据拷贝只有两次了,而且都是内核态完成,相较于传统的数据拷贝效率可以说是大大提升。
还有一点就是除了我们的本机IO效率外,网络IO效率也是我们kafka需要关注的问题,为了提升网路IO效率kafka采用了批量压缩技术,即将我们的消息进行批量压缩,压缩后的数据大小会大大减小,也就是说在相同的网路情况下,相较于不进行数据压缩,我们可以传输更多的数据了。
10 Kafka怎么保证消息可靠传输?
ISR机制
我们kafka中的每一个分区都有一个领导者副本和若干个追随者副本,我们的操作都是在领导者副本上进行的,追随者副本只做冗余用,然后我们还为每个分区维护了一个ISR列表,这个列表中包含了所有和领导者副本同步的追随者副本,当然也包括领导者副本自己,我们生产者发送消息时会要求领导者副本上消息已经同步并且至少有一个(和参数ack有关)ISR中追随者副本同步才认为消息传送成功,否则会进行重试
ack参数
ack是producer端的一个参数,常见的有以下三个值
-
0
producer发送消息后不管消息有没有被broker接受都认为是发送成功了
-
1
producer发送消息后必须得领导者副本上写入了消息才认为成功,否则写入失败进行重试
-
all
producer发送消息后必须领导者副本和ISR中的其他副本都写入成功后,才认为消息成功写入,否则写入失败进行重试
在这种机制下,当我们的领导者副本宕机后,也至少有一个和领导者副本数据完全同步的追随者副本来备用
11 为什么不让一个partition被同组的多个consumer消费
假设可以多个消费者消费同一个分区,由于我们一个消费者组对于某条消息只能消费一次,所以消费同一个分区的消费者之间则需要一个协调者来对他们进行管理,比如通过加锁使得该分区某个消息被消费时,其他消费者不能消费该消息,否则就会导致重复消费。这样就会使得这些消费者管理起来比较困难。