不知道这些知识点,面试的时候别说你懂 Kafka

kafka是一个分布式消息队列。具有高性能、持久化、多副本备份、横向扩展能力。生产者往队列里写消息,消费者从队列里取消息进行业务逻辑。一般在架构设计中起到解耦、削峰、异步处理的作用。

kafka对外使用topic的概念,生产者往topic里写消息,消费者从读消息。为了做到水平扩展,一个topic实际是由多个partition组成的,遇到瓶颈时,可以通过增加partition的数量来进行横向扩容。单个parition内是保证消息有序。

每新写一条消息,kafka就是在对应的文件append写,所以性能非常高。

kafka的总体数据流是这样的:

不知道这些知识点,面试的时候别说你懂 Kafka_第1张图片

大概用法就是,Producers往Brokers里面的指定Topic中写消息,Consumers从Brokers里面拉去指定Topic的消息,然后进行业务处理。

关于broker、topics、partitions的一些元信息用zk来存,监控和路由啥的也都会用到zk。

生产

基本流程是这样的:

不知道这些知识点,面试的时候别说你懂 Kafka_第2张图片

创建一条记录,记录中一个要指定对应的topic和value,key和partition可选。先序列化,然后按照topic和partition,放进对应的发送队列中。kafka produce都是批量请求,会积攒一批,然后一起发送,不是调send()就进行立刻进行网络发包。

1、key有填

API

有high level api,替我们把很多事情都干了,offset,路由啥都替我们干了,用以来很简单。还有simple api,offset啥的都是要我们自己记录。

partition

当存在多副本的情况下,会尽量把多个副本,分配到不同的broker上。kafka会为partition选出一个leader,之后所有该partition的请求,实际操作的都是leader,然后再同步到其他的follower。当一个broker歇菜后,所有leader在该broker上的partition都会重新选举,选出一个leader。(这里不像分布式文件存储系统那样会自动进行复制保持副本数)

然后这里就涉及两个细节:怎么分配partition,怎么选leader。

关于partition的分配,还有leader的选举,总得有个执行者。在kafka中,这个执行者就叫controller。kafka使用zk在broker中选出一个controller,用于partition分配和leader选举。

partition的分配

1、将所有Broker(假设共n个Broker)和待分配的Partition排序

leader容灾

controller会在Zookeeper的/brokers/ids节点上注册Watch,一旦有broker宕机,它就能知道。当broker宕机后,controller就会给受到影响的partition选出新leader。controller从zk的/brokers/topics/[topic]/partitions/[partition]/state中,读取对应partition的ISR(in-sync replica已同步的副本)列表,选一个出来做leader。

如果ISR列表是空,那么会根据配置,随便选一个replica做leader,或者干脆这个partition就是歇菜。如果ISR列表的有机器,但是也歇菜了,那么还可以等ISR的机器活过来。

多副本同步

这里的策略,服务端这边的处理是follower从leader批量拉取数据来同步。但是具体的可靠性,是由生产者来决定的。

不知道这些知识点,面试的时候别说你懂 Kafka_第3张图片

在acks=-1的时候,如果ISR少于min.insync.replicas指定的数目,那么就会返回不可用。

这里ISR列表中的机器是会变化的,根据配置replica.lag.time.max.ms,多久没同步,就会从ISR列表中剔除。以前还有根据落后多少条消息就踢出ISR,在1.0版本后就去掉了,因为这个值很难取,在高峰的时候很容易出现节点不断的进出ISR列表。

从ISA中选出leader后,follower会从把自己日志中上一个高水位后面的记录去掉,然后去和leader拿新的数据。因为新的leader选出来后,follower上面的数据,可能比新leader多,所以要截取。这里高水位的意思,对于partition和leader,就是所有ISR中都有的最新一条记录。消费者最多只能读到高水位;

从leader的角度来说高水位的更新会延迟一轮,例如写入了一条新消息,ISR中的broker都fetch到了,但是ISR中的broker只有在下一轮的fetch中才能告诉leader。

也正是由于这个高水位延迟一轮,在一些情况下,kafka会出现丢数据和主备数据不一致的情况,0.11开始,使用leader epoch来代替高水位。(https://cwiki.apache.org/confluence/display/KAFKA/KIP-101+-+Alter+Replication+Protocol+to+use+Leader+Epoch+rather+than+High+Watermark+for+Truncation#KIP-101-AlterReplicationProtocoltouseLeaderEpochratherthanHighWatermarkforTruncation-Scenario1:HighWatermarkTruncationfollowedbyImmediateLeaderElection)

思考:是follwers都来fetch就返回成功,还是等follwers第二轮fetch?leader已经写入本地,但是ISR中有些机器失败,那么怎么处理呢?

消费

订阅topic是以一个消费组来订阅的,一个消费组里面可以有多个消费者。同一个消费组中的两个消费者,不会同时消费一个partition。换句话来说,就是一个partition,只能被消费组里的一个消费者消费,但是可以同时被多个消费组消费。因此,如果消费组内的消费者如果比partition多的话,那么就会有个别消费者一直空闲。

不知道这些知识点,面试的时候别说你懂 Kafka_第4张图片

API

订阅topic时,可以用正则表达式,如果有新topic匹配上,那能自动订阅上。

offset的保存

一个消费组消费partition,需要保存offset记录消费到哪,以前保存在zk中,由于zk的写性能不好,以前的解决方法都是consumer每隔一分钟上报一次。这里zk的性能严重影响了消费的速度,而且很容易出现重复消费。

确定consumer group位移信息写入__consumers_offsets的哪个partition,具体计算公式:

1__consumers_offsets partition =
2           Math.abs(groupId.hashCode() % groupMetadataTopicPartitionCount)   
3//groupMetadataTopicPartitionCount由offsets.topic.num.partitions指定,默认是50个分区。

思考:

分配partition--reblance

生产过程中broker要分配partition,消费过程这里,也要分配partition给消费者。类似broker中选了一个controller出来,消费也要从broker中选一个coordinator,用于分配partition。

1、怎么选coordinator。

选coordinator

1、看offset保存在那个partition

这里我们可以看到,consumer group的coordinator,和保存consumer group offset的partition leader是同一台机器。

交互流程

把coordinator选出来之后,就是要分配了

1、consumer启动、或者coordinator宕机了,consumer会任意请求一个broker,发送ConsumerMetadataRequest请求,broker会按照上面说的方法,选出这个consumer对应coordinator的地址。

reblance流程

1、consumer给coordinator发送JoinGroupRequest请求。

5、consumer向coordinator发送SyncGroupRequest,其中leader的SyncGroupRequest会包含分配的情况。

当partition或者消费者的数量发生变化时,都得进行reblance。

  • 增加partition

  • 增加消费者

  • 消费者主动关闭

  • 消费者宕机了

  • coordinator自己也宕机了

消息投递语义

kafka支持3种消息投递语义

  • At most once:最多一次,消息可能会丢失,但不会重复

  • At least once:最少一次,消息不会丢失,可能会重复

  • Exactly once:只且一次,消息不丢失不重复,只且消费一次(0.11中实现,仅限于下游也是kafka)

在业务中,常常都是使用At least once的模型,如果需要可重入的话,往往是业务自己实现。

At least once

先获取数据,再进行业务处理,业务处理成功后commit offset。

At most once

先获取数据,再commit offset,最后进行业务处理。

Exactly once

思路是这样的,首先要保证消息不丢,再去保证不重复。所以盯着At least once的原因来搞。首先想出来的:

1、生产者重做导致重复写入消息----生产保证幂等性

由于业务接口是否幂等,不是kafka能保证的,所以kafka这里提供的exactly once是有限制的,消费者的下游也必须是kafka。所以一下讨论的,没特殊说明,消费者的下游系统都是kafka(注:使用kafka conector,它对部分系统做了适配,实现了exactly once)。

生产者幂等性好做,没啥问题。

解决重复消费有两个方法:

1、下游系统保证幂等性,重复消费也不会导致多条记录。

本来exactly once实现第1点就ok了。

但是在一些使用场景下,我们的数据源可能是多个topic,处理后输出到多个topic,这时我们会希望输出时要么全部成功,要么全部失败。这就需要实现事务性。既然要做事务,那么干脆把重复消费的问题从根源上解决,把commit offset和输出到其他topic绑定成一个事务。

生产幂等性

思路是这样的,为每个producer分配一个pid,作为该producer的唯一标识。producer会为每一个维护一个单调递增的seq。类似的,broker也会为每个记录下最新的seq。当req_seq == broker_seq+1时,broker才会接受该消息。因为:

1、消息的seq比broker的seq大超过时,说明中间有数据还没写入,即乱序了。

不知道这些知识点,面试的时候别说你懂 Kafka_第5张图片

事务性/原子性广播

场景是这样的:

1、先从多个源topic中获取数据。

其中第2、3点作为一个事务,要么全成功,要么全失败。这里得益与offset实际上是用特殊的topic去保存,这两点都归一为写多个topic的事务性处理。

不知道这些知识点,面试的时候别说你懂 Kafka_第6张图片

基本思路是这样的:

做事务时,先标记开启事务,写入数据,全部成功就在transaction log中记录为prepare commit状态,否则写入prepare abort的状态。之后再去给每个相关的partition写入一条marker(commit或者abort)消息,标记这个事务的message可以被读取或已经废弃。成功后在transaction log记录下commit/abort状态,至此事务结束。

数据流:

不知道这些知识点,面试的时候别说你懂 Kafka_第7张图片

1、首先使用tid请求任意一个broker(代码中写的是负载最小的broker),找到对应的transaction coordinator。

  • client先请求transaction coordinator记录

    的事务状态,初始状态是BEGIN,如果是该事务中第一个到达的,同时会对事务进行计时;client输出数据到相关的partition中;client再请求transaction coordinator记录offset的事务状态;client发送offset commit到对应offset partition。
  • client发送commit请求,transaction coordinator记录prepare commit/abort,然后发送marker给相关的partition。全部成功后,记录commit/abort的状态,最后这个记录不需要等待其他replica的ack,因为prepare不丢就能保证最终的正确性了。

这里prepare的状态主要是用于事务恢复,例如给相关的partition发送控制消息,没发完就宕机了,备机起来后,producer发送请求获取pid时,会把未完成的事务接着完成。

当partition中写入commit的marker后,相关的消息就可被读取。所以kafka事务在prepare commit到commit这个时间段内,消息是逐渐可见的,而不是同一时刻可见。

详细细节可看:https://cwiki.apache.org/confluence/display/KAFKA/KIP-98+-+Exactly+Once+Delivery+and+Transactional+Messaging#KIP-98-ExactlyOnceDeliveryandTransactionalMessaging-TransactionalGuarantees

消费事务

前面都是从生产的角度看待事务。还需要从消费的角度去考虑一些问题。

文件组织

kafka的数据,实际上是以文件的形式存储在文件系统的。topic下有partition,partition下有segment,segment是实际的一个个文件,topic和partition都是抽象概念。

在目录/

1不能识别此Latex公式:
2{topicName}-{

partitionid}/下,存储着实际的log文件(即segment),还有对应的索引文件。

每个segment文件大小相等,文件名以这个segment中最小的offset命名,文件扩展名是.log;segment对应的索引的文件名字一样,扩展名是.index。有两个index文件,一个是offset index用于按offset去查message,一个是time index用于按照时间去查,其实这里可以优化合到一起,下面只说offset index。总体的组织是这样的:

不知道这些知识点,面试的时候别说你懂 Kafka_第8张图片

为了减少索引文件的大小,降低空间使用,方便直接加载进内存中,这里的索引使用稀疏矩阵,不会每一个message都记录下具体位置,而是每隔一定的字节数,再建立一条索引。索引包含两部分,分别是baseOffset,还有position。

baseOffset:意思是这条索引对应segment文件中的第几条message。这样做方便使用数值压缩算法来节省空间。例如kafka使用的是varint。

position:在segment中的绝对位置。

查找offset对应的记录时,会先用二分法,找出对应的offset在哪个segment中,然后使用索引,在定位出offset在segment中的大概位置,再遍历查找message。

常用配置项

broker配置

不知道这些知识点,面试的时候别说你懂 Kafka_第9张图片

topic配置

不知道这些知识点,面试的时候别说你懂 Kafka_第10张图片

关于日志清理,默认当前正在写的日志,是怎么也不会清理掉的。

 本文原文链接:https://www.jianshu.com/p/d3e963ff8b70

新福利:

从11月01日开始至12月06日截止,一共五周时间,每周五我会从公众号底部留言+转发+在看综合最多的读者中抽取一名读者,免费包邮送实体新书《Flink入门与实战》,留言互动起来吧~

上周获奖名单:佐佑640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

猜你喜欢

你可能感兴趣的:(不知道这些知识点,面试的时候别说你懂 Kafka)