浅谈分布式存储之单机对象存储引擎

前言

构建大规模分布式存储系统通常需要一个高性能、低延迟、稳定可靠的单机存储引擎。对于不同的分布式存储系统其需要的单机存储引擎也不相同。分布式KV存储通常使用RocksDB等单机KV存储引擎,其特点是value通常很小且不可变;分布式数据库通常也是使用RocksDB作为单机存储引擎;分布式对象存储使用Haystack、Bitcask等作为单机对象存储引擎,本质上也是kv存储,其特点是value可以是1B–5GB且对象不可变;分布式块存储也需要高性能的单机对象存储引擎,其特点是对象是可变的,需要支持覆盖写、快照等复杂的功能,其实现难度也大。

不同的单机存储引擎技术点也不一样,在这里,我们仅仅讨论分布式对象存储单机对象存储引擎的设计,但是不会讨论Haystack、Bitcask等模型,具体技术细节可前往Google。我们主要讨论基于Ceph BlueStore单机对象存储引擎来设计适用于分布式对象存储的单机存储引擎。

目录

Inspiration

之前已经有一系列的文章介绍Ceph BlueStore,我们可以发现BlueStore支持覆盖写、快照、对象扩展属性、IO保序等复杂的操作,比较适用于块存储的场景;但是在对象存储S3的场景中,我们发现其实覆盖写、快照、对象扩展属性这些特性我们根本不需要,因为对象存储S3是不可变的,也就不需要覆盖写,甚至IO保序也不需要,因为对象存储S3是使用HTTP协议访问的,不论在多客户端还是单客户端的并发条件下,都不需要保证同一对象put object的并发性。

线上生产环境的存储机型通常可以选配为大容量的HDD盘和高性能的NVME盘结合;同时专门为NVME打造的SPDK直接By Pass Kernel,大大缩短了IO路径,减少了IO延迟;此外BlueStore的写入延迟也有一部分耗费在IO保序、覆盖写的DeferredWrite和RocksDB存储元数据的sync等上面。

我们可以考虑结合BlueStore的优秀特性、SPDK的高性能以及特定的存储机型,打造一个适用于分布式对象存储的支持分层缓存的单机对象存储引擎BlobStore。不要与SPDK的blobstore搞混哦。

BlobStore接口

相比于块存储或者文件存储的单机存储引擎,对象存储S3的单机存储引擎对外提供的接口很简单。

class BlobStore {
  public:
  	typedef void *completion_t;
  	
  	int open();
  	void close();
  	
  	int put(const char *key, const char *val, uint64_t expire);
  	int get(const char *key, char *val, uint64_t off, size_t len);
  	int delete(const char *key);
  	
  	int async_put(const char *key, const char *val, uint64_t expire, completion_t comp);
  	int async_get(const char *key, char *val, uint64_t off, size_t len, completion_t comp);
  	int async_delete(const char *key, completion_t comp);
  	
  	......
};

提供同步和异步的读写操作,推荐使用异步的读写操作。

BlobStore架构

BlobStore在BlueStore的工作上简化了许多功能,同时也增加了分层缓存功能,所以大致上和BlueStore的架构很类似。

  • BlockDevice:物理块设备,直接操作裸设备,支持KernelDevice(内核态异步IO)、NVMEDevice(SPDK用户态IO);同时提供分层缓存的功能。
  • RocksDB:存储WAL、对象元数据、磁盘分配器元数据。
  • Allocator:磁盘分配器,使用bitmap磁盘分配器,负责高效的分配磁盘空间。
  • SPDK:使用spdk提供的blobfs、blobstore来支持rocksdb的存储,也可使用BlueStore的。
  • FreeListManager:使用bitmap方式来管理空闲空间列表。
  • Cache:主要用来缓存元数据,可缓存部分数据,数据主要缓存在BlockDevice提供的缓存里。

具体的实现细节可参考之前写的系列文章:

IO流程

写流程

去掉了覆盖写的状态机,简化了BlueStore的写入流程后,写入流程类似于SimpleWrite。

  1. 上层调用BlobStore提供的async_put()接口,提交数据到BlockDevice等待回调。
  2. BlockDevice回调处理,标识着数据已写完,放入KV_QUEUED,准备写KV元数据。
  3. kv_sync_thread提交KV元数据事务,持久化存储到RocksDB中,元数据写完,放入kv_committed队列。
  4. kv_finalize_thread设置DONE的状态,并回调上层。

对象存储S3并不保证对象的并发性,所以kv_sync_threadkv_finalize_thread均可以多线程化,但是kv_sync_thread多线程化就会同时对RocksDB进行并发的写入,也即并发的对磁盘执行sync操作,在磁盘比较繁忙的情况下未必会好。具体多线程化带来的性能提升还需要实际测试验证。

并行的提交元数据和数据,然后异步等待元数据和数据完成,然后写入Object的状态位标志成功还是失败;但是写入状态位也需要持久化,对象存储的元数据通常很小而且RocksDB使用SPDK的话,状态位持久化花费的时间可能并不比元数据持久化的时间少多少,带来的性能提升或许很有限,却会使得写入流程复杂化。

读流程

读流程比较简单:

  1. 上层调用BlobStore提供的async_get()接口,先从缓存读取对象元数据。
  2. 如果缓存没有命中,则从RocksDB读取对象元数据,包括数据位置,checksum等。
  3. 从BlockDevice读取数据,计算并且比对checksum。
  4. 回调通知上层数据读取成功。

分层缓存

BlobStore相对于BlueStore来说,最大的区别便是加入了分层存储,准确的来说,应该是BlockDevice层面的SSD缓存。分层存储和缓存对外展现的效果大同小异,无外乎提升读写性能,但是内部实现确实不同。 最简单的便是容量大小的不同,分层存储的容量大小是 FastDevice + SlowDevice。缓存的容量大小是 SlowDevice。考虑到实现程度的难易、SSD相对于HDD更容易发生磁盘错误、SSD通常容量很小的情况,我们实现BlockDevice层面的SSD缓存而不是分层存储。

为什么不直接使用bcache、flashcache、lvmcache?因为这些都是在内核态,SPDK的IO路径全部在用户态;同时由于BlobStore是适用于对象存储的单机存储引擎,IO路径以及状态机比较简单,对于BlockDevice读写性能的提升能够很好的反应到整体BlobStore的读写性能提升。所以我们打算自己造一个简单可控的轮子。

直接在BlockDevice层面实现缓存,BlockDevice因为是block粒度,对象无关的,不能感知对象语义,同时BlobStore又是对象粒度的,就类似于PageCache之于文件系统,一个是page粒度,一个是file粒度;所以为了更好的进行缓存的promote和demote,BlobStore可能需要一个object access statistics模块,用来统计对象粒度的读写情况,然后转换成block粒度,同时BlockDevice对外提供一个依赖注入的接口,object access statistics模块实现并且注册这个接口,使得外部可以干预缓存的提升和淘汰,从而提升缓存的命中率。当然外部不实现这个接口,仅依赖于BlockDevice内部的淘汰提升策略也是可以的。

FastDevice:快速的存储介质,通常使用SSD或者NVME。

SlowDevice:慢速的存储介质,通常使用大容量的HDD。

支持3中不同的缓存模式:

  • Writeback:数据写入FastDevice就返回,之后异步的刷入SlowDevice。
  • Writethrough:数据写入FastDevice的同时也会写入SlowDevice。
  • Writearound:数据不写入FastDevice,仅仅写入SlowDevice,类似于ReadOnly Cache。

对于读操作,所有的数据都会从FastDevice读取,如果FastDevice没有,则从SlowDevice读取相应的数据到FastDevive,然后从FastDevice读取返回。

我们知道HDD的随机IO速度很慢,但是顺序IO速度还可以;SSD的随机IO相对于HDD很快,但是顺序IO相对于HDD提升并不大,尤其是做了RAID的HDD。我们可以设置参数skip_seq_block_size,如果顺序IO的size小于这个值,那么便从FastDevice读写;如果大于这个值,那么便从SlowDevice读写。

同时由于NVME以及SPDK的出现,相对于HDD不管是随机IO还是顺序IO有了全面性的提升,此时就算将顺序IO放在NVME上,对性能的提升也很大。我们可以设置参数cache_all,使得所有的数据都在FastDevice进行读写。

SSD虽然相对于HDD速度快了不少,但是我们知道其是由一个个的Flash构成,靠电压的高低进行读写,没有HDD的稳定性好,数据发生静默错误、磁盘出错的概率也比HDD要高。如果在FastDevice上发生数据错误时,可以尝试恢复数据,否则就把FastDevice Writeback模式下的脏数据刷回SlowDevice,然后关闭FastDevice,使得之后所有的IO都走SlowDevice。

最后YY

这只是一个Idea,并没有实现的原型,但是可以基于BlueStore快速实现一个没有分层缓存的BlobStore。关于分层缓存的思考只是一个模型,并没有具体到细节,大家有好的想法和意见欢迎随时交流指导。

参考资源

转载请注明:史明亚的博客 » 浅谈分布式存储之单机对象存储引擎

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,您说多少就多少!

君子爱财,取之有道;贞妇爱色,纳之以礼。