【kafka】原理解析

主要内容:

  • kafka基本概念
  • 消息如何分发
  • 消费者如何消费对应的分区
  • 消息的存储
  • 高可用的副本机制

组件版本

kafka 2.3.0

详细内容

一、kafka基本概念

Topic

在kafka中,topic是一个存储消息的逻辑概念,可以认为是一个消息集合。每条消息发送到 kafka 集群的消息都有一个类别。物理上来说,不同的 topic 的消息是分开存储 的, 每个 topic 可以有多个生产者向它发送消息,也可以有多个消费者去消费其中的消息。
在这里插入图片描述

Partition

每个topic 可以划分多个分区(每个 Topic 至少有一个分区),同一topic下的不同分区包含的消息是不同的。每个消息在被添加到分区时,都会被分配一个offset(称之为偏移量),它是消息在此分区中的唯一编号,kafka通过offset 保证消息在分区内的顺序,offset的顺序不跨分区,即kafka 只保证在同一个分区内的消息是有序的。

下图中,对于名字为test的topic,做了3个分区,分别是 p0、p1、p2. 每一条消息发送到 broker 时,会根据 partition的规则选择存储到哪一个partition。如果partition规则设置合理,那么所有的消息会均匀的分布在不同的partition中, 这样就有点类似数据库的分库分表的概念,把数据做了分片处理。
在这里插入图片描述

Topic&Partition 的存储

Partition 是以文件的形式存储在文件系统中,比如创建一 个名为 firstTopic 的 topic,其中有 3 个 partition,那么在 kafka 的数据目录(/tmp/kafka-log)中就有 3 个目录, firstTopic-0~3,

命名规则是<topic_name>-<partition_id>

在这里插入图片描述

二、消息分发

kafka 消息分发策略

消息是kafka中最基本的数据单元,在kafka中,一条消息 由key、value两部分构成,在发送一条消息时,我们可以指定这个 key,那么 producer 会根据 key 和 partition 机 制来判断当前这条消息应该发送并存储到哪个partition中。 我们可以根据需要进行扩展producer的partition机制。

消息默认的分发机制

默认情况下,kafka采用的是hash取模的分区算法。如果 Key为null,则会随机分配一个分区。这个随机是在这个参 数 ”metadata.max.age.ms” 的时间范围内随机选择一个。对于这个时间段内,如果 key 为 null,则只会发送到唯一的 分区。这个值默认情况下是10分钟更新一次。 简单理解就是 Topic/Partition 和 broker 的映射关系,每一个 topic 的每 一个partition,需要知道对应的broker列表是什么,leader 是谁、follower是谁。这些信息都是存储在Metadata这个里面

三、消费者如何消费对应的分区

分区分配策略

在kafka中,存在两种分区分配策略,

• Range(默认)、

• RoundRobin(轮询)。

通过 partition.assignment.strategy这个参数来设置。

Range strategy(范围分区)

Range 策略是对每个主题而言的,首先对同一个主题里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。假设我们有10个分区,3个消费者,排完序的分区将会是0, 1, 2, 3, 4, 5, 6, 7, 8, 9;消费者线程排完序将会是 C1-0, C2-0, C3-0。然后将partitions的个数除于消费者线 程的总数来决定每个消费者线程消费几个分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。在我 们的例子里面,我们有10个分区,3个消费者线程, 10 / 3 = 3,而且除不尽,那么消费者线程 C1-0 将会多消费一 个分区,所以最后分区分配 的结果看起来是这样的:

C1-0 将消费 0, 1, 2, 3 分区

C2-0 将消费 4, 5, 6 分区

C3-0 将消费 7, 8, 9 分区

Range strategy的弊端 C1-0 消费者线程比其他消费者线程多消费了分区。

RoundRobin strategy(轮询分区)

轮询分区策略是把所有partition和所有consumer线程都列出来,然后按照 hashcode 进行排序。最后通过轮询算法分配partition给消费线程。如果所有consumer实例的订阅是相同的,那么partition会均匀分布。 在我们的例子里面,假如按照 hashCode 排序完的partitions组依次为T1-5, T1-3, T1-0, T1-8, T1-2, T1-1, T1-4, T1-7, T1-6, T1-9,我们的消费者线程排序为C1-0, C1-1, C2-0, C2-1,最后分区分配的结果为:

C1-0 将消费 T1-5, T1-2, T1-6 分区;

C1-1 将消费 T1-3, T1-1, T1-9 分区;

C2-0 将消费 T1-0, T1-4 分区;

C2-1 将消费 T1-8, T1-7 分区;

什么时候会触发这个策略呢?(rebalance)

  1. 同一个consumer group内新增了消费者

  2. 消费者离开当前所属的 consumer group,比如主动停机或者宕机

  3. topic新增了分区(也就是分区数量发生了变化)

谁来执行 Rebalance 以及管理 consumer group 呢?

Kafka提供了一个角色:coordinator来执行对于consumer group的管理,当consumer group的 第一个 consumer 启动的时候,它会去和 kafka server 确定谁是它们组的 coordinator。之后该 group 内的所有成员都会和该coordinator进行协调通信

如何确定 coordinator

consumer group如何确定自己的coordinator是谁呢, 消费者向 kafka 集群中的任意一个 broker 发送一个 GroupCoordinatorRequest 请求,服务端会返回一个负载最小的 broker 节点的 id,并将该 broker 设置为 coordinator

Rebalance 的过程

在rebalance之前,需要保证coordinator是已经确定好了 的,整个rebalance的过程分为两个步骤,JoinSync join: 表示加入到consumer group中,在这一步中,所有的成员都会向 coordinator 发送 joinGroup 的请求。一旦所有成员都发送了 joinGroup 请求,那么 coordinator 会选择一个consumer担任leader角色,并把组成员信息和订阅信息发送消费者。

Join阶段

在这里插入图片描述

•protocol_metadata: 序列化后的消费者的订阅信息

•leader_id: 消费组中的消费者,coordinator 会选择一个作为leader,对应的就是member_id members:consumer group中全部的消费者的订阅信息

•generation_id: 年代信息,类似于之前讲解zookeeper的 时候的 epoch 是一样的,对于每一轮 rebalance, generation_id都会递增。主要用来保护consumer group。 隔离无效的 offset 提交。也就是上一轮的 consumer 成员 无法提交offset到新的consumer group中。

Synchronizing Group State 阶段

完成分区分配之后,就进入了Synchronizing Group State 阶段,主要逻辑是向 GroupCoordinator 发送 SyncGroupRequest请求,并且处理SyncGroupResponse 响应,简单来说,就是leader将消费者对应的partition分配方案同步给consumer group 中的所有consumer

每个消费者都会向 coordinator 发送 syncgroup 请求,不过只有leader节点会发送分配方案,其他消费者只是打打酱油而已。当 leader 把方案发给 coordinator 以后, coordinator会把结果设置到SyncGroupResponse中。这样所有成员都知道自己应该消费哪个分区。

consumer group 的分区分配方案是在客户端执行的! Kafka 将这个权利下放给客户端主要是因为这样做可以有更好的灵活性

简单来说:第一阶段:所有成员向coordinate发送join group请求,coordinate选出leader并把成员信息发送给leader,leader进行分区分配。第二阶段,leader将分区分配的策略发送给coordinate,coordinate将方案发送给所有成员。

四、消息储存

什么是 offset

在 kafka 中,提供了一个consumer_offsets_* 的一个 topic ,把 offset 信 息 写 入 到 这 个 topic 中。 consumer_offsets——按保存了每个 consumer group 某一时刻提交的offset信息。 __consumer_offsets 默认有 50个分区。

计算公式

➢ Math.abs(“groupid”.hashCode())%groupMetadataTopicPartitionCount ; 由 于 默 认 情 况 下 groupMetadataTopicPartitionCount 有 50 个分区,计算得到的结果为:35, 意味着当前的consumer_group的位移信息保存在__consumer_offsets的第35个分区
在这里插入图片描述

消息的保存路径

消息发送端发送消息到broker上以后,消息是如何持久化 的呢?那么接下来去分析下消息的存储 首先我们需要了解的是,kafka是使用日志文件的方式来保存生产者和发送者的消息,每条消息都有一个 offset 值来 表示它在分区中的偏移量。Kafka中存储的一般都是海量的消息数据,为了避免日志文件过大,Log 并不是直接对应在一个磁盘上的日志文件,而是对应磁盘上的一个目录, 这个目录的命名规则是<topic_name>_<partition_id> 比如创建一个名为firstTopic的topic,其中有3个partition, 那么在kafka的数据目录(/tmp/kafka-log)中就有3个目 录,firstTopic-0~3

kafka 是通过分段的方式将 Log 分为多个 LogSegment, LogSegment是一个逻辑上的概念,一个LogSegment对 应磁盘上的一个日志文件和一个索引文件,其中日志文件 是用来记录消息的。索引文件是用来保存消息的索引。

LogSegment

log.segment.bytes=107370 (设置分段大小),默认是 1gb,我们把这个值调小以后,可以看到日志分段的效果。 segmentfile 由 2 大部分组成,分别为index file 和 data file,此 2个文件一一对应,成对出现,后缀“.index”和“.log” 分别表示为segment索引文件、数据文件.segment 文件命名规则:partion 全局的第一个 segment从 0 开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值进行递增。数值最大为64位 long大小,20位数字字符长度,没有数字用0填充 。

在这里插入图片描述

如图所示,index中存储了索引以及物理偏移量。log 存储了消息的内容。索引文件的元数据执行对应数据文件中 message的物理偏移地址。举个简单的案例来说,以[4053,80899]为例,左边一列是offset,右边一列是position(物理偏移量4053~4559的position都是一样的),比如说查找offset为4055的消息。那么会去找消息对应的segment,然后通过二分法去找index文件对应的offset区间,会找到4053,然后根据4053找到对应的position80899,然后去日志文件里面找到80899,依次向下查找,找到offset为4055的消息

多个分区在集群中的分配

如果我们对于一个topic,在集群中创建多个partition,那么partition是如何分布的呢?

1.将所有N Broker和待分配的i个Partition排序

2.将第i个Partition分配到第(i mod n)个Broker上

日志的清除策略以及压缩策略

日志清除策略

前面提到过,日志的分段存储,一方面能够减少单个文件内容的大小,另一方面,方便kafka进行日志清理。日志的清理策略有两个

  1. 根据消息的保留时间,当消息在 kafka 中保存的时间超过了指定的时间,就会触发清理过程

  2. 根据topic存储的数据大小,当topic所占的日志文件大小大于一定的阀值,则可以开始删除最旧的消息。kafka 会启动一个后台线程,定期检查是否存在可以删除的消 息 通过 log.retention.bytes 和 log.retention.hours 这两个参数来设置,当其中任意一个达到要求,都会执行删除。 默认的保留时间是:7天

日志压缩策略

Kafka 还提供了“日志压缩(Log Compaction)”功能,通过这个功能可以有效的减少日志文件的大小,缓解磁盘紧 张的情况,在很多实际场景中,消息的 key 和 value 的值 之间的对应关系是不断变化的,就像数据库中的数据会不断被修改一样,消费者只关心key对应的最新的value。因 此,我们可以开启 kafka 的日志压缩功能,服务端会在后 台启动启动Cleaner线程池,定期将相同的key进行合并, 只保留最新的value值。日志的压缩原理是

在这里插入图片描述

五、高可用副本机制

我们已经知道Kafka的每个topic都可以分为多个Partition, 并且多个partition会均匀分布在集群的各个节点下。虽然 这种方式能够有效的对数据进行分片,但是对于每个 partition来说,都是单点的,当其中一个partition不可用的时候,那么这部分消息就没办法消费。所以 kafka 为了提高partition的可靠性而提供了副本的概念(Replica) ,通过副本机制来实现冗余备份。 每个分区可以有多个副本,并且在副本集合中会存在一个 leader的副本,所有的读写请求都是由leader*副本来进行处理。剩余的其他副本都做为 follower* 副本,follower 副 本会从 leader 副本同步消息日志。这个有点类似 zookeeper中leader和follower的概念,但是具体的实现方式还是有比较大的差异。所以我们可以认为,副本集会存在一主多从的关系。

一般情况下,同一个分区的多个副本会被**均匀分配到**集群中的不同broker上,当leader副本所在的broker出现故障后,可以重新选举新的 leader 副本继续对外提供服务。 通过这样的副本机制来提高kafka集群的可用性。

副本分配算法

将所有N Broker和待分配的i个Partition排序. 将第i个Partition分配到第(i mod n)个Broker上. 将第i个Partition的第j个副本分配到第((i + j) mod n)个 Broker上.

Kafka 提供了数据复制算法保证,如果 leader发生故障或挂掉,一个新leader被选举并被接受客户端的消息成功写入。Kafka确保从同步副本列表中选举一个副本为leader; leader 负责维护和跟踪ISR(in-Syncreplicas , 副本同步队列)中所有 follower滞后的状态。当producer发送一条消息到broker后,leader写入消息并复制到所有follower。消息提交之后才被成功复制到所有的同步副本。

kafka 副本机制中的几个概念

Kafka 分区下有可能有很多个副本(replica)用于实现冗余, 从而进一步实现高可用。副本根据角色的不同可分为3类:

Leader 副本:响应读写请求

Follower 副本:被动地备份leader副本中的数据,不响应读写请求。

ISR 副本:包含了leader副本和所有与leader副本保持同步的 follower 副本

——如何判定是否与 leader 同步后面会提到每个 Kafka 副本对象都有两个重要的属性:LEO 和 HW。注意是所有的副本,而不只是leader副本。

副本协同机制

刚刚提到了,消息的读写操作都只会由leader节点来接收和处理。follower副本只负责同步数据以及当leader副本所在的 broker 挂了以后,会从ISR中的 follower 副本中选取新的 leader。

ISR 表示目前“可用且消息量与 leader 相差不多的副本集合, 这是整个副本集合的一个子集”。

  1. 副本所在节点必须维持着与zookeeper的连接

  2. 副本最后一条消息的 offset 与 leader 副本的最后一条消息 的 offset 之 间 的 差 值 不 能 超 过 指 定 的 阈值 (replica.lag.time.max.ms) replica.lag.time.max.ms:如果该 follower 在此时间间隔 内一直没有追上过leader的所有消息,则该follower就会被剔除isr列表

ISR 数 据 保 存 在 Zookeeper 的 /brokers/topics//partitions//state节点中

LEO&HW

LEO:是当前offset的下一个值。如果LEO=10的话,接受了[0,9]个offset。

HW: 小于等于LEO,且HW之前的消息可被消费。取ISR中最小的LEO作为HW。

每个 replica 都有 HW, leader和follower各自维护更新自己的HW的状态。一条消息只有被 ISR 里的所有 Follower 都从 Leader 复制过去,才会被认为已提交。

在这里插入图片描述

数据的同步过程

Producer 在发布消息到某个 Partition 时,先通过ZooKeeper 找到该 Partition 的 Leader 【 get /brokers/topics//partitions/2/state】,然后无论该 Topic的Replication Factor为多少(也即该Partition有多 少个Replica),Producer只将该消息发送到该Partition的 Leader。Leader会将该消息写入其本地Log。每个Follower 都从Leader pull数据。这种方式上,Follower存储的数据 顺序与Leader保持一致。Follower在收到该消息并写入其 Log后,向Leader发送ACK。一旦Leader收到了ISR中 的所有Replica的ACK,该消息就被认为已经commit了, Leader 将增加 HW(HighWatermark)并且向 Producer 发送 ACK

数据同步使用同步还是异步

同步:一个副本故障,脱慢整个系统

异步:异步延迟很大的话,一旦leader挂了,follwer存在数据的延迟,造成数据丢失。

Kafka采用ISR

leader管理ISR集合,如果出现延迟高,自动踢出节点。

总结

通过本次分享,了解了kafka消息收发的机制,这样以后消息收发出现故障,可以快速定位到问题。在数据量变大的情况下,可以对kafka进行更好的优化。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值