kafka杂记

本文深入探讨了Apache Kafka的工作原理,重点在于Topic和Partition的概念,Partition的高可用副本机制,以及Kafka如何通过ISR确保数据一致性。内容还涵盖了Kafka的存储方式,包括Segment文件和索引结构,以及Kafka如何利用顺序读写和零拷贝技术实现高性能。此外,详述了ProducerRecord、KafkaProducer的send()方法以及Sender线程的角色,展示Kafka如何异步发送消息。
摘要由CSDN通过智能技术生成

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中,这样就有点类似数据库的分库分表的概念,把数据做了分片处理。

Partition 的高可用副本机制:

Kafka的每个topic都可以分为多个Partition,并且多个 partition 会均匀分布在集群的各个节点下。虽然这种方式能够有效的对数据进行分片,但是对于每个partition 来说,都是单点的,当其中一个 partition 不可用的时候,那么这部分消息就没办法消费。所以 kafka 为了提高 partition 的可靠性而提供了副本的概念(Replica),通过副本机制来实现冗余备份。每个分区可以有多个副本,并且在副本集合中会存在一个leader 的副本,所有的读写请求都是由 leader 副本来进行处理。剩余的其他副本都做为 follower 副本,follower 副本 会 从 leader 副 本 同 步 消 息 日 志 。 这 个 有 点 类 似zookeeper 中 leader 和 follower 的概念,但是具体的时间方式还是有比较大的差异。所以我们可以认为,副本集会存在一主多从的关系。一般情况下,同一个分区的多个副本会被均匀分配到集群中的不同 broker 上,当 leader 副本所在的 broker 出现故障后,可以重新选举新的 leader 副本继续对外提供服务。通过这样的副本机制来提高 kafka 集群的可用性(这里是借助zookeeper来实现旧partition_leader所在的broker发生单点故障时进行新一轮的leader选举(选举出1个新的Leader_broker,在这个leader_broker上的partition_replica即为对应topic的leader_replica))

Topic&Partition 的存储:

首先先明白一点:kafka 是使用日志文件的方式来保存生产者和发送者的消息(也就是这里讨论的topic和partition的存储方式都是使用的是日志文件的形式),每条消息都有一个 offset 值来表示它在分区中的偏移。Kafka 中存储的一般都是海量的消息数据,为了避免日志文件过大,一个Topic在实际存储时并不是对应磁盘上的一个(日志)文件,而是对应磁盘上的若干个个目录:其内的每个partition对应一个目录。目录的命名规则是<topic_name>_<partition_id>也就是为topic名称+有序序号,第一个partition目录的序号从0开始,序号最大值为partitions数量减1
 
1、存储路径
假设实验环境中Kafka集群只有一个broker,xxx/message-folder为数据文件存储根目录,在Kafka broker中server.properties文件配置(参数log.dirs=xxx/message-folder),例如创建2个topic名称分别为report_push、launch_info, partitions数量都为partitions=4 存储路径和目录规则为: xxx/message-folder

           |--report_push-0
              |--report_push-1
              |--report_push-2
              |--report_push-3
              |--launch_info-0
              |--launch_info-1
              |--launch_info-2
              |--launch_info-3

如果对于一个 有N个broker的Kafka Cluster ,若存在这么一个topic,其内有多个 partition,每个partition都有若干个replica(包含1个leader副本和若干个follower副本)那么这些partition 是如何在这个具有N个borker的kafka Cluster上分布的呢(也就是这些partition在这个cluster上的存储路径是什么)?

2、副本分配算法如下:
将所有N个 Broker和待分配的i个Partition排序(方便编号?).
将第i个Partition分配到第(i mod n)个Broker上.(在Kafka集群中,每个Broker都有均等分配Partition的Leader机会。)
将第i个Partition的第j个副本分配到第((i + j) mod n)个Broker上.

如何知道那个各个分区中对应的 leader 是谁呢?在 zookeeper 服务器上,通过如下命令去获取对应分区的信息,
比如get /brokers/topics/testCopyTopic/partitions/1/state这个是获取 testCopyTopic第 1 个分区的状态信息。 如下图,leader 表示当前分区的 leader 是那个 broker-id,这里即表示partition-1,即testCopyTopic-1这个分区的3个副本中,leader副本所处的位置是在broker-0这个节点上。
在这里插入图片描述

ISR(in-Sync replicas) 副本同步队列 :包含了 leader 副本和所有与 leader 副本保持同步的 follower 副本,这是某个partition全部副本构成的集合的一个子集”。具体来说,ISR 集合中的副本必须满足两个条件

  1. 副本所在节点必须维持着与 zookeeper 的连接(心跳机制)
  2. 副本最后一条消息的 offset 与 leader 副本的最后一条消息的 offset 之 间 的 差 值 不 能 超 过 指 定 的 阈 值(replica.lag.time.max.ms,以及replica.lag.max.messages) :如果该 follower 在此时间间隔内一直没有追上过 leader 的消息,则该 follower 就会被剔除 isr 列表
  • 默认情况下,Kafka topic的replica数量为1,即每个partition都有一个唯一的leader,为了确保消息的可靠性,通常应用中将其值(由broker的参数offsets.topic.replication.factor指定)大小设定为1。所有的副本(replicas)统称为AR (Assigned Replicas)。ISR是AR的一个子集,由leader维护ISR列表,follower从Leader同步数据有一些延迟,任意一个超过阈值都会把follower踢出ISR,存入OSR(Outof-Sync Replicas)列表,新加入的follower也会先存放到OSR中。AR = ISR + OSR
  • leader会负责维护ISR列表并将ISR的信息反馈到zookeeper的相关节点中:每个partition的replica_leader所在的broker会维护该partition对应的in-sync replicas (ISR)列表,replica_leader所在的broker有单独的线程定期检查该partition的 ISR 中是否有 follower 不满足 ISR 的约束,如果有,把其剔除 ISR 。并把新的 ISR 信息返回到Zookeeper的相关节点中(其follower_replica所在的broker所注册的zk节点上?)。
  • Controller维护维护zookeeper相关节点信息:Kafka集群中的其中一个Broker会被选举为Controller,主要负责Partition管理和副本状态管理,也会执行类似于重分配partition之类的管理任务。在符合某些特定条件下,Controller下的LeaderSelector会选举新的leader(比如某个partition的leader_replica所在的broker宕机了,会触发新一轮的leader选举);新一轮的leader选举完成后,将新的 ISR 和新的leader_epoch及controller_epoch写入Zookeeper的相关节点中,同时发起LeaderAndIsrRequest通知该partition的所有replicas。

ISR 的设计原理 :

在所有的分布式存储中,冗余备份是一种常见的设计方式,而常用的模式有同步复制和异步复制,按照 kafka 这个副本模型来说如果采用同步复制,那么需要要求所有能工作的 Follower 副本都复制完,这条消息才会被认为提交成功,一旦有一个follower 副本出现故障,就会导致 HW 无法完成递增,消息就无法提交,消费者就获取不到消息。这种情况下,故障的Follower 副本会拖慢整个系统的性能,甚至导致系统不可用如果采用异步复制,leader 副本收到生产者推送的消息后,就认为次消息提交成功。follower 副本则异步从 leader 副本同步。这种设计虽然避免了同步复制的问题,但是假设所有follower 副本的同步速度都比较慢他们保存的消息量远远落后于 leader 副本。而此时 leader 副本所在的 broker 突然宕机,则会重新选举新的 leader 副本,而新的 leader 副本中没有原来 leader 副本的消息。这就出现了消息的丢失。

kafka 权衡了同步和异步的两种策略,采用 ISR 集合,巧妙解决了两种方案的缺陷:当 follower 副本延迟过高,leader 副本则会把该 follower 副本剔除ISR 集合,消息依然可以快速提交。当 leader 副本所在的 broker 突然宕机,会优先将 ISR 集合中follower 副本选举为 leader,新 leader 副本包含了 HW 之前的全部消息,这样就避免了消息的丢失(因为consumer只能消费各partition HW之前的消息)。

【partition的follower追上leader含义】:
Kafka中每个partition的follower没有“赶上”leader的日志可能会从同步副本列表(ISR)中移除。下面用一个例子解释一下“追赶”到底是什么意思。请看一个例子:主题名称为foo 1 partition 3 replicas。假如partition的replication分布在Brokers 1、2和3上,并且Broker 3消息已经成功提交。同步副本列表中1为leader、2和3为follower。假设replica.lag.max.messages设置为4,表明只要follower落后leader不超过3,就不会从同步副本列表中移除。replica.lag.time.max设置为500 ms,表明只要follower向leader发送请求时间间隔不超过500 ms,就不会被标记为死亡,也不会从同步副本列中移除。
在这里插入图片描述
下面看看,生产者发送下一条消息写入leader,与此同时follower Broker 3 GC暂停,如下图所示:
在这里插入图片描述
直到follower Broker 3从同步副本列表中移除或追赶上leader log end offset,最新的消息才会认为提交(假设采用的是全同步模式也就是ACK参数 = -1)。注意,因为follower Broker 3小于replica.lag.max.messages= 4落后于leader Broker 1,Kafka不会从同步副本列表中移除。在这种情况下,这意味着follower Broker 3需要迎头追赶上知道offset = 6,如果是,那么它完全“赶上” leader Broker 1 log end offset。让我们假设代理3出来的GC暂停在100 ms和追赶上领袖的日志结束偏移量。在这种状态下,下面partition日志会看起来像这样
在这里插入图片描述

2、文件的实际存储方式

2.1、partiton中文件存储方式:
在这里插入图片描述

  1. 每个partion(目录)相当于一个巨型文件被平均分配到多个大小相等segment(段)数据文件中。但每个段segment file消息数量不一定相等,这种特性方便old segment file快速被删除。

  2. 每个partiton只需要支持顺序读写就行了,segment文件生命周期由服务端配置参数决定。

这样做的好处就是能快速删除无用文件,有效提高磁盘利用率。

2.2、partiton中segment文件存储结构

  • segment file组成:由2大部分组成,分别为index file和data file,此2个文件一一对应,成对出现,后缀”.index”和“.log”分别表示为segment索引文件、数据文件.
  • segment文件命名规则partion全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值(这样设计是为了方便后续进行二分查找)。数值最大为64位long大小,19位数字字符长度,没有数字用0填充

在这里插入图片描述

我们可以通过如下命令去查看这个log文件看看:

sh kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/testTopic-0/00000000000000000000.log --print-data-log(可不加) 
#这里 --print-data-log 是表示查看消息内容的,不加此项是查看不到详细的消息内容

在这里插入图片描述
2.3、log日志中每一条消息(Message)的物理存储结构如下:

offset: 4964(逻辑偏移量,在parition(分区)内的每条消息都有一个有序的id号,这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message) 
position: 75088(物理偏移量)
CreateTime: 1545203239308(创建时间) 
isvalid: true(是否有效) 
keysize: -1(键大小) 
valuesize: 9(值大小) 
magic: 2  表示本次发布Kafka服务程序协议版本号
compresscodec: NONE(压缩编码) 
producerId: -1 
producerEpoch: -1(epoch号) 
sequence: -1(序号) 
isTransactional: false(是否事务) 
headerKeys: []
payload: message_0(消息的具体内容)
通过以下命令查看索引内容

sh kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/testTopic-0/00000000000000000000.index --print-data-log(可不加)

在这里插入图片描述
既然是个索引文件,我们先上个图把 index 跟 log 文件的对应关系描述一下。
在这里插入图片描述
上述图中索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。 其中以索引文件中元数据3,497为例,依次在数据文件中表示第3个message(在全局partiton表示第368772个message)、以及该消息的物理偏移地址(position)为497(position是ByteBuffer 的指针位置)。

2.4 在partition中如何通过offset查找message

例如读取offset=368776的message,需要通过下面2个步骤查找

  1. 根据 offset 的值,查找 segment 段中 index 索引文件。由于索引文件命名是以上一个文件的最后一个offset 进行命名的,所以,使用二分查找算法能够根据offset 快速定位到指定的索引文件。

  2. 找到索引文件后,根据 offset 进行定位,找到索引文件中的符合范围的索引(这里还是需要利用二分查找进行定位,先在.index文件中基于offest二分查找对应的索引项,找到该offest对应的message在磁盘上所位于的存储块的起始position,然后在基于该position进行step3。kafka在设计segment index file采取稀疏索引存储方式(参考上图,并没有对每一条message都建立一个索引项),使用稀疏索引进一步减少索引文件大小,比稠密索引节省了更多的存储空间,但查找起来需要消耗更多的时间(在查找时必须先二分查找找到数据所在的物理块,再在该物理块里进行顺序查找)

  3. 得到 position 以后,再到对应的 log 文件中,从 position出发顺序查找每条message的offset,将每条消息的 offset 与目标 offset 进行比较,直到找到消息

比如说,我们要查找 offset=3040这条消息,那么先找到00000000000000000000.index, 然后找到[3039,58720]这个索引,再到 log 文件中,根据 58720这个 position 起开始顺序查找(message所在存储块的起始位置),比较每条消息的 offset 是否等于 3040。最后查找到对应的消息以后返回.

Kakfa的副本同步机制:也就是 Kafka中各Follower如何与Leader同步数据

1、数据的大致处理过程是:

Producer 在 发 布 消 息 到 某 个 Partition 时 , 先 通 过ZooKeeper 找到该 Partition 的 Leader 【 get /brokers/topics//partitions/2/state】,然后无论该Topic 的 Replication Factor 为多少(也即该 Partition 有多少个 Replica),Producer 只将该消息发送到该 Partition 的Leader(只有replica_leader才支持消息的读写)。Leader 会将该消息写入其本地 Log。每个 Follower都从 Leader pull 数据(follower所在broker中会新建一个ReplicaFetcherThread 线程 ,主动从leader批量拉取(pull)消息,这极大提高了吞吐量。Follower 在收到该消息并写入其Log 后,向 Leader 发送 ACK。一旦 Leader 收到了 其 ISR 列表中的所有 Replica 的 ACK,该消息就被认为已经 commit 了,Leader 将增加自己的 HW(HighWatermark)并且向 Producer 发送ACK(假设producer的ACK参数设置为-1,也就是数据是要求强一致性的情况下)。

2、Kafka中partition replica复制机制:

  • 每个 partition_Follower都主动从 partition_Leader pull 数据(follower_replica所在broker中会新建一个ReplicaFetcherThread 线程 ,主动从leader批量拉取(pull)消息(如果此时leader正好有新数据则直接拉取数据并返回,如果此时leader没有新数据则该线程被阻塞直到有新消息(数据)到来,或者等待超时才会被唤醒),这极大提高了吞吐量
  • <
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值