Kafka—消息队列(理论部分)
一、Kafka概述
1.1、简介
kafka是一个分布式的基于发布/订阅模式的消息队列
主要应用场景:大数据实时处理领域
1.2、什么是消息队列?
消息队列 = 消息 + 队列
消息 : 说白了就是数据(请求数据、业务数据等等)
队列 : 就是队列(数据结构中线性表或链表实现的先入先出的队列)
消息队列就是存放数据的队列,一种容器
而已,消息队列的概念很简单,但结合实际的应用场景便很复杂
如图便是消息队列最简单的消息队列模型
- 向消息队列中存放数据的叫做
生产者
- 从消息队列中获取数据的叫做
消费者
1.3、消息队列模式
消息队列有两种
1.3.1、点对点模式(消费者主动拉取数据)
生产者将数据放在消息队列中,消费者消费数据后,消息队列会将数据删除
,队列支持可以存在多个消费者,但一个消息能由其中一个消费者消费,即消息和消费者之间是一对一的
1.3.2、发布/订阅模式
如图发布\订阅模式有一个主题的概念,生产者定义主题,将消息存放在相应的主题中,消费者订阅主题,从该主题中获取数据进行消费,如图该模式下,允许多个消费者订阅同一主题,主题中的每个消息可由多个消费者进行消费,即:消息与消费者之间是一对多的
1.3.3、消费者获取数据的两种方式
- pull:消费者主动拉取;数据消费者决定自己何时请求并接收数据,数据持有者只能被动地响应请求
- push:生产者主动推送;数据生产者决定何时向消费者推送数据。数据消费者不知道何时会收到数据更新
二、Kafka分布式基础架构
如图是Kafka的基础架构:
- Kafka是一个基于分布式的发布订阅模式的消息队列,所以在真实场景中,Kafka是分布式部署在集群上
- Topic:主题,就是一个消息队列,一个Kafka集群上运行者多个主题,也就是多个消息队列
- Partition:分区,为了增强可扩展性,提高吞吐量,引入了分区,一个主题可以被分成多个分区,每个分区运行在集群的一个节点上
- leader:每个节点可能同时存在多个主题的分区,为了增强集群的高可用,为每个主题的分区都提供了副本,一个分区可以有多个副本,但只有一个能被选为leader,被选为leader的分区对外提供服务,即生产者只将数据存放在leader中,消费者只从leader中获取数据
- follew:leader的副本,一般情况下只将leader中的数据进行同步,当leader挂掉了,有重新选择一个副本作为leader,对外服务,目的提高系统的可用性
- CG:消费者组,逻辑意义上的消费者,一个消费者组中有多个消费者,这些消费者订阅了同一个主题,也可能订阅不同的主题,每个消费者消费不同分区的数据,一个分区只能被一个消费者消费,消费者组之间互不影响
三、 Kafka架构深入
3.1、offset及工作流程
如图所示,在kafka中,消息使用topic(主题)来分类的,而Topic是抽象的,分区是物理上的,kafka为每个分区都创建了一个log文件,文件存储了分区中的数据,生产者生产的数据会不断被追加到log文件的末尾,每条数据都有自己的offset,消费者都会实时记录自己消费到那个offset,即消费到了哪个消息了,如果出错了,就会从上次的offset记录处继续消费
3.2、文件存储机制
由于数据是源源不断到来的,为了避免log文件被越存越大,提高数据查找的效率,kafka引入了分片
和索引
机制,将文件分割成许多小文件,为了快速定位数据的位置,又提供了数据的索引,即元数据,降低了每次检索数据的开销和IO
如图Segment就是一个分片,.log文件中存放着该分片中的数据,.index中存放着数据的索引
在物理逻辑上:
- 一个分区对应着一个文件夹 topic名+分区编号
- 每个分区的文件夹下存放着分区对应的分片文件,随着数据越来越多,分区也会越来越多
3.3 生产者使用分区的策略
解决生产者使用消息队列的问题:
怎样选择分区?
保证数据的可靠性?
故障处理?
3.3.1、分区原因
- 提高扩展性:分片分布式部署,有利于集群扩展
- 提高吞吐量:一个主题,分为多个分区,分区分布在服务器集群中,可以处理大量数据
- 提高并发:以分区为单位读写
3.3.2、生产者使用分区原则
- 指明分区的情况下,直接将指明的值作为消息要推送的分区
- 没有指明分区,但设置了
key
,将key的hash值,与分区总数取余等到的余数为消息要推送的分区 - 既没有 partition 值又没有 key 值的情况下,取一个随机数,与分区可用的总数取余等到的余数为消息要推送的分区
3.3.3 数据可靠性保证
如图,生产者向指定的主题发送消息,主题的分区接收到消息后需要给生产者发送确认消息,生产者收到确认消息后,将发送下一波消息,否则再次发送
为了保证可用性,分区收到消息,副本要进行同步,有可能在未同步完成时,leader节点挂掉了,如果又向生产者发送了确认信息,新的leader分区,又没有新的数据,数据便发生了丢失
因此存在了两个问题:==(1)==何时向生产者发送确认信息,才能保证数据的完整可靠?对此有两种策略:
- 全部副本同步完成了再发送
- 半数以上同步完成了再发送
1)副本数据同步策略
方案 | 优点 | 缺点 |
半数以上完成同步,就发送ack | 延迟低 | 选举新的leader时,容忍n台节点的故障,需要2n+1个副本 |
全部完成同步,才发送ack | 选举新的leader时,容忍n台节点的故障,需要n+1个副本 | 延迟高 |
Kafka选择了第二种方案,原因如下:
1.同样为了容忍n台节点的故障,第一种方案需要2n+1个副本,而第二种方案只需要n+1个副本,而Kafka的每个分区都有大量的数据,第一种方案会造成大量数据的冗余。
2.虽然第二种方案的网络延迟会比较高,但网络延迟对Kafka的影响较小。
==问题二:==采用第二种方案之后,设想以下情景:leader收到数据,所有follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。这个问题怎么解决呢?
Leader维护了一个动态的和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给follower发送ack。
如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。
Leader发生故障之后,就会从ISR中选举新的leader。
2) ack应答机制
并不是所有的数据都要进行保证,可靠对于有些数据必须完整可靠,而有些数据丢失一部分也无关紧要;
ack应答级别分为三种情况,对应ack参数的三种设置:
- 0 生产者不等待,即leader一收到数据就返回ack(未写入磁盘),在leader发送故障时,可能引起数据丢失
- 1 生产者等待,leader落盘成功后,返回ACK,在副本未同步成功之前,leader故障,数据引起丢失
- -1 生产者等待,所有leader和副本落盘成功后,发送ack,但在成功后,ack发送之前,leader故障,可能造成数据重复
3)故障处理细节
如图:kafka为了更好的处理故障,在每个分区包括副本的log文件里引入了两个参数
- LEO(Log end offset):每个副本中最后一个offset(leader中也有)
- HW(High WaterMark):所有副本中最小的LEO(之前的数据才对消费者可见)
(1)follower故障
follower故障会被踢出ISR,等其恢复后,会读取上次的HW的值,并将HW之后的数据截掉,重新从leader同步数据,等其同步追上leader后(LEO大于该分区的最新HW),重新加入ISR
(2)leader故障
leader故障后,会从ISR中选出新的leader,为了保证数据的一致性,所有的副本会将高于HW的数据都截掉,重新从leader中同步
这只保证副本间数据的一致性,不能保证数据不丢失,与不重复
3.3.4 Exactly Once语义
保证每条数据有且仅被发送一次配合ack=-1,保证数据的不丢失,不重复
使用时,只需将enable.idempotence属性设置为true,kafka自动将acks属性设为-1。
3.4、消费者消费策略
看看这部分的问题:
- 消费方式?
- 分区分配策略?
- offset的维护?
3.4.1、消费者的消费方式
消费者消费方式前面提到过两种方式:
- 消费者拉取pull
- 有broker推送 push
由于在推送时,由于数据的发送速度是由broker决定的,但一般消费者接收的速率比较低,很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞,所以消费者才用了拉取:即消费者主动去拉取数据
pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。
3.4.2 分区分配策略
消费者组有多个消费者,一个主题有多个分区,一个分区只能由一个消费者消费,所以涉及到了分区的分配策略
Kafka有两种分配策略:
- roundrobin --轮询
RoundRobin 轮询分区策略,是把所有的 partition 和所有的 consumer 都列出来,然后按照 hascode 进行排序,最后通过轮询算法来分配 partition 给到各个消费者。
两种情况:
- 组内消费者订阅的主题是相同的
那么 RoundRobin 策略的分区分配会是均匀的。 - 组内消费者订阅的主题是不同的
有可能会导致分区分配的不均匀。如果某个消费者没有订阅消费组内的某个 topic,那么在分配分区的时候,此消费者将不会分配到这个 topic 的任何分区。
- range
Range 范围分区策略是对每个 topic 而言的。首先对同一个 topic 里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。假如现在有 10 个分区,3 个消费者,排序后的分区将会是0,1,2,3,4,5,6,7,8,9;消费者排序完之后将会是C1-0,C2-0,C3-0。通过 partitions数/consumer数 来决定每个消费者应该消费几个分区。如果除不尽,那么前面几个消费者将会多消费 1 个分区。
弊端:
如上,只是针对 1 个 topic 而言,C1-0消费者多消费1个分区影响不是很大。如果有 N 多个 topic,那么针对每个 topic,消费者 C1-0 都将多消费 1 个分区,topic越多,C1-0 消费的分区会比其他消费者明显多消费 N 个分区
3.4.3、触发分区分配策略
- 消费者变化时
- 分区变化时
3.4.4 offset的维护
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为**__consumer_offsets**。
3.5 Kafka 高效读写数据
3.5.1、顺序写磁盘
顺序写之所以快,是因为其省去了大量磁头寻址的时间。
Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到到600M/s,而随机写只有100k/s。
3.5.2、零复制技术
一般复制:
该复制方式需要经过核心态与用户态的复制传递,比较耗时,开销也大
零复制:
优化了中间过程,
提高了效率
四、 Zookeeper在Kafka中的作用
Kafka是依赖于zookeeper集群
Kafka集群中有一个broker会被选举为Controller,负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。并且都是依赖于Zookeeper的
如图就是Kafka集群中Controller的选举过程(也就是leader)可能是为了与分区的leader区分
上图中zookeeper中维护了两种数据:
- 集群中所有机器broker的编号
- 每个主题中分区与副本信息
kafkaController监听zookeeper中broker信息,有节点故障、上下线时,通过使用zookeeper中节点注册信息,维护主题,分区,副本,ISR等信息,协调维持集群的稳定
p9juzu-1605326382881)]
如图就是Kafka集群中Controller的选举过程(也就是leader)可能是为了与分区的leader区分
上图中zookeeper中维护了两种数据:
- 集群中所有机器broker的编号
- 每个主题中分区与副本信息
kafkaController监听zookeeper中broker信息,有节点故障、上下线时,通过使用zookeeper中节点注册信息,维护主题,分区,副本,ISR等信息,协调维持集群的稳定