kafka是一个分布式的基于发布/订阅模式的消息队列
主要应用场景:大数据实时处理领域
消息队列 = 消息 + 队列
消息 : 说白了就是数据(请求数据、业务数据等等)
队列 : 就是队列(数据结构中线性表或链表实现的先入先出的队列)
消息队列就是存放数据的队列,一种容器
而已,消息队列的概念很简单,但结合实际的应用场景便很复杂
如图便是消息队列最简单的消息队列模型
向消息队列中存放数据的叫做生产者
从消息队列中获取数据的叫做消费者
消息队列有两种
生产者将数据放在消息队列中,消费者消费数据后,消息队列会将数据删除
,队列支持可以存在多个消费者,但一个消息能由其中一个消费者消费,即消息和消费者之间是一对一的
如图发布\订阅模式有一个主题的概念,生产者定义主题,将消息存放在相应的主题中,消费者订阅主题,从该主题中获取数据进行消费,如图该模式下,允许多个消费者订阅同一主题,主题中的每个消息可由多个消费者进行消费,即:消息与消费者之间是一对多的
如图是Kafka的基础架构:
如图所示,在kafka中,消息使用topic(主题)来分类的,而Topic是抽象的,分区是物理上的,kafka为每个分区都创建了一个log文件,文件存储了分区中的数据,生产者生产的数据会不断被追加到log文件的末尾,每条数据都有自己的offset,消费者都会实时记录自己消费到那个offset,即消费到了哪个消息了,如果出错了,就会从上次的offset记录处继续消费
由于数据是源源不断到来的,为了避免log文件被越存越大,提高数据查找的效率,kafka引入了分片
和索引
机制,将文件分割成许多小文件,为了快速定位数据的位置,又提供了数据的索引,即元数据,降低了每次检索数据的开销和IO
如图Segment就是一个分片,.log文件中存放着该分片中的数据,.index中存放着数据的索引
在物理逻辑上:
解决生产者使用消息队列的问题:
怎样选择分区?
保证数据的可靠性?
故障处理?
key
,将key的hash值,与分区总数取余等到的余数为消息要推送的分区如图,生产者向指定的主题发送消息,主题的分区接收到消息后需要给生产者发送确认消息,生产者收到确认消息后,将发送下一波消息,否则再次发送
为了保证可用性,分区收到消息,副本要进行同步,有可能在未同步完成时,leader节点挂掉了,如果又向生产者发送了确认信息,新的leader分区,又没有新的数据,数据便发生了丢失
因此存在了两个问题:==(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。
并不是所有的数据都要进行保证,可靠对于有些数据必须完整可靠,而有些数据丢失一部分也无关紧要;
ack应答级别分为三种情况,对应ack参数的三种设置:
如图:kafka为了更好的处理故障,在每个分区包括副本的log文件里引入了两个参数
LEO(Log end offset):每个副本中最后一个offset(leader中也有)
HW(High WaterMark):所有副本中最小的LEO(之前的数据才对消费者可见)
follower故障会被踢出ISR,等其恢复后,会读取上次的HW的值,并将HW之后的数据截掉,重新从leader同步数据,等其同步追上leader后(LEO大于该分区的最新HW),重新加入ISR
leader故障后,会从ISR中选出新的leader,为了保证数据的一致性,所有的副本会将高于HW的数据都截掉,重新从leader中同步
这只保证副本间数据的一致性,不能保证数据不丢失,与不重复
保证每条数据有且仅被发送一次配合ack=-1,保证数据的不丢失,不重复
使用时,只需将enable.idempotence属性设置为true,kafka自动将acks属性设为-1。
看看这部分的问题:
消费者消费方式前面提到过两种方式:
由于在推送时,由于数据的发送速度是由broker决定的,但一般消费者接收的速率比较低,很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞,所以消费者才用了拉取:即消费者主动去拉取数据
pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。
消费者组有多个消费者,一个主题有多个分区,一个分区只能由一个消费者消费,所以涉及到了分区的分配策略
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 个分区
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为**__consumer_offsets**。
顺序写之所以快,是因为其省去了大量磁头寻址的时间。
Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到到600M/s,而随机写只有100k/s。
一般复制:
该复制方式需要经过核心态与用户态的复制传递,比较耗时,开销也大
零复制:
优化了中间过程,
提高了效率
Kafka是依赖于zookeeper集群
Kafka集群中有一个broker会被选举为Controller,负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。并且都是依赖于Zookeeper的
如图就是Kafka集群中Controller的选举过程(也就是leader)可能是为了与分区的leader区分
上图中zookeeper中维护了两种数据:
kafkaController监听zookeeper中broker信息,有节点故障、上下线时,通过使用zookeeper中节点注册信息,维护主题,分区,副本,ISR等信息,协调维持集群的稳定
p9juzu-1605326382881)]
如图就是Kafka集群中Controller的选举过程(也就是leader)可能是为了与分区的leader区分
上图中zookeeper中维护了两种数据:
kafkaController监听zookeeper中broker信息,有节点故障、上下线时,通过使用zookeeper中节点注册信息,维护主题,分区,副本,ISR等信息,协调维持集群的稳定