1.kafka是什么
消息中间件(消息队列),本质可以理解为是一个队列,实现进程间消息的承载,传递,而且是为大数据场景开发设计的一款中间件。本身就支持分布式和集群,是一款分布式消息发布和订阅系统
2.进程内队列和进程间队列的区别
进程内的队列(queue):
(1)一定是有序的,先进先出
(2)而且不需要考虑跨语言(进程内用的一定是同一个语言)
(3)消息一般不持久化(存在内存)
(4)不需要网络传递(通过内存共享信息)
(5)消息可控制不会重发和重复消费
进程间消息队列(kafka):
(1)可以保证消息的局部有序,如果是单分区,则可以保证全局有序(从发送方的角度考虑)
(2)跨语言->传输层协议需要使用通用协议, 也就是序列化,反序列化需要使用通用协议,比如json,bson,protobuf等
(3)消息做了持久化->存磁盘(可设置超时时间,默认7天)
(4)消息需要网络传递,这也是为啥需要序列化和反序列化的原因
(5)可做到消息不会重发和重复消费
(6)会存在丢消息的问题
3.消息队列使用场景
1.对实时性要求不高的场景(解耦)
2.上下游处理能力不对等的场景(防止下游崩溃,限流(削峰))
4.消息队列获取数据的三种模型
(1)Push模型
服务端主动发送数据给客户端。在服务端收到消息之后立即推送给客户端
优点:实时性好,服务器已有数据就给消费者推送,而且消费者不需要空转轮询队列是否有数据
缺点:
1):在Broker端需要维护Consumer的状态,不利于Broker去支持大量的Consumer的场景
2):Consumer的消费速度是不一致的,由Broker进行推送难以处理不同的Consumer的状况
3):Broker难以处理Consumer无法消费消息的情况(Broker无法确定Consumer的故障是短暂的还是永久的)
(2)Poll模型
由Consumer主动从Broker获取消息
优点:
1)在Broker端不需要维护Consumer的状态
2)可以自己觉定消费速度
缺点:
1)实时性
2)如果消息队列里面大部分时间没有数据,消费者的cpu可能会做无意义的空转
(3)Long-Polling
长轮询模式,在Poll模型的一个变种,可以防止CPU空转。Consumer主动发起请求到Broker,正常情况下Broker响应消息给Consumer;在没有消息或者其他一些特殊场景下,可以将请求阻塞在服务端延迟返回
优点:
1)在Broker一直有可读消息的情况下,long-polling就等价于执行间隔为0的pull模式(每次收到Pull结果就发起下一次Pull请求)。
2)在Broker没有可读消息的情况下,请求阻塞在了Broker,在产生下一条消息或者请求“超时之前”响应请求给Consumer
5.kafka的结构
(1)数据结构
kafka是一个发布订阅的分布式消息系统,发布和订阅的东西,kafka用主题(topic)来表示
(2)集群结构
我们说了kafka本身是为了大数据,分布式开发的一款中间件,本身支持集群
(3)集群结构-partitions
按照(2)的结构,如果把主题的信息都存在服务器broker0里面,大数据场景下必定会因为触发服务器的硬件瓶颈(宽带,cpu,磁盘IO)等。联想mongo,es的数据分片,kafka采用了一个类似的策略,术语叫分区(partitions),创建主题的时候可以选择分区的数量(比如2),就是把topic1里面的消息尽量均分到不同的分区里面
(4)集群结构-复制因子
按照(3)的结构存在一个问题,当broker0的机子宕机后,topic1在p0分区的消息会丢失
为了解决这个问题,需要做数据备份。类似mongo的主从部署,kafka引入了个术语,复制因子
,其实就是数据做多少备份(不能超过broker数量),以达到容灾的目的。假设topic1这个主题,需要创建两个分区,复制因子为2(尽量均分算法)
(5)集群结构-复制因子之leader(follow)
当主题的每个分区都有了备份,防止消息被重复消费,意味着同一个分区的多个备份只能有一个作为主分区,其他的都是从分区,也就是有leader 和follow(leader的选举是通过zookeeper来完成得)
规则:分区所在的所有broker里面,borker id最小的分区成为leader分区,复制该分区的读写操作
6.kafka消息布局及生产者,消费者
(1)生产者及消息布局(为啥是局部有序,也就是分区有序)
kafka消息做了持久化->存磁盘(可设置超时时间,默认7天),而且可支持多方的发布订阅,对于同一个主题,可以存着多个订阅者,每个订阅者消费速度,及消费到的消息位置是不一样的,需求记录每一个订阅者消费到的位置。因此kafka对每一个topic的每一个分区的消息做了编号,从0开始,递增+1,术语叫offset(偏移量)
(2)消费者及订阅群
什么是订阅群:假设A服务,B服务都订阅了topic1的消息(假设消息是用户id),A服务获取消息给用户做信息推荐,B获取消息是给用户发放激励信息,A和B都需要获取消息,但是获取消息后做的事情是不一样的,那么A,B就是两个消费群
什么是消费者:假设目前A服务目前开启了一个线程(t1)去获取topic1的消息并做处理,这个t1就是具体消费信息干事的人,这个t1可以理解为消费者。当A服务嫌弃一个线程消费太慢的时候,开了5个线程去消费,这五个线程就是五个消费者
服务A开启了5个现在消费《==》订阅群A里面有5个消费者
kafka如何实现订阅者的概念:消费者组,也就是每个消费者可以添加一个消费者组标识,相同组标识的消费者视为同一个订阅者的不同消费者
(3)偏移量存放地方
每个消费者(订阅者)需要知道自己消费到哪里了(也就是offset),需要有一个地方记录偏移量,这个偏移量可以存着哪里?
1)kafka服务端
如果存在kafka服务端,消费者每次消费都需要询问一次kafka服务端,而且kafka消息发送给了消费者,也不代表消息者确实把消息消费完了(消费者所在的服务器宕机)
2)消费者
消息存放在消费者,消费者服务全崩溃了怎么办,消费者服务重启的时候怎么知道自己消费的偏移量
综上所述,kafka在服务器端,和消费者端都存储了偏移量,消费者消费的时候已自身的消费偏移量为准,在一定的周期(每隔1s)上报kafka自己的消费进度,kafka也会存储一份消费者的消费偏移量
(4)消费者如何消费
(1)可以指定分区消费,存在弊端,消费者挂了,这个分区的数据就没人消费了
(2)也可以利用kafka和订阅者自动协调消费,当分区数发生变化,或者订阅者的某个消费者挂了,自动reblance,也就是消费者级别的重负载均衡
方案(2)各种消费者消费场景
1)假设主题t1现在有三个分区,订阅者A只有一个消费者
2)假设主题t1现在有三个分区,订阅者A只有2个消费者
看起来是分配不均匀的,因为有一个customer1需要消费两个分区,一个消费者只能消费一个分区
能不能是下面这种方式:
就是两个消费者都消费一个半分区,但是因为kafka的设计导致这种分配方式有问题,我们都知道每个消费者都会保存自己消费的分区的偏移量,当一个组的两个消费者同时消费一个分区的时候,必然存在重复消费
3)假设主题t1现在有三个分区,订阅者A只有3个消费者
4)假设主题t1现在有三个分区,订阅者A有4个消费者
(5)分区分配策略(分区如何分配给指定的消费者消费)
1)Range(默认,订阅了多个主题的场景会导致前面的消费者过载)
RangeAssignor(范围分区)
假设n = 一个topic分区数/消费者数量
m= 一个topic分区数%消费者数量
那么前m个消费者每个分配n+l个分区,后面的(消费者数量-m)个消费者每个分配n个分区
假设我们有10个分区,3个消费者,排完序的分区将会是0, 1, 2, 3, 4, 5, 6, 7, 8, 9;消费者线程排完序将
会是C1-0, C2-0, C3-0。然后将partitions的个数除于消费者线程的总数来决定每个消费者线程消费几个
分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。在我们的例子里面,我们有10个分
区,3个消费者线程, 10 / 3 = 3,而且除不尽,那么消费者线程 C1-0 将会多消费一个分区
的结果看起来是这样的:
C1-0 将消费 0, 1, 2, 3 分区
C2-0 将消费 4, 5, 6 分区
C3-0 将消费 7, 8, 9 分区
2)RoundRobin
RoundRobinAssignor(轮询分区)
轮询分区策略是把所有topic的partition和所有consumer线程都列出来,然后按照hashcode进行排序。最后通
过轮询算法分配partition给消费线程。如果所有consumer实例的订阅是相同的,那么partition会均匀
分布。
在我们的例子里面,假如按照 hashCode 排序完的topic-partitions组依次为T1-5, T1-3, T1-0, T1-8, T1-2, T1-1, T1-4, T1-7, T1-6, T1-9,我们的消费者线程排序为C1-0, C1-1, C2-0, C2-1,最后分区分配的结果
为:
C1-0 将消费 T1-5, T1-2, T1-6 分区;
C1-1 将消费 T1-3, T1-1, T1-9 分区;
C2-0 将消费 T1-0, T1-4 分区;
C2-1 将消费 T1-8, T1-7 分区;
使用轮询分区策略必须满足两个条件
1. 每个主题的消费者实例具有相同数量的流
2. 每个消费者订阅的主题必须是相同的
3)Stricky
StrickyAssignor(粘性分区策略)
① 分区的分配要尽可能的均匀;
② 分区的分配尽可能的与上次分配的保持相同。
当两者发生冲突时,第一个目标优先于第二个目标。
优点:减少系统资源的损耗以及其它异常情况的发生(因为消费者是保存了偏移量的等信息的)
假设消费组有3个消费者:C0,C1,C2,它们分别订阅了4个Topic(t0,t1,t2,t3),并且每个主题有两个分
区(p0,p1),也就是说,整个消费组订阅了8个分区:tOpO 、 tOpl 、 tlpO 、 tlpl 、 t2p0 、
t2pl 、t3p0 、 t3pl
那么最终的分配场景结果为
CO: tOpO、tlpl 、 t3p0
Cl: tOpl、t2p0 、 t3pl
C2: tlpO、t2pl
这种分配方式有点类似于轮询策略,但实际上并不是,因为假设这个时候,C1这个消费者挂了,就势必会造成
重新分区(reblance),如果是轮询,那么结果应该是
CO: tOpO、tlpO、t2p0、t3p0
C2: tOpl、tlpl、t2pl、t3pl
然后,strickyAssignor它是一种粘滞策略,所以它会满足`分区的分配尽可能和上次分配保持相同`,所以分配结果应该是
消费者CO: tOpO、tlpl 、 t3p0、t2p0
消费者C2: tlpO、t2pl、tOpl、t3pl
也就是说,C0和C2保留了上一次是的分配结果,并且把原来C1的分区分配给了C0和C2。 这种策略的好处是
使得分区发生变化时,由于分区的“粘性,减少了不必要的分区移动
7.生产者生产消息确认机制
(1)为啥需要生产者的消息确认机制
防止丢消息,当kafka的分区leader节点收到消息后,消息会同步到所有的follow节点,如果kafka leader分区收到消息后就给生产者回复确认,如果消息来不及同步到follow节点,leader挂了,那么这个休息就丢失了
(2)如何选择消息确认机制
kafka提供了多种方式的消息确认机制。总结起来就是两种
1):降低吞吐量,当消息都同步到所有follow节点才确认,如果消息的价值比较高,不允许丢 失(如果一个follow网络不好,大大降低了吞吐量)
2):leader收到了马上回复,消息不重要的场景
3):折中,一半收到了马上回复
8.消费者的消息确认机制
之前说了,为了防止消费者重复消费,kafka会在服务器端保存消费者消费的偏移量,这个偏移量实际上是(消费者确认自己收到了的消息,并且回复kafka的偏移量)
(1)如何选择消息确认机制
定期发送确认信号(间隔时间可以设置,比如1s)
(2)缺点
存在重复消费的嫌疑,如果消费者消费了某个信息但是还没提交,消费者崩溃了,就会触发重复消费
9. kafka分区分配Reblance
实际上kafka的reblance是kafka帮助消费组leader,在消费组leader计算后完成的统一分配策略
每个kafka的broker都会启动一个服务
Group Coordinator(消费组协调服务)
(1) kafka如何存储订阅者者偏移量信息和元数据
之前说过kafka会把订阅者消费的主题的偏移量也会在服务器端保存一份,信息会保存在一个内部主题__consumer_offsets里面(默认50个分区,1个复制因子)
(2)消费者组的偏移量落点分区计算方式
Math.abs(groupId.hashCode() % groupMetadataTopicPartitionCount)
(3)broker如何均分不同的消费者组
由(2)可知,消费者组的偏移量和元数据信息会落点到__consumer_offsets的某个分区里面,比如34,34分区的leader所在的broker就是这个消费者组的协调服务的broker
(4)大约流程
zookerper watcher 触发发现需要做reblance-> kafka在消费者组里选择一个leader->leader根据配置的策略分配最新的分区分配方案-> 上传协调者->协调者下发给所有的消费者
10.kafka进阶之数据存储方式
(1)主题布局
主题+分区(主题是个逻辑概念,分区是物理上的概念)
三个broker,ftopic 2分区2复制因子在各个broker的分布
2 * 2 = 4,应该是有4份数据
broker0,id = 0
broker1: id =1
broker2: id =2
(2)分区如何做leader选举
分区0在 breoker0,id=0 和broker1, id=1, 那么id比较小的的broker的所拥有的分区成为leader分区
(2)数据布局
每个分区文件的数据布局:
类似mysql的MyISAM存储引擎,做了索引,而且将索引文件和数据文件分开了
索引文件内容:(稀疏索引)
和mysql的MyISAM索引结构的区别:
MyISAM:b+数
kafka:稀疏索引(log.index.interval.bytes 指定,默认值为 4096),类似跳跃表