各中间件的文件存储结构分析/mq/db/es

前言

  说明

   本文主要介绍几个高性能中间件的磁盘文件存储结构、存储逻辑等,从而学习它们的设计思路。

  linux磁盘文件

  1. 硬盘的最小存储单位是扇区(Sector,512B),块(block)由多个连续的扇区组成,块的最常见的大小是4kb(8个连续的扇区组成)。
  2. 磁盘读取的基本单位是扇区(物理层),而操作系统(文件管理系统)读写的基本单位是块(逻辑层)。
  3. block:文件数据存储在块中,一个文件可能会占用多个块,但是一个块只能存放一个文件。
  4. inode:linux/unix文件系统的基础,每个文件都有一个inode,存储文件的元信息。虽然我们将文件存储在了块(block)中,但是我们还需要一个空间来存储文件的元信息metadata:如某个文件被分成几块、每一块在的地址、文件拥有者,创建时间,权限,大小等。这种 存储文件元信息的区域就叫inode(索引节点:indexNode)。
  5. 可以使用 stat 命令可以查看文件的 inode 信息。每个 inode 都有一个号码,Linux/Unix 操作系统不使用文件名来区分文件,而是使用 inode 号码区分不同的文件。

Kafka

每个分区会创建一个分区目录,分区目录下面存放的是日志文件(.log),名称相同的索引文件(.index / .timeIndex)

这里仅分析文件结构,更多实现细节查看:https://blog.csdn.net/songcf_faith/article/details/124741797

1.日志文件

  • 命名:日志文件名为上一个log文件的最后一个offset的值+1,作为新的日志和索引文件名(索引和日志文件同名/仅后缀不同)。(比如00000000000000000271.log文件的上一个日志文件中最后的offset一定是270)
  • 大小:每个日志文件大小默认1G,可根据log.segment.bytes配置;
  • 内容:日志文件存储了消息的具体内容(offset、messageSize、data);offset类似消息自增id
  • 清理:日志文件和索引文件会不断被清理,依赖于topic的保留时长(log.retention.ms)和保留字节大小(log.retention.bytes)决定。

2.索引文件

  • 命名:同日志文件名,仅后缀不同。
  • offset索引:索引文件每条记录包含相对offset(4B)和日志position(4B)两部分,共8字节
  • time索引:索引文件每条记录包含时间戳(8B)和日志position(4B)两部分,共8字节
  • 内容:存放的是稀疏索引(像跳表)并非全部,可以节约空间将索引全部加载进内存,缺点就是部分消息不能一次性定位。

图片引用自:https://segmentfault.com/a/1190000021824942

 

RocketMQ

所有消息数据存到CommitLog中,提升文件刷盘效率,然后另外文件存消息队列位置索引(ConsumerQueueFiles)、时间/关键词索引(IndexFiles)。

 这里仅分析文件结构,更多实现细节查看:https://blog.csdn.net/songcf_faith/article/details/124600106

1.CommitLog

  • 命名:文件名称为当前文件开始字符在整个日志中的偏移量固定20字符(前面补0)。无法写入最后一条消息且文件没满1G,则写入空余字节数(4B)+结尾标记(4B)
  • 大小:单文件默认1G,方便内存映射,fileChannel.map映射不能大小>=有符号int(2^31-1即2G),为了方便理解所以默认大小定为1G(可配置修改)
  • 内容:消息完整内容,所有主题日志都追加到该文件中,比起按主题分文件写(如kafka)能够更高效的顺序写,刷盘效率高。
  • 多个线程调用write把日志写到文件的pageCache中;也可以配置堆外缓冲(5个1G的DirectByteBuffer),循环写入堆外缓冲,再由线程写入pageCache

2.ConsumerQueueFiles

  • 命名:文件名称为当前文件开始字符在索引日志中的偏移量固定20字符(前面补0)(0、600w、1200w)
  • 内容:每个队列一个目录,30w条消息一个文件,每条消息20字节(hashCode、在commitLog中的偏移、消息字节数)

  

3.IndexFiles

(slots类似hash表,kafka没有这种索引)

  • 命名:按照创建时间来命名,所以key搜索时都需要传时间
  • 内容:文件内容分三部分(header和slots是固定的,indexes是追加的/数量不定)
  • header——文件头部,共40B(开始/结束时间,最大/最小偏移,有效slot数量,索引数量)
  • slots——keyHash槽slot,每个4B,固定500w个(索引编号)
  • indexes——索引数据,每个20B,最多2000w个(keyHash,logOffset,timeDiff,indexNo)
  • 索引数据的最后4B——是indexNo,指向相同hash的下一个索引数据,slot中hash冲突时会替换为新记录的indexNo,然后新记录的最后4B存原slot中数据,形成冲突链表

图片引用自:https://www.cnblogs.com/xijiu/p/15565997.html

 

PostgreSQL

  1. pg的存储管理单元是page,一个文件由多个page组成(相当于数组的一个bucket),和Oracle中的数据块一样,指的是数据库的块,操作系统块的整数倍个,默认是8K也就是两个操作系统块(4k的文件系统块)。
  2. 由于32位寻址所以表或索引大小最多2^32 * 8k = 32T,单个文件超过1G(可配置)就会分割新文件。
  3. 如果page设低了,相同数据量的文件需要分裂成更多的page,IO次数和索引分裂次数都会增加,性能会降低较多;如果page设高了,page内部的数据检索效率会降低,性能一样会降低不少,一般来说8K和16K对于数据库系统来说是最优解。
  4. 为什么需要page:便于文件管理,将文件分为大小相等的数个逻辑单元,通过page编号作为索引直接将文件当作数组访问。索引文件中一个page是B树的一个树节点,数据文件中一个page是的一个数组槽。

这里仅分析文件结构,更多实现细节查看

 图片引用自pg官网

 

1.数据文件

数据文件每个page的内容

包含5部分:PageHeaderData、ItemList、FreeSpace、Tuple、SpecialSpace

  • PageHeaderData——页面头部信息24B,空闲空间/特殊空间位置偏移、校验码、标志位等
  • ItemList/LinePoint——4B,记录了3个数据:定位tuple的偏移量15b、属性位2b、tuple的长度15b
  • FreeSpace——空闲空间,Item从它前面开始向后分配,Tuple从它后面开始向前分配
  • Tuple——数据项,头部信息HeapTupleHeaderData(实现MVCC的关键)、表记录的数据字段
  • SpecialSpace——索引访问模式相关的数据,不同的索引访问方式存放不同的数据。比如b-tree索引用存储指向页面的左右兄妹的链接,以及其他一些和索引结构相关的数据。

2.索引文件

  1. pg采用的非聚集索引,所以索引和数据是分开存储的,索引page内容和数据page内容同样是5部分,一个page页相当于B树的一个节点。
  2. 树结构:metaPage -> rootPage -> branchPage(0-n个非叶子节点) -> leafPage(0-n个叶子节点)。树结构参考MySQL中的BTree。

索引文件每个page的内容

  • PageHeaderData
  • ItemList
  • FreeSpace
  • Tuple——索引字段数据、完整数据物理位置<块号,块内偏移>、其它
  • SpecialSpace——左右兄弟块号、层级等信息

MySQL

  1. mysql的存储管理单元是page(同pg),区别是mysql的默认page大小是16kb。
  2. 文件空间(space)包含多个区段(extent),每个区段包含连续的64个页面(page)
  3. 主键索引是聚集索引,叶节点存储了完整的数据记录
  4. 辅助索引(二级索引)是非聚集索引,叶节点存储了主键索引值

这里仅分析文件结构,更多实现细节查看:https://blog.csdn.net/songcf_faith/article/details/124599663

数据文件和索引文件

每个文件page的内容

  • File Header
  • Index Header
  • FSEG Header
  • System Records
  • User Records——(类似postgresql的Tuple)数据字段,具体的一条数据(索引数据或完整的行数据),大小是不定的,各条数据已排序,并通过尾指针串联起来形成单向链表。
  • Free Space——(类似postgresql的FreeSpace)空闲空间,暂未存放数据的区域。UserRecords从它的前面往后分配,PageDirectory从它的后面往前分配。
  • Page Directory——(类似postgresql的Item/LinePopint)每个元素大小固定2B的一个动态数组,顺序存放具体数据的指针(方便多分查找,因为具体数据的长度是不确定的)。
  • File Trailer

注意这里4-8条Record才会有一个Directory,1是为了节约空间,2是避免每次插入数据Directory都产生大量移动(因为数组是有序的)

BTree结构(类似pg,不过pg索引类似innodb的辅助索引 叶节点不存数据)

 图片引用自:https://blog.jcole.us/2013/01/07/the-physical-structure-of-innodb-index-pages/

Page中的结构(类似postgresql中的page)

 

ES / Lucene

lucene中的文件比较多,文档数据相关的如下:

  • XXX.fdx,XXX.fdt 保存了XXX此段包含的所有文档,每篇文档包含了多少字段,每个字段保存了那些信息。
  • XXX.tis,XXX.tii保存了词典(Term Dictionary),也即此段包含的所有的词按字典顺序的排序。
  • XXX.frq保存了倒排表,也即包含每个词的文档ID列表。
  • XXX.prx保存了倒排表中每个词在包含此词的文档中的位置。

这里仅分析文件结构,更多实现细节查看:https://blog.csdn.net/songcf_faith/article/details/124599368

1.词典索引文件和词典文件

文件内容格式TODO

图片引用自:https://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623599.html

2.词倒排表和词位置文件

3.文档数据索引和文档文件

总结

  • 文件的结构主要用到了:数组、hash表、跳表、升级的B树、排序数组、单/双向链表等数据结构,来提升查找、访问效率;
  • MQ存的是消息,消息索引,索引方式比较简单,索引通常是线性或二分查找,所以通过固定索引项20B存储;
  • DB的索引复杂,涉及BTree,将文件拆分为多个块来管理,一个块节点内有多个项,而且是块节点带指向兄弟节点指针;块的使用会导致部分空间浪费,但是提升了访问效率;
  • 核心目的降低磁盘IO次数,然后尽可能的少浪费空间。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值