上一篇blog亲手搭建了一个kafka的分布式集群【Kafka从入门到放弃系列 二】Kafka集群搭建并且成功的发送和消费消息,初步验证了Kafka的功能,接下来会在第一篇【Kafka从入门到放弃系列 一】概述及基本架构的简单理论和第二篇的实践基础之上,开始深入探讨Kafka的架构和一些策略,本篇blog重点介绍全流程以及文件存储机制,接下来的几篇分别介绍下生产者策略、消费者策略和一些高级特性
Kafka工作流程
通过基础概念的学习可以知道kafka的消息分为Topic,而Topic从逻辑上又可以划分为Partion,关于Topic&Partion需要注意以下几点:
- 一个 Topic可以认为是一类消息,每个 topic 将被分成多个 Partion,每个 Partion在存储层面是 append log 文件。
- 任何发布到partition 的消息都会被追加到log文件的尾部,每条消息在文件中的位置称为 offset(偏移量),offset 为一个 long 型的数字,它唯一标记一条消息。
- Kafka 机制中,producer push 来的消息是追加(append)到 Partion中的,这是一种顺序写磁盘的机制,效率远高于随机写内存
- 消费者组中的每个消费者,都会实时记录自己消费到了哪个 offset,以便出错恢复时,从上次的位置继续消费
- Kafka只保证Partion内的消息有序,不能保证全局Topic的消息有序
整体的流程如下图所示:
那么为什么有分区的概念呢?主要还是为了我们分布式的一切中间件的共同特性:负载均衡&水平扩展,Topic 只是逻辑概念,面向的是 producer 和 consumer;而 Partition 则是物理概念。可以想象,如果 Topic 不进行分区,而将 Topic 内的消息存储于一个 broker,那么关于该 Topic 的所有读写请求都将由这一个 broker 处理,吞吐量很容易陷入瓶颈,这显然是不符合高吞吐量应用场景的。所以一定是需要分区来将流量分发到不同的服务器上去的。
Kafka文件存储机制
当然即使是对一个Partition 而言,如果消息量过大的话也会有堵塞的风险,所以我们需要定期清理消息,当然是从旧的开始清理,如果只有一个Partion,那么就得全盘清除,这将对消息文件的维护以及已消费的消息的清理带来严重的影响。所以我们需要在物理上进一步细分Partition 。因此,需以 segment 为单位将 partition 进一步细分,每个 partition(目录)相当于一个巨型文件被平均分配到多个大小相等的 segment(段)数据文件中(每个 segment 文件中消息数量不一定相等)这种特性也方便 old segment 的删除,即方便已被消费的消息的清理,提高磁盘的利用率。每个 partition 只需要支持顺序读写就行。
Partition&Segment
接下来我们发送一条消息,看看在物理存储上是什么样的:
三个消息:hello、dashuaige、hhh。打开存储目录可以看到,在103机器上,一组index和log,这就是一个segment的内容
segment 文件由两部分组成,分别为 “.index” 文件和 “.log” 文件,分别表示为 segment 索引文件和数据文件。这两个文件的命令规则为:partition 全局的第一个 segment 从 0 开始,后续每个 segment 文件名为上一个 segment 文件最后一条消息的 offset 值,数值大小为 64 位,20 位数字字符长度,没有数字用 0 填充,我这里只有一条数据,所以是从0开始的,打开log文件可以看到:
虽然是乱码的,但是隐约可以看到顺序发送的消息。整体的存储架构如下:
Segment存储结构
通过以上对Segment落盘文件的了解,我们基本搞清楚了Segment的结构,当然我这里是单segment,看不出来。这里从网上找了一个大量文件的示例:
//第一段segment,起始位置为0 00000000000000000000.index 00000000000000000000.log //第一段segment,起始位置为170410 00000000000000170410.index 00000000000000170410.log //第一段segment,起始位置为239430 00000000000000239430.index 00000000000000239430.log
以上面的 segment 文件为例,展示出 segment:00000000000000170410 的 “.index” 文件和 “.log” 文件的对应的关系,如下图:
如上图,“.index” 索引文件存储大量的元数据,“.log” 数据文件存储大量的消息,索引文件中的元数据指向对应数据文件中 message 的物理偏移地址。其中以 “.index” 索引文件中的元数据 [3, 348] 为例,在 “.log” 数据文件表示第 3 个消息,即在全局 partition 中表示 170410+3=170413 个消息,该消息的物理偏移地址为 348【注意此物理偏移地址不是offset,全局offset为170413 】
快速定位partion中消息
既然消息在Partion中被分为了一段段的segment,那么我们如何快速定位消息的位置,来精准的对消息进行操作呢?以上图为例,读取 offset=170418 的消息:
- 首先查找 segment 文件,其中 00000000000000000000.index 为最开始的文件,第二个文件为 00000000000000170410.index(起始偏移为 170410+1=170411),而第三个文件为 00000000000000239430.index(起始偏移为 239430+1=239431),所以这个 offset=170418 就落到了第二个文件之中。其它后续文件依次类推,以其偏移量命名并排列这些文件,然后根据二分查找法就可以快速定位到具体文件位置。
- 其次根据 00000000000000170410.index 文件中的170418 -170410=8,得出是该segment 中的第8个消息,再次依据二分查找法定位到该索引,得到**[消息偏移量,物理偏移量]**坐标 [8,1325] 定位到 00000000000000170410.log 文件中的 1325 的位置进行读取。
- 找到1325位置后,顺序读取消息即可,确定读完本条消息【本条消息读到哪里结束】由消息的物理结构解决,消息都具有固定的物理结构,包括:offset(8 Bytes)、消息体的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes)等等字段,可以确定一条消息的大小,即读取到哪里截止。
以上就是定位消息的详细方法,通过索引的方式,可以在kafka顺序写磁盘的基础上仍然能快速的找到对应的消息。
本篇blog讲解了Kafka的工作流程和存储机制,其实对于Kafka而言,更多的策略体现在生产者和消费者端,接下来的两篇blog我们分别介绍下生产者策略和消费者策略。
部分内容参考 https://gitbook.cn/books/5ae1e77197c22f130e67ec4e/index.html