本文适用于初学者,学习kafka之前 应该都知道它是消息队列,但是和我们印象中数据结构的队列不同的是,它持久化到磁盘上。
1、我们首先从定义来看 Kafka 一个分布式的、分区化、可复制提交的日志服务,我们先来想想什么是分区,好比图书馆的书,会分成好几个区。那么kafka也是这样的,他会把消息分成几个区,0-100寸到A区,100-200 存到B区。这个专有名词就叫做partition。可复制说的就是主从节点之前的复制了。
2、那么此时你会不会有疑问,我们为什么需要中间件,为什么会用到kafka呢? 都会说:应用场景:解耦、异步、削峰
假设一个场景:A系统需要告诉B系统,我这边某些业务处理完了,到B系统处理相关业务了,但是A系统并不关心B系统是否处理完,只是通知B系统。这时候我们就可以引入消息队列了。A系统只需要把消息扔到消息队列里就好,不需要等待B系统处理完,就可以做自己的逻辑。B系统监听到消息就可以执行自己的业务了。
目录
初识kafka
框架图
首先先来思考几个问题:
1.想一想为什么采用的是发布订阅模式 发布订阅模式好处
kafka采用发布订阅的模式,我们先试想一下,假如不是发布订阅模式的话,是broker去像消费者推送消息的话,举个例子,双十一,你买了很多很多快递,然后快递小哥一直把快递往你身上塞,他也不管你拿不拿得住,最后你坚持不住了,撒手不要了。 但是如果是发布订阅模式,你可以一次去快递点取5件,拿回家之后,再去快递点拿剩下5件。
所以明白为什么是发布订阅模式了嘛?消费者的消费能力只有它自己知道,这样的话 消费者可以知道以一个什么样的速度去队列中拉取消息。可以控制自己的消费速度。
2.Producer怎么知道消息被broker接收到了?
因为网络是不可靠的,生产者怎么知道 我发了一个消息,一定被消息队列接收到了呢?所以 Producer是有个ACK的应答机制的,在xml文件中可以配置,不配的话有默认的
3.消费者为什么要有分组?消费者组中的leader有什么用?
再拿取快递作为例子,消费者分组就好比你们寝室,你和室友一起去帮你拿快递,是不是速度就提高了一倍。所以分组就是为了缓解消费端的压力的,为了横向扩展,数据量过大的话,如果多线程解决不了,就加机器嘛, 分组就是给他们一个唯一标识,组内可以进行负载均衡。Leader的作用就是进行消费者和分区之间的负载均衡
我们再来看一张broker内部是怎么组成的
再来思考几个问题:
1.Broker中存储这全量的消息,cunsumer怎么知道我上次消费到哪里了呢?消费者的偏移量Offset存储在哪里?
在broker中有一个topic专门存储_consumer_offset? 我的第一想法是应该存储在consumer中,自己携带着offset去消费就好了。但是这样会有一个问题,如果消费者进行重平衡了,根本不知道上次消费到哪里了。所以在broker中也存储一份。
2 .Broker中宕机了,如果该机器存在partition的leader,数据丢失怎么办?
这就是开篇kafka定义中的复制的意义所在了,即使是leader宕机了,还有follower同步leader所有的消息。会选举一个follower作为leader。
深入kafka
Kafka如何进行复制
HW (High Watermark)俗称高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉取到这个offset之前的消息。
LEO (Log End Offset),标识当前日志文件中下一条待写入的消息的offset。
1. 为什么消费者只能拉取到这个offset之前的消息?
大家思考一下这样的场景,如果消费者可以消费HW之后的消息,但是这个消息还没有写入follow,这时候leader宕机了,follower1成为了leader,而这个新leader没有4,5消息。Cunsumer去提交的时候,就没有这个offset呀,这样的消费端和broker的数据就会不一致。
2、如何确保新选举出的 leader 是优选呢?(网络原因可能不是所有follow都复制了leader上的数据)
Kafka 机制中,leader 将负责维护和跟踪一个 ISR(In-Sync Replicas)列表,即同步副本队列,这个列表里面的副本与 leader 保持同步,状态一致。 Kafka在Zookeeper中动态维护了一个ISR(in-sync replicas) set,这个set里的所有replica都跟上了leader,(定时去zookeeper中更新)
Kafka得存储细节
Broker下有多个topic, 一个topic下有多个partition,一个partition下有多个segment 。
1、如何从 partition 中通过 offset 查找 message 呢?
比如查找170418。先找index 在通过index找到log (从宏观上看到partiton的最小单位,但是实际存储message的地方是log,)
生产者producer
生产者 发送消息流程
注意这里的第六个步骤 这个ack的可以在配置问件中配置参数的(0,1,all)
消费者consumer
问题:什么时候会再均衡?群组协调器(broker)是怎么感知的消费者宕机?
消费者死亡或者有新的消费者加入。 通过心跳感知。将“REBALANCE_IN_PROGRESS”封装进心跳请求的响应中,发还给消费者实例。
重平衡分为两个步骤 1.加入组的JoinGroup请求,就是上面这个图。2 分配分区的SyncGroup请求。就下面这张图。
看图,首先要注意的是这个协调者是一个broker,其次仔细看,真正去给consumer分配分区,进行负载均衡的是成员2,也就是消费者组中的领导者。
重平衡的触发条件:1、组成员数量发生变化。2、订阅主题数量发生变化。3、订阅主题的分区数发生变化。
Kafka两种默认的分配策略.
Range 范围分区
假如现在有 10 个分区,3 个消费者,排序后的分区将会是0,1,2,3,4,5,6,7,8,9;消费者排序完之后将会是C1-0,C2-0,C3-0。通过 partitions数/consumer数 来决定每个消费者应该消费几个分区。如果除不尽,那么前面几个消费者将会多消费 1 个分区。
例如,10/3 = 3 余 1 ,除不尽,那么 消费者 C1-0 便会多消费 1 个分区,最终分区分配结果如下:
Range 范围分区的弊端:
如上,只是针对 1 个 topic 而言,C1-0消费者多消费1个分区影响不是很大。如果有 N 多个 topic,那么针对每个 topic,消费者 C1-0 都将多消费 1 个分区,topic越多,C1-0 消费的分区会比其他消费者明显多消费 N 个分区。这就是 Range 范围分区的一个很明显的弊端了
RoudRobin轮询分区
RoundRobin 轮询分区策略,是把所有的 partition 和所有的 consumer 都列出来,然后按照 hascode 进行排序,最后通过轮询算法来分配 partition 给到各个消费者。
轮询分区分为如下两种情况:①同一消费组内所有消费者订阅的消息都是相同的 ②同一消费者组内的消费者所订阅的消息不相同
①如果同一消费组内,所有的消费者订阅的消息都是相同的,那么 RoundRobin 策略的分区分配会是均匀的。
例如:同一消费者组中,有 3 个消费者C0、C1和C2,都订阅了 2 个主题 t0 和 t1,并且每个主题都有 3 个分区(p0、p1、p2),那么所订阅的所以分区可以标识为t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终分区分配结果如下:
如果想要使用RoundRobin 轮询分区策略,必须满足如下两个条件:
①每个消费者订阅的主题,必须是相同的。因为如果不同消费者订阅的消息不相同,就会出现分区不均匀
②如果同一消费者组内,所订阅的消息是不相同的,那么在执行分区分配的时候,就不是完全的轮询分配,有可能会导致分区分配的不均匀。如果某个消费者没有订阅消费组内的某个 topic,那么在分配分区的时候,此消费者将不会分配到这个 topic 的任何分区。
例如:同一消费者组中,有3个消费者C0、C1和C2。有t0、t1,t2三个主题。C0订阅了t0和t1,C1订阅了t0,t2,C2订阅了t1,,t2。t0有三个分区t0p0,t0p1,t0p2,。t1有两个分区 t1p0,t1p1。T2有两个分区t2p0,t2p1。最终分区分配结果如下:
提交和偏移量
消费者提交偏移量的主要是消费者往一个名为_consumer_offset的特殊主题发送消息,这个主题会保存每次所发送消息中的分区偏移量,这个主题的主要作用就是消费者触发重平衡后记录偏移使用的,消费者每次向这个主题发送消息,正常情况下不触发重平衡,这个主题是不起作用的,当触发重平衡后,消费者停止工作,每个消费者可能会分到对应的分区,这个主题就是让消费者能够继续处理消息所设置的。