kafka是一个分布式的基于发布订阅模式的消息队列,主要用于大数据实时处理灵越
用于异步、削峰、解耦
一对一,消费者主动拉取数据。消息生产者将消息发送到queue中,然后消费者从queue中取出并消费消息,消息被消费者消费以后,queue中不在存储消息,所以消息消费者不可能重复消费已经被消费过的消息。queue支持存在多个消费者,即一个消费者组可以有多个消费者,但是消息只能被一个消费者消费。
一对多,消费者消费数据之后不会清楚消息数据
消息生成者发布者将消息发布到topic中,同时有多个消息消费者订阅消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。
kafka架构 由producer、consumer group、broker组成,producer是消息的生产者,consumer是消息的消费者,broker是代理服务器,消息都存放在broker上。producer、consumer要想向kafka发送和消费数据需要先申请一个主题,都是面向主题进行操作,申请创建topic的时候需要指定分区的个数,副本的个数,kafka集群会采用轮询或者range分区的方式将分区分散到不同的broker上。
为了方便扩展,提高吞吐量,一个topic分为多个partition
配合分区的设计,提出消费者组的概念,组内每个消费者并行消费
为提高可用性,每个partition增加若干副本,类似于namenode Ha结构
producer : 消息的生产者,向kafka broker发送消息
consumer :消息的消费者,从broker拉去消息进行消费
consumergroup :消费者组,一个消费者组里可以有多个消费者,组内每个消费者可以负责消费topic不同分区的数据,可以并行消费,一个消费者组逻辑上是一个订阅者。
broker :kafka代理,即kafka代理服务器,一个集群由多个broker组成,一个broker可以容纳多个topic。
topic : 主题,可以理解成队列,但是和点对点队列不同的是,不同的消费者组都可以从topic拉去相同的消息。
由此引出推模型和拉模型的区别:
推模型 push :指定消息推送给谁,如果要给多个对象推送的话,需要推送多份。
拉模型 pull :消息发布出去,放到某个地方,感兴趣的自己来拉。只需要推一份数据。
partition : 分区,为了实现扩展性,一个非常大的topic可以分布到多个broker上,一个topic分为多个partition提高消息的发送和消费的速度,可以将一个partition看成一个有序的队列。
replica : 副本,为了保证集群中某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍然能继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower。
leader : 每个分区多个副本的master,生产者、消费者消息发送和消费的对象都是和leader进行通信。
follower : 每个分区多个副本中的slave,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的leader。
kafka中的消息是以topic进行分类的,消息的生产者生产消息,消息的消费者消费消息都是面向topic的。
topic是逻辑上的概念,而partition是物理上的概念,一个partition对应一个log文件,producer每次生产的消息都会追加到该文件的末尾。
由于每次生产者生产消息都会追加到log文件中,为了防止日志文件过大影响消息的查找定位效率,kafka引入了分片和索引的机制,将一个partition又分为若干个segment,每个segment包含一个存储数据的log文件和查找的index索引文件,这些文件位于一个文件夹下,文件夹的命名规则为topic名称+分区序号。
log文件和index文件以该segment的第一条消息的offset命名。下图展示了如何根据offset来查找对应的消息。
更具offset找到index文件,在index文件中找到该offset消息在消息文件中的结束位置,根据上一条消息的结束位置和本条消息的结束位置就能得到该消息。
a)方便在集群中扩张,每个partition可以通过调整以适应它所在的机器,而一个topic又可以由多个partition组成,因此整个集群就可以适应任意大小的数据了。
b)可以提高并发,以partition为单位进行读写
producer将消息包装成一个producerRecord对象发送出去
a)指明分区的情况话直接发送给该分区
b)没指明partition,对象存在key的时候,将key的hashcode对partition数进行取模得到partition值
c)上面都没指定的时候,第一次调用时随机生成一个整数,后面每次在这个整数上自增,将这个值与topic的partition数取模,得到partition位置,即round-robin算法轮询算法。
为了保证producer发送的数据能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后都需要向producer发送确认消息,即acknowledgement确认收到消息,如果producer收到ack就会进行下一轮的发送,否则重新发送数据。
那么什么partition所在的broker什么时候发送ack给producer?有什么选择方案?
方式 | 问题 | 优点 | 缺点 |
---|---|---|---|
leader落盘后直接返回ack | 如果producer收到ack后leader挂了,follower还未同步成功,造成消息丢失。 | 不接受 | 不接受 |
follower全部同步成功后返回ack | 不存在上面消息丢失的问题。 | leader选举时容忍n台节点故障,需要n+1个副本,随便选一个follower即可。 | 高延迟,需要等待follower都同步成功。 |
半数以上follower同步成功后返回ack | 不存在上面消息丢失的问题。 | 低延迟 | 选举新leader时,容忍n台节点故障,需要2n+1个副本。 |
leader选举为啥会成为缺点?
因为需要半数以上的follower同意后才能选出leader,之所以需要半数以上是因为为了避免脑裂的问题SB问题。
kafka选取哪种方案?
kafka选择所有follower同步成功后再返回ack的方案,原因如下:
a–>同样为了容忍n台节点故障,第一种方案需要2n+1个副本,而第二种方案只需要n+1个副本,而kafka每个分区都有大量的数据,第一种方案会造成大量的数据冗余。
b–>第二种方案的网络延迟虽然比较高,但都是一个局域网中,网络延时的影响也比较小。
kafka怎么解决高延迟的问题?ISR
如果一台follower因为某种原因迟迟不能同步数据,那么leader就需要一直等它而不能发送ack,为了解决这个问题,kafka采用了一个ISR方案。
leader维护了一个动态的in-sync-replica (与leader保持同步的副本集合,该集合中的副本和leader数据是一致的),当isr中的follower和leader数据同步完成之后,leader就会向follower发送ack,如果isr中的follower长时间未向leader发送同步完成消息,leader会将其从isr中剔除,等待的时长由replica.time.max.ms参数设定。leader故障之后,就会从isr中选举出新的leader。
0:producer不等待broker的ack,这种方式提供了一个最低的延迟,broker一接收到还没写入磁盘就已经返回,当broker故障时可能存在数据丢失。
1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果follower同步成功之前leader故障,存在数据丢失。
-1: producer等待leader、follower全部落盘成功后返回ack,这种情况不存在数据丢失,但是,如果落盘成功后,leader故障还未来得及向producer发送ack,producer认为失败了会重新发送消息。就会造成数据重复。
要实现数据不重复,保证exactly-once,kafka需要进行重复判断(幂等),如果已经有该条offset数据就不重新写入了。
follower故障后会被leader踢出ISR,follower故障恢复时,需要恢复到和leader数据保持一致,follower会读取本地磁盘上次记录的HW并截取掉高于HW的offset消息,高于HW的消息重新冲leader同步获取。等follower的LEO大于等于该partition的HW,即follower追上leader后,重新加入到ISR中。
leader HW 和 LEO之间的数据对consumer是不可见的,因为follower数据还没有同步完成。
因此如果一个被剔除出ISR的follower需要恢复,重新加入到ISR中,它需要从它记录的上次HW开始同步leader数据,如果它上次记录的HW和LEO之间还有数据,这部分数据不是有效要丢掉,重新从leader同步,知道它的LEO追上现在leader的HW才能加入到ISR集合中
leader故障后,从ISR中重新选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会现将各自的log文件高于HW的部分截掉,然后从新的leader同步数据
注意:者只能保证副本之间数据的一致性,并不能保证数据不丢失或者数据不重复。
对于某些比较重要的消息,我们需要保证exactly one,即每条消息被发送且只被发送一次。
在kafka 0.11版本之后,kafka引入了幂等性机制,配合acks=-1的at least once语义,实现了从producer到broker的exactly once语义。
即 at least once + idempotent = exactly once
使用时,只需要将enable.idempotent属性设置为true即可。kafka会自动将acks属性设为-1.
kafka的producer发送消息采用的是异步的方式。在消息发送的过程中,涉及到两个线程:main线程和sender线程,以及一个线程共享变量-recordAccumulator。main线程将消息发送给recordAccumulator,sender线程不断从recordAccumulator中拉去消息发送到kafka broker中。
相关参数配置:
batch.size : 只要数据累计到batch.size 之后,sender才会将这批数据发送出去。
linger.ms :如果数据迟迟未达到batch.size, sender等待该事件后会发送数据。
同步发送指,一条消息发送后会阻塞当前线程,直至返回ack,send方法返回的是一个future对象,因此可以根据future对象的特点,实现同步发送的效果,通过future对象的get方法阻塞主线程。
consumer采用pull模式从broker中读取数据。
push模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。
pull 模式:消费者自己从broker中拉取消息。
push模式:由broker主动将消息推送给消费者。这种模式的目标是尽可能以最快的速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络堵塞,而pull模式则可以根据consumer的消费能力以适当的速率消费消息。
pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据,针对这一点,kafka的消费在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即timeout。
一个consumer group中有多个consumer,一个topic有多个partition,所以必然会涉及到partition的分配问题,决定哪个partition由哪个consumer消费的问题。
kafka有两种分配策略,round-robin 和range
由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置继续消费,所以consumer需要实时记录自己消费到哪个offset,以便故障恢复后继续消费。
kafka 0.9版本前,consumer默认将offset保存在zookeeper中,从0.9版本后,consumer默认将offset保存在一个kafka的内置topic中,该topic为__consumer_offsets.
因此consumer在消费消息后需要向kafka提交offset,kafka配置了默认提交,也可以通过手动的方式进行提交。
kafka的producer生产数据,需要写到log文件中,写的过程是一直追加到文件的末端,为顺序写,官网有数据表明,同样的磁盘,顺序写能达到600M/s,二随机写只要100K/s。这与磁盘的机械结构有关,顺序写之所以块是因为省去了大量磁盘寻址的时间。
kafka集群中有一个broker会被选举为controller,负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。
Controller的管理工作都是依赖于zookeeper的。
下图展示了topic的partition leader选举过程
1、kafka中的ISR、OSR、AR是什么?
ISR 是和leader保持同步的follower集合(in-asyn-replica),OSR是没和leader保持同步的follower集合(out-asyned-replica),AR 是所有副本(assigned replica)。
2、kafka中的HW、LEO是什么意思?
LEO log end offset 是日志文件最后一个message的offset
HW是高水位,是所有ISR中所有follower log文件中LEO最小的值 ,HW之前的所有数据对consumer 可见,因为所有follower都同步完成了,但是HW到LEO之间的数据对consumer不可见。
3、kafka是怎么体现消息顺序性的?
某个分区的消息消费是顺序的,但不同分区之间的消费不是顺序的。
4、kafka中的分区器、序列化器、拦截器是什么,顺序是什么
先执行拦截器,然后执行序列化器,然后执行分区器,然后发送给record accumulator,由send线程从record accumulator拉去消息发送给broker。
拦截器中断统计方法在最后执行。
5、kafka生产者客户端是什么样的,采用了几个线程处理?分别是什么线程
由拦截器、序列化器、分区器、record accumulate、sender组成,主要包括两个处理线程主线程和sender线程。
6、消费者组中消费者个数超过了分区的个数,就会有消费者消费不到数据是否正确?
如果是一个topic那么,如果消费者个数超过分区个数那么会存在有消费者消费不到数据的情况,如果是很多topic,那么所有的消费者消费的partition大体是均匀的,最多只会多一个。
7、消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1?
offset+1
8、有哪些情形会造成重复消费?
a、重复消费主要是消费者消费完后还没来得及提交offset,然后宕机了,下次右从上次记录的offset消费,导致重复消费。
b、或者leader将消息同步给follower后还未来得及发ack,producer重试了,leader没有设置幂等,就会产生重复数据
9、哪些情景会造成消息漏消费?
主要是生产者发送消息时,leader还未同步完成返回ack给生产者,然后宕机了,新的follower成为leader然而没有数据。可以通过acks=-1 实现至少消费一次。
10、当使用kafka-topic.sh创建或者删除了一个topic后,kafka背后会执行什么逻辑?
1)会在zookeeper中的/brokers/topics节点下创建一个新的topic节点,如:/brokers/topics/first_topic
2)出发controller的监听程序
3)kafka controller负责topic的创建工作,并更新metadata cache
11、topic的分区数可不可以增加,如果可以怎么增加?
可以增加扩展
bin/kafka-topics.sh --zookeeper localhost:2181/kafka --alter --topic topic-config --partitions 3
12、topic的分区数可不可以减少,为什么?
不可以减少,因为删除分区的数据难以处理,因为要删分区的话,该分区还未处理完的消费,正在处理的消息不好迁移。
13、kafka有内部topic吗?是什么,干什么用的?
有,__consumers_offset 主题,用于记录各个partition 消费者提交的offset消费记录。
14、kafka分区分配是怎么分的?
可以通过轮询或者range的方式将不同的分区分配到不同的broker上;同时对于一个consumer 组里面的consumer也可以采用上面两种方式分配不同的分区给他们消费。
15、简述下kafka的日志目录结构?
kafka日志的文件夹名称=topic_partion 组成,文件夹中包含log、和index两类文件,问价的文件名以该segment的第一个消息的offset命名。
16、如果指定一个offset,kafka controller怎么找到对应的消息?
先找index文件,然后在index文件中找到offset对应的消息的位置,然后根据消息的位置去log文件中找到相应的消息。
17、kafka哪些地方需要选举?这些地方的选举策略又有哪些?
a)选kafka controller,第一个broker成为controller
b)分区副本的leader选举,从isr中选一个作为leader。
18、失效副本是指什么?有哪些应对措施?
不能与leader保持同步,被踢出isr的follower副本,等它同步数据追上leader中hw时重新加入到isr中。
19、聊一聊kafka controller的作用?
管理broker集群,包括创建topic,从zk中获取isr并选举leader,管理broker的上线和下线。
20、kafka的哪些设计使它如此高性能?
分区
顺序写磁盘
零拷贝