Kafka 物理存储机制
一个商业化消息队列的性能好坏,其文件存储机制设计是衡量一个消息队列服务技术水平和最关键指标之一。下面将从 Kafka文件存储机制和物理结构角度,分析 Kafka是如何实现高效文件存储,及实际应用效果。Kafka 的基本存储单位是分区。在配置 Kafka 的时候,管理员指定了一个用于存储分区的目录清单 log.dirs 参数的值。
一、分区分配
创建主题时,Kafka 首先决定如何在 broker 之间分配分区。假设有 6个 broker,打算创建一个包含 10个分区的主题。并且复制系数是3,相当于 30个分区副本。在被分配到6个broker 上时,要达到如下的目标:
【1】在 broker 间平均分配分区副本。对于上述例子来说,就是要保证每个 broker 可以分到 5个副本。
【2】确保每个分区的每个副本分布在不同的 broker 上。
【3】如果为 broker 指定了机架信息,那么尽可能把每个分区的副本分配到不同机架的 broker 上。这样做是为了保证一个机架不可用不会导致整个分区不可用。
为了实现这个目标,我们先随机选择一个 broker(假设是2),然后通过轮询给每个 broker 分配分区来确定首领的位置。如果分区0的首领在 broker2上,那么分区1的首领就在 broker3上,以此类推。然后,从分区首领开始,以此分配跟随者副本。如分区0首领在 broker2 上,那么它的第一个副本会出现在 broker3 上,第二个出现在 broker4 上。如果配置了机架信息,那么就不是按照数字顺序来选择 broker 了,而是按照交替机架的方式来选择 broker。假设 broker0、broker1、broker2 放在同一个机架,broker3、broker4、broker5 放在其他不同的机架。此时就不是按照0到5的顺序来选择 broker,而是按照0,3,1,4,2,5的顺序进行选择的。
二、文件管理
保留数据时 Kafka 的一个基本特性,Kafka 不会一直保留数据,也不会等到所有消费者都读取了消息之后才删除消息。相反,Kafka 管理员为每个主题配置了数据保留期限,规定数据被删除之前可以保留多长时间,或者清理数据之前可以保留的数据量大小。 因为在一个大文件里查找和删除消息是很费时的,也很容易出错,所以我们把分区分成若干个片段。默认情况下,index 大小为10M,每个片段(log)包含 1GB 或一周数据,以较小的那个为准。当前正在写入数据的片段叫做活跃片段,活跃片段永远不会被删除。
三、文件格式
我们把 Kafka 的消息和偏移量保存在文件里。保存在磁盘上的数据格式与从生产者发送过来或者发送给消费者的消息格式是一样的。因为使用相同的消息格式进行磁盘存储和网络传输,Kafka 可以使用零复制技术给消费者发送消息,同时避免了对生产者已经压缩过的消息进行解压缩。除了键、值和偏移量外,消息里还包含了消息大小、校验和、消息格式版本号、压缩算法和时间戳。时间戳可以是生产者发送消息的时间,也可以是消息到达 broker的时间,这个是可配置的。如果生产者发送的是压缩过的消息,那么同一个批次的消息会被压缩再一次,被当做包装消息进行发送。下面是普通消息和包装消息图:
四、文件存储机制
【1】Broker:消息中间件处理结点,一个 Kafka节点就是一个 Broker,多个 Broker可以组成一个 Kafka集群。
【2】Topic:主题,如 page view日志、click日志等都可以以 Topic的形式存在,Kafka集群能够同时负责多个 Topic的分发。
【3】Partition:Topic物理上的分组,一个 Topic可以分为多个 Partition,每个 Partition是一个有序的队列。
【4】Segment:Partition 物理上由多个 Segment组成。
【5】offset:每个 Partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到 Partition中。Partition中的每个消息都有一个连续的序列号叫做 offset,用于 Partition唯一标识一条消息。
分析过程分为以下4个步骤:
【1】Topic 中 Partition存储分布:假设 Kafka集群只有一个 Broker,xxx/message-folder为数据文件存储根目录,在 Kafka Broker 中 server.properties 文件配置(参数 log.dirs=xxx/message-folder),例如创建2个 Topic名称分别为 report_push、launch_info,Partitions 数量都为 partitions=4(将一个 Topic分为4个部分存储)存储路径和目录规则为:
索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中 message的物理偏移地址。 其中以索引文件中元数据 3,497为例,依次在数据文件中表示第3个 message(在全局 partiton表示第 368772个 message)、以及该消息的物理偏移地址为497。从上述图3了解到 segment data file 由许多 message组成,下面详细说明 message物理结构如下:
【4】在 partition 中如何通过 offset查找 message:例如读取 offset=368776 的 Message,需要通过下面2个步骤查找。
【第一步】查找 segment file:上述图为例,其中 00000000000000000000.index 表示最开始的文件,起始偏移量(offset)为0。第二个文件 00000000000000368769.index 的消息量起始偏移量为 368770 = 368769 + 1。同样,第三个文件00000000000000737337.index 的起始偏移量为 737338=737337 + 1,其他后续文件依次类推,以起始偏移量命名并排序这些文件,只要根据 offset 二分查找文件列表,就可以快速定位到具体文件。当 offset=368776 时定位到00000000000000368769.index|log;
【第二步】通过 segment file 查找 message:通过第一步定位到 segment file,当 offset=368776 时,依次定位到00000000000000368769.index 的元数据物理位置和 00000000000000368769.log 的物理偏移地址,然后再通过00000000000000368769.log 顺序查找直到 offset=368776为止。从上述图可知这样做的优点,segment index file 采取稀疏索引存储方式,它减少索引文件大小,通过 mmap可以直接内存操作,稀疏索引为数据文件的每个对应 message设置一个元数据指针,它比稠密索引节省了更多的存储空间,但查找起来需要消耗更多的时间。通过上述过程详细分析,我们就可以清楚认识到 kafka文件存储机制的奥秘。
五、Kafka 文件存储机制的实际运行效果
【实验环境】:Kafka 集群 = 由2台虚拟机组成;CPU = 4核;物理内存 = 8GB;网卡 = 千兆网卡;JVM HEAP = 4GB;详细 Kafka服务端配置及其优化请参考:Kafka server.properties配置详解:
从上述图5可以看出,Kafka 运行时很少有大量读磁盘的操作,主要是定期批量写磁盘操作,因此操作磁盘很高效。这跟 Kafka文件存储中读写 message的设计是息息相关的。Kafka 中读写 message有如下特点:写 message,消息从 java堆转入 page cache即物理内存。由异步线程刷盘,消息从 page cache刷入磁盘。读 message 消息直接从 page cache转入 socket发送出去。当从 page cache 没有找到相应数据时,此时会产生磁盘IO,从磁盘 Load消息到 page cache,然后直接从 socket发出去。
六、Kafka 中的 Partition 和 Offset
【1】Log机制:说到分区,就要说 Kafka对消息的存储,首先,kafka是通过 log(日志)来记录消息发布的。每当产生一个消息,Kafka 会记录到本地的 log文件中,这个 log和我们平时的 log有一定的区别。这里可以参考一下 The Log,不多解释。这个 log文件默认的位置在 config/server.properties 中指定的,默认的位置是 log.dirs=/tmp/kafka-logs,Linux不用说,windows的话就在你对应磁盘的根目录下。
分区 Partition:Kafka是为分布式环境设计的,因此如果日志文件,其实也可以理解成消息数据库,放在同一个地方,那么必然会带来可用性的下降,一挂全挂,如果全量拷贝到所有的机器上,那么数据又存在过多的冗余,而且由于每台机器的磁盘大小是有限的,所以即使有再多的机器,可处理的消息还是被磁盘所限制,无法超越当前磁盘大小。因此有了 Partition的概念。Kafka 对消息进行一定的计算,通过 hash来进行分区。这样,就把一份 log文件分成了多份。如上面的分区读写日志图,分成多份以后,在单台 Broker上,比如快速上手中,如果新建 Topic的时候,我们选择 replication-factor 1 partitions 2,那么在 log目录里,我们会看到 test-0目录和 test-1目录就是两个分区了。你可能会想,这没啥区别呀。注意,当有了多个 broker之后,这个意义就存在了。这里上一张图:
七、索引
消费者可以从 Kafka 的任意可用偏移量位置开始读取消息,假设消费者要读取从偏移量 100开始的 1MB 消息,那么 Broker 必须立即定位到偏移量 100,为了帮组 broker 更快地定位到指定的偏移量,Kafka 为每个分区维护一个索引。索引吧偏移量映射到片段文件和偏移量在文件里的位置。索引也被分成片段,所以再删除消息时,也可以删除相应的索引。Kafka 不维护索引的校验和。如果索引出现损坏,Kafka 会通过重新读取消息并录制偏移量和位置来重新生成索引。如果有必要,管理员是可以删除索引的,这样做是绝对安全的,Kafka 会自动重新生成这些索引。
八、Kafka高效文件存储设计特点
【1】Kafka把 topic中一个 parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。
【2】通过索引信息可以快速定位 message和确定 response的最大大小。
【3】通过 index元数据全部映射到 memory,可以避免 segment file的 IO磁盘操作。
【4】通过索引文件稀疏存储,可以大幅降低 index文件元数据占用空间大小。