本文第一部分介绍经典文件系统ext3的块存储,第二部分介绍一个Nosql分布式存储系统的块存储。
ext系列文件系统是linux的土著文件系统,历经4个版本,最新是ext4,在linux2.6.28内核正式引入,目前比较新的linux发行版都已经把ext4做为默认文件系统。下面先看看ext3的数据块存储结构,而ext4是对ext3的继承与优化,核心结构基本类似,同时也对ext3的提供向下兼容,后面再讨论它们的区别。
首先看看ext3的文件系统的结构图:
ext3整体结构还是清晰明了:
1)引导块,主要是给系统引导的时候用的,包括512字节的MBR和64字节的分区表,余下的是保留区间。
2)块组部分,则是由ext3文件系统管理的区域,块组编号从0开始。每个块组都包含datablockbitmap、inodebitmap、inodetable以及datablocktable(少数块组会有superblock和gdt(gdt包含了文件系统所有块组的块描述符),但是文件系统一般只使用块组0的superblock和gdt,其他块组的superblock和gdt只是在一些时间点上进行备份,以防块组0崩溃了可以恢复到一个比较一致性的状态)。
块大小、每个块组包含的块数目、块组内inodetable和datablocktable都是在创建文件系统的时候就设定好了。所以在文件系统的使用过程中根据给定的inodenumber(inodenumber从0开始编号,一直单调递增1),可以快速定位到所属的块组和在该块组的inodetable里面的偏移量。同理给定blocknumber(blocknumber也是从0开始编号,一直单调递增1),可以快速定位到所属的块组和在该块组中datablocktable的偏移量。可以看到这种线性的组织方式是静态的,从文件系统建立那一刻就分配好了(不过由于ext3有一些保留块,这些保留块也为文件系统的resize提供了一定支持,主要是为了保存更多的gd)。
下面再看看ext3的数据块索引:
ext3的inode中有一个重要的数据成员i_block(其实上面这个图如果横着展开的话,其实是一颗树型结构),是一个包含15个32位整数的数组,其中前12个元素直接保存的存储文件数据的block的blocknumber;第13个元素保存的blocknumber指向的block里面保存的是blocknumber数组,而非文件数据,简称一次间接索引。同理,第14个元素保存的二次间接索引,第15个元素保存的是三次间接索引,此机制保证了ext3最大支持的单个文件大小为4TB多一点(假设blocksize是4K,那么最大的文件大小=4K*12+4K/4*4K+4K/4*4K/4*4K+4K/4*4K/4*4K/4*4K~=4TB,但是实际上由于ext3实现的限制,为了使得每个文件包含的磁盘扇区数不超过2^32个,所以在blocksize为4K的情况下,ext3的最大文件大小实际上只有2TB)。如果文件系统的blocksize是4K,那么只要不大于48K,都只使用前12个元素,否则就会用到后面的间接索引。试想如果要访问一个2G文件的最后4KB,那么将要经历二次索引和一次索引,如果包含索引的数据块不在pagecache中,那么将发生多次IO读,效率有点低。
在ext4中,ext4的inode依然保留了i_block这种结构,而且提供向前兼容,同时也提供了更具弹性的extend分配方式(最多有4个extend,但是extend也可以是多级树形索引组织,一个extend由逻辑块号、块数和物理块号标识),只是extend的存储是重用这个i_block的存储空间的,也就是ext4兼容两种分配方式。对比ext3基于三级间接索引的分配机制和ext4的extend分配机制,首先在元数据规模上就有很大区别。试想如果一个4G的大文件,且文件系统blocksize是4K,则文件需要100w个block存储,也就是需要保存100w个blocknumber,每个blocknumber是4个字节,那么就至少需要4M的元数据了,这个还不包括一次和二次间接索引需要的数据块。其实问题很简单,在这种分配方式下的索引数据的熵值永远>=4/4K,即千分之一。如果采用extend分配方式,元数据规模将大大降低,因为一个extend可以标识很多块连续的数据块。
以上两个图的结构基本上是ext3的ondisk结构(也就是物理磁盘上实实在在存储的信息),至于inmemory的结构基本上跟这个ondisk的结构一一映射,只是做了少许封装。个人认为ext3/4的精髓在于分组管理和bitmap应用(这里暂时不讨论ext3/4的journal部分,因为jbd严格上来说不属于具体的文件系统,而是一个通用的文件系统日志管理模块,jbd本身的复杂度一点不比ext3简单),一般文件的数据都优先存储在同一个块组里面,这样就带来了IO的聚集特征,对性能有好处。其次简单的bitmap既节省了元数据的规模计算也比较简单。由于简单的物理结构,所以必定带来不怎么高的性能,幸好inodecache和dentrycache和pagecache的充分利用使得ext3在一般情况下性能还可以。
使用ext3文件系统往往有这样一种纠结:该文件系统对小文件的物理存储本身是高效率的,从i_block的构造看出来,但是小文件带来了大量索引开销(inode存储和查找)却起到了相反的作用;物理存储本身对大文件却是低效率的,因为要经过多次索引,且数据块不连续的概率很大,但是使用大文件索引开销却比较低。从实际的应用角度上看,大量小文件的应用是真正的性能杀手,特别是32位系统以前经常会由于大量小文件而消耗了大量lowmemory,导致内核由于低内存不足而产生oom-killer现象,所以一般都对小文件进行封装成大文件,然后动态映射大文件访问,只要内存足够大,间接索引块的pagecache命中率还是很高的。
ext4是对ext3的继承和优化改进,主要打破了ext3在磁盘容量、文件大小、子目录数等等的硬限制。但是为了向前兼容,核心的ondisk数据结构基本没什么大改,主要是在结构尾部增加了一些细化计量和加速字段,ext4在功能算法和inmemory结构组织方面有比较大的改进。