技术分享 | Kafka之Log存储方法

Kafka中的Message是以topic为基本单位组织的,不同的topic之间是相互独立的。每个topic又可以分成几个不同的partition(每个topic有几个partition是在创建topic时指定的),每个partition存储一部分Message。借用官方的一张图,可以直观地看到topic和partition的关系。

partition是以文件的形式存储在文件系统中,比如,创建了一个名为page_visits的topic,其有5个partition,那么在Kafka的数据目录中(由配置文件中的log.dirs指定的)中就有这样5个目录: page_visits-0, page_visits-1,page_visits-2,page_visits-3,page_visits-4,其命名规则为<topic_name>-<partition_id>,里面存储的分别就是这5个partition的数据。

partition目录中的文件的存储格式和相关的代码所在的位置:

Partition的数据文件

Partition中的每条Message由offset来表示它在这个partition中的偏移量,这个offset不是该Message在partition数据文件中的实际存储位置,而是逻辑上一个值,它唯一确定了partition中的一条Message。因此,可以认为offset是partition中Message的id。

partition中的每条Message包含了以下三个属性:

offset

MessageSize

data

其中offset为long型,MessageSize为int32,表示data有多大,data为message的具体内容。它的格式和Kafka通讯协议中介绍的MessageSet格式是一致。

Partition的数据文件则包含了若干条上述格式的Message,按offset由小到大排列在一起。它的实现类为FileMessageSet。

它的主要方法如下:

append: 把给定的ByteBufferMessageSet中的Message写入到这个数据文件中。

searchFor: 从指定的startingPosition开始搜索找到第一个Message其offset是大于或者等于指定的offset,并返回其在文件中的位置Position。它的实现方式是从startingPosition开始读取12个字节,分别是当前MessageSet的offset和size。如果当前offset小于指定的offset,那么将position向后移动LogOverHead+MessageSize(其中LogOverHead为offset+messagesize,为12个字节)。

read:准确名字应该是slice,它截取其中一部分返回一个新的FileMessageSet。它不保证截取的位置数据的完整性。

sizeInBytes: 表示这个FileMessageSet占有了多少字节的空间。

truncateTo: 把这个文件截断,这个方法不保证截断位置的Message的完整性。

readInto: 从指定的相对位置开始把文件的内容读取到对应的ByteBuffer中。

我们来思考一下,如果一个partition只有一个数据文件会怎么样?

新数据是添加在文件末尾(调用FileMessageSet的append方法),不论文件数据文件有多大,这个操作永远都是O(1)的。

查找某个offset的Message(调用FileMessageSet的searchFor方法)是顺序查找的。因此,如果数据文件很大的话,查找的效率就低。

那Kafka是如何解决查找效率的的问题呢?有两大法宝:1) 分段 2) 索引。

数据文件的分段

Kafka解决查询效率的手段之一是将数据文件分段,比如有100条Message,它们的offset是从0到99。假设将数据文件分成5段,第一段为0-19,第二段为20-39,以此类推,每段放在一个单独的数据文件里面,数据文件以该段中最小的offset命名。这样在查找指定offset的Message的时候,用二分查找就可以定位到该Message在哪个段中。

为数据文件建索引

数据文件分段使得可以在一个较小的数据文件中查找对应offset的Message了,但是这依然需要顺序扫描才能找到对应offset的Message。为了进一步提高查找的效率,Kafka为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为.index。 索引文件中包含若干个索引条目,每个条目表示数据文件中一条Message的索引。索引包含两个部分(均为4个字节的数字),分别为相对offset和position。

相对offset:因为数据文件分段以后,每个数据文件的起始offset不为0,相对offset表示这条Message相对于其所属数据文件中最小的offset的大小。举例,分段后的一个数据文件的offset是从20开始,那么offset为25的Message在index文件中的相对offset就是25-20 = 5。存储相对offset可以减小索引文件占用的空间。

position,表示该条Message在数据文件中的绝对位置。只要打开文件并移动文件指针到这个position就可以读取对应的Message了。

index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。

在Kafka中,索引文件的实现类为OffsetIndex,它的类图如下:

主要的方法有:

append方法,添加一对offset和position到index文件中,这里的offset将会被转成相对的offset。

lookup, 用二分查找的方式去查找小于或等于给定offset的最大的那个offset

原文发布于微信公众号 - 加米谷大数据(DtinoneBD)

原文发表时间:2018-01-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java思维导图

数据库常见的面试题大全

触发器是一中特殊的存储过程,主要是通过事件来触发而被执行的。它可以强化约束,来维护数据的完整性和一致性,可以跟踪数据库内的操作从而不允许未经许可的更新和变化。可...

1394
来自专栏coderhuo

去掉宏__FILE__的路径

为了不让宏_FILE__带有路径信息,可以在Makefile中重定义宏__FILE_:

3092
来自专栏kwcode

C# 读取指定文件夹下所有文件

#region 读取文件 //返回指定目录中的文件的名称(绝对路径) string[] files = S...

3027
来自专栏木头编程 - moTzxx

PHP DB 数据库连接类

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011415782/article/de...

1381
来自专栏xingoo, 一个梦想做发明家的程序员

程序猿的日常——Mybatis现学现卖

最近有一个小项目需求,需要用spring mvc + mybatis实现一个复杂的配置系统。其中遇到了很多不太常见的问题,在这里特意记录下: 主要涉及的内容有...

3567
来自专栏coder修行路

Go基础之--操作Mysql(三)

事务是数据库的一个非常重要的特性,尤其对于银行,支付系统,等等。 database/sql提供了事务处理的功能。通过Tx对象实现。db.Begin会创建tx对象...

3669
来自专栏landv

金蝶k3密码批量修改

1696
来自专栏我爱编程

Python数据库操作之pymysql模块和sqlalchemy模块

参考博客https://www.cnblogs.com/aylin/p/5770888.html

1884
来自专栏Python小屋

Python把docx文档中的题库导入SQLite数据库

#本文所用的docx文档题库包含很多段,每段一个题目,格式为: 问题。(答案) #与之对应的数据库datase.db中tiku表包含kechengming...

2807
来自专栏乐沙弥的世界

Oracle 硬解析与软解析

Oracle 硬解析与软解析是我们经常遇到的问题,什么情况会产生硬解析,什么情况产生软解析,又当如何避免硬解析?下面的描述将给出

1123

扫码关注云+社区

领取腾讯云代金券