01海量小文件存储的挑战
为了解决海量小文件的存储问题,必须采用分布式存储,目前分布式存储主要采用两种架构:集中式元数据管理架构和去中心化架构。
(1)集中式元数据架构:
典型的集中式元数据架构的分布式存储有GFS,HDFS,MooseFs等。其采用的典型架构如下图1所示:
此架构主要包含3个部分:
1)客户端:主要用于提供访问分布式存储系统的接口;
2) 元数据服务器:主要用于存放分布式存储系统的命名空间和文件的一些元数据信息。
3)存储服务器: 主要负责存储文件的具体数据。
使用集中式的元数据管理的方式,其主要优点如下:
1) 元数据的操作性能高: 存储系统的命令空间和文件的元数据都存放在元数据服务器上,元数据操作如list directory和create file等元数据的操作性能会比较高;
2) 扩容时不需要数据迁移:元数据服务器上存放有所有文件的位置信息,在集群需要扩容增加新的节点时,这些位置信息不需要变动,因此集群在扩容时不需要进行数据迁移。
其主要缺点如下:
1) 元数据节点是瓶颈:客户端在访问文件数据之前通常都需要到元数据节点上查询文件的位置信息,因此元数据节点不可避免地成为了整个系统的性能瓶颈。
2) 文件的数量受限:为了提高性能,元数据节点中的数据一般都会保存到内存中,而元数据节点的内存不是无限增长的。
基于以上缺点,集中式的元数据管理方式非常不适合于海量小文件的存储。
(2) 去中心化架构:
为了解决集中式元数据架构的问题,去中心化架构的分布式存储产生,典型的去中心化分布式存储有GlusterFs,Ceph等。其采用的典型的架构如下图2所示:
此架构主要包含3个部分:
1) 客户端: 主要用于提供访问分布式存储系统的接口。
2) 存储服务器:主要负责存储文件的具体数据和元数据。
去中心化架构没有单独的元数据节点去保存文件的命名空间和元数据,元数据依然存储在存储节点上,文件的寻址一般采用DHT(一致性HASH)的方式计算。此架构一般会将多个存储节点进行逻辑分组,组内复制保证数据可靠性,因此会有可选的中心端服务器保存整个集群的存储节点以及分组信息。例如Ceph使用Ceph monitor保存整个集群的成员和状态信息(不保存文件信息),而GlusterFs选择将这些信息存放在所有的存储节点上。
使用去中心化的的方式,其主要优点如下:
1)无单点的性能瓶颈: 没有单独的元数据节点,客户端可以直接通过Hash的方式寻址文件,直接到存储节点上访问。
2) 文件的数量几乎不受限制: 没有单独的元数据节点,理论上文件的数量不受中心端节点容量的限制。
3) 读写性能更高: 读写请求不用到元数据节点上寻址而采用Hash计算的方式,理论上性能更高。
从以上优点可以得出去中心化的架构还是比较适合海量小文件的存储。由于GlusterFs的存储节点采用Linux本地文件系统存储数据,没有对小文件的读写进行优化,而Ceph从Jewel版本开始引入BlueStore存储引擎,对海量小文件的读写有较大的优化,因此我们选择使用Ceph存储海量小文件。
Ceph相对其他的分布式存储更适合海量小文件的存储,但还是有几个问题待解决:
1) Index数据量太大:海量小文件会导致bucket index中的数据量增长过快,从而导致bucket index需要经常resharding,而在bucket index resharding过程中会阻塞读写请求,对于大多数不需要list bucket的场景,此问题可以通过使用indexless bucket解决。对于必须bucket index的场景,增加了顺序分布的bucket index优化解决方案,此方案不在本文的讨论范围内,会在后面的文章中进行介绍。
2) 扩容时需要数据迁移:Ceph在数据扩容的过程中,需要对文件的位置进行重新哈希,并会带来大量的数据迁移操作,数据迁移过程中会带来巨大的性能损耗。同时海量小文件的数据迁移会耗费较长的时间,如果在此过程中磁盘故障或机器故障,数据恢复的时间将不确定,会带来极大的数据安全风险。
3) 存储效率问题:Ceph默认的最小分配单元为64KB,而当前小文件的平均size为15KB。为了提高存储效率,可以将最小分配单元调整为4KB(读写性能会随之降低)。当使用纠删码的方式存储存储小文件,如(4+2)的纠删码,此时文件的最小分配单元变为6 * 4KB = 24KB。为了提高存储效率而采用纠删码的方式存储小文件,反而更加浪费存储资源。
为了解决Ceph存储海量小文件的问题,需要设计一个满足以下目标的系统:
1) 高性能:分布式存储系统需要承载终端用户的读写需求,低延时地满足用户对数据的存取需求,是任何分布式存储系统的重要目标。
2)扩容时避免大量的数据迁移: 避免大量的数据迁移操作增加对存储系统的负载,从而影响存储系统对终端用户的服务质量,也是该系统设计的重要目标。
3) 加快故障恢复速度: 机器故障或磁盘故障会导致数据迁移,海量小文件导致故障恢复缓慢,故障恢复的时间越长,在此期间其他机器或磁盘发生故障的概率越大,数据丢失的风险越大,因此加快故障的恢复速度也是系统设计的目标之一。
4) 提升存储效率: 海量的小文件会占用大量的存储资源,同时大量的文件经过一段时间后就会变成冷数据,如何提升存储效率降低存储成本也是系统设计的目标之一。
02系统设计
目前业界解决海量小文件存储主要有以下的解决几种优化方式:
1) 硬件优化: 海量小文件的读写请求,瓶颈一般在机械硬盘上。硬件优化主要是采用支持随机读写的SSD硬盘代替机械硬盘,可以显著提高海量小文件的读写性能。但是考虑到成本因素,在数据量很大的情况下SSD硬盘一般只会在系统做作为Cache存在。
2) 文件元数据管理优化:分布式存储系统中文件的元数据包含文件的位置信息,文件的size,创建时间等。在读写小文件之前,都需要先得到文件的元数据信息,例如需要得到文件的位置信息才能到对应的存储节点上读写文件数据,只有拿到文件的size才能知道需要读取数据的长度。为了减小访问元数据的开销,应该尽量减少元数据的数量,元数据的数量越少,cache命中率越高,性能越高。
3) 小文件合并成大文件: 通过将大量的小文件合并成一个大文件,可以显著减少文件的数量,也就减少了元数据的数量,元数据的查询会更快。对于大文件机械硬盘可以做到顺序读写,可以显著降低硬盘的负载。
本系统结合以上的方式设计了一种基于双层存储池的小文件合并优化方案解决Ceph海量小文件存储的问题,其系统设计如下图3所示:
系统分为两级存储池,分别是高性能SSD副本存储池和大容量(副本/EC)存储池。可以配置不同的存储策略,将不同bucket或不同size的文件写到不同的存储池上。对于海量小文件优先写到高性能存储池中,这样可以保证小文件读写的高性能。
然后通过配置策略,开启合并功能将高性能存储池中的小文件合并成大文件存储到大容量存储池中。当大容量存储池中的空闲空间不足时,增加新的存储节点创建新的大容量存储池,而不是对已有的存储池进行扩容,这样可以避免大量的数据迁移操作对终端用户的读写请求产生影响。
对于大容量存储池,可以通过EC方式存储,提升已合并的文件的存储效率。由于存储的是大文件,最小分配单元可以调整的比较大,如:128KB,这样可以减小BlueStore中的元数据的数量,提升性能。
03
对于以上的系统设计,在具体实现上主要包含以下几个关键问题。
3.1 小文件的合并方式
将大文件看做一个Volume,每个小文件占其中的一个部分空间,如下图4所示:
将小文件顺序的追加到Volume中,每个小文件在Volume中分成3个部分:
1) Header: 保存文件的一些元数据信息,如文件名,文件size,文件的校验信息,在Volume中的offset。
2)Data: 小文件的实际数据内容。
3)Footer: 保存固定的magic,做校验用。
3.2 数据从高性能存储池迁移到大容量存储池
将数据从高性能存储池迁移到大容量存储的具体方式,如下图5所示:
存储网关的操作日志会记录当前存储网关上的操作,操作日志按照bucket和时间存放,迁移工具可以按照不同的bucket策略(如延迟迁移的时间间隔)去读取当前bucket的操作日志文件。具体的迁移流程可分成4个步骤:
1) 读取操作日志: 根据bucket的迁移策略,读取操作日志文件并解析每个操作,得到文件的名称和一些操作信息。为了支持并行的迁移,可以将文件名通过hash方式放到多个队列中。
2) 读取文件内容: 从队列中的得到待处理的文件名,从高性能存储池中读取文件的数据。
3) 小文件数据写到大容量存储池: 从大容量存储池中申请一个Volume,将当前的小文件追加到Volume中,按照Header,Data,Footer的顺序迁移文件。
4) 删除高性能存储池中的文件数据: 删除高性能存储池中的文件数据,只是将数据清空,保留文件的元数据信息,并在元数据中添加当前小文件在Volume中的位置信息,方便后面读取数据时的检索。
3.3 文件读取
在文件的数据被迁移到大容量存储池后,存储网关读取数据的流程也会随之变化,如下图6所示:
此时文件的数据内容存储在大容量存储池中,文件的元数据和文件内容的位置信息存放在高性能存储池中,因此在读取数据内容之前,存储网关要先到高性能存储池中读取文件的位置信息。从读取流程上看,相对于直接在大容量存储池中存储小文件增加了一次网络往返的开销,但是该方案有以下几个优点:
1) 大量的热数据已经在高性能存储池中完成了读取操作,迁移到大容量存储中的一般是冷数据,读取的请求会少很多。
2) 文件元数据的读取还是在高性能存储池中进行的,性能相对直接在大容量存储池中好很多。
3) 该方案大容量存储池BlueStore的分配单元较大,BlueStore的元数3.4文件的删除和空间回收
文件的删除需要删除文件的数据内容和文件的元数据两个部分,文件的元数据在高性能存储池中存放可以直接删除,而文件的数据内容只是大容量存储池中Volume的一部分,不能直接删除回收存储空间,因此将文件的删除和空间回收分开进行。
在服务终端用户的删除请求时,在Volume中找到对应的文件Header,并写入一个删除标记,表示文件已经删除不能访问,并修改当前Volume的有效size信息(实际存在的文件的size总和),后续作为开始空间回收的判断依据。如下图7所示:
假设红色的Header表示当前的文件已经被标记为删除。根据空间回收策略,如有效空间相对Volume存储空间的占比小于50%,表明已经删除的空间占比超过50%,此时需要对Volume进行Compaction操作,方法如下:
1) 扫描整个Volume里面的每个文件,如果已经删除直接跳过这个文件。
2) 如果是没有被删除的文件,将文件的内容追加到新的Volume中,并修改高性能存储池中文件的元数据,修改位置信息重新指向新Volume中的位置。
3) 整个Volume扫描完成,有效文件迁移完毕后,删除当前的Volume大文件。
除了文件删除操作,文件修改操作也需要回收老的数据内容占用的空间,采用类似的方法。在处理修改的操作日志时,将旧数据内容的Header标记为删除,在后续Compaction操作时回收这部分数据空间。
04评价
为了验证此设计方案的优化效果,在相同的网络环境上搭建了两套分布式存储系统:
优化前系统:
1) 3台服务器,每台部署5个机械硬盘存储节点,最小分配单元4KB。
2) 在15个存储节点上创建一个大容量3副本存储池。
优化后系统:
1) 3台服务器,每台部署5个机械硬盘存储节点和一个SATA SSD存储节点,最小分配单元64KB。
2) 在15个存储节点上创建一个大容量3副本存储池,在3个SATA SSD存储节点上创建一个高性能3副本存储池。
3) 设置高性能存储池中的小文件在写入2小时后,自动迁移到大容量存储池中,大容量存储池中的volume size默认为4MB。
可以从以下几个方面评价该设计方案的优化效果:
4.1热数据的读写性能
在海量小文件数据还没有迁移到大容量存储池之前的性能比较。
(1) 写入性能:
如下图8所示,分别在多种不同并发情况下写入1000万20KB文件的性能测试结果:
由于高性能存储池采用的存储硬件是SATA SSD,虽然高性能存储池只有3个存储节点,大容量存储池有15个存储节点,优化后系统的写入QPS依然达到优化前的3倍。
(2) 读取性能:
如下图9所示,分别在多种不同并发情况下读取1000万 20KB文件的性能测试结果:
可以看到优化后系统的读取QPS相对优化前提高20%~30%,没有达到写入的优化效果主要是存储节点缓存的作用。
4.2冷数据的读取性能
数据在高性能存储池中写入后,按照策略数据变冷会迁移到大容量存储池中,优化前和优化后(小文件合并之后)的读取性能对比结果如下图10所示:
由于小文件合并之后,文件的读取需要先到高性能存储池中读取元数据,然后到大容量存储池中读取文件的数据,实际上多经过了一次网络RTT,因此会导致一定的读取性能下降,从结果看读取QPS下降了约12%。后续系统会采用性能更好的NVME SSD搭建高性能存储池进行测试。
4.3存储效率提升
在优化后的系统中,在小文件合并前与合并后,存储资源的使用情况如下图11所示:
合并前最小存储单元是64KB,因此一个20KB的文件也需要占用64KB的存储空间,而且是3副本,因此不算元数据需要的存储空间是:64KB * 3 * 10000000 = 1831GB。
合并之后的Volume size是4MB,1个20KB的文件,只需要占据20KB的数据存储空间加上少量元数据的存储空间,因此不算元数据需要的存储空间是:20KB * 3 * 10000000 = 572GB。
因此合并之后的存储效率是合并之前存储效率的3倍多。
4.4故障恢复速度
同样的1000万文件,下线1个存储节点,此时会导致数据迁移恢复数据,完全采用默认的配置,优化前和优化后耗费的数据恢复时间如下图12所示:
由于优化后大容量存储池中只存放大文件,因此其恢复速度比较快,从结果看优化后的故障恢复速度是优化前的16倍。
05结论
本文分析了使用Ceph存储海量小文件的挑战和一些问题,并从硬件优化,元数据管理优化和小文件合并优化几个层面考虑并设计实现了一套基于双层存储池的小文件合并优化分案,通过比较可以发现新方案在读写性能,存储效率以及故障恢复速度上都有较大的提升。