注:本文内容整理自 SmartX CTO 张凯在 SMTX OS 3.5 新品发布会上的演讲。算法
咱们仍是先来看一下咱们会对数据存储引擎模块有什么样的需求。服务器
首先,确定是仍是可靠。由于咱们客户的应用场景都大部分是核心的应用,数据可靠是要绝对保证的,没有任何妥协的空间。网络
其次是性能,目前在万兆网络和 SSD,包括 NVMe SSD 都已经很是普及。随着硬件的速度愈来愈快,性能的瓶颈会从硬件转移到软件。尤为对于存储引擎来讲,性能相当重要。数据结构
除了追求绝对的性能之外,咱们还但愿可以作到高效。咱们但愿每个 CPU 指令都不被浪费。咱们追求用最少的 CPU 指令完成一次 IO 操做。这背后的缘由是,存储硬件设备愈来愈快,目前最快的存储已经能够作到单次访问只须要 10 纳秒。而若是程序中加一次锁,作一次上下文切换,可能几百个纳秒就过去了。若是不作到高效的话,目前的 CPU 可能彻底没法发挥出 SSD 的性能。除了高效的使用 CPU 之外,咱们也要高效的使用内存资源,网络带宽资源。同时,因为目前相同容量的 SSD 的价格还高于 HDD 的价格,因此咱们也尽量的节省磁盘空间的占用,经过利用压缩,去重等技术,提升 SSD 的空间使用效率。异步
最后,也是很是重要的一点,存储引擎须要易于 Debug,并且要易于升级。对于软件工程师来讲,50% 以上的工做时间都是在作 Debug,而对存储软件工程师来讲,这个比例可能更高。咱们但愿作一个很是易于 Debug 的软件产品,若是发现问题,能够快速的定位并修复。升级也是同样,如今软件的迭代速度愈来愈快,咱们但愿软件能够方便的易于升级,这样咱们可让用户更快的使用上新版本的软件,享受到新版本的功能,以及性能的优化。ide
接下来,咱们来看一下具体的实现。不少传统的存储厂商在实现存储引擎的时候,每每会选择把整个 IO 路径的实现放在 Kernel Space 里面。例如在上图中,上层是一个核心的存储引擎,下层是文件系统,块设备,以及驱动。因为网络栈也是实如今内核中的,把存储引擎放在内核里面就能够最大化性能,减小上下文切换(Context Switch)。但这种实现有不少很是严重的问题,首先就是难于 Debug。若是你们作过内核开发,就会知道在内核中 Debug 是一件很是麻烦的事情。并且开发语言也只能用 C,不能用其余语言。同时,在内核里面开发,升级会很是困难。一次升级,不论是 Bugfix,仍是增长新功能,均可能须要重启整个服务器,这对于存储系统来讲代价是很是巨大的。还有一个很重要的因素就是故障域很是大。Kernel 里面的模块若是出问题,可能致使整个 Kernel 被污染,多是死锁,多是 Kernel Panic。一般也是须要重启服务器才能修复。
性能
既然有这么多问题,那咱们在设计的时候确定不会选择用 Kernel Space 的方式。咱们选择在 Userspace,也就是用户态实现咱们的存储引擎。测试
在 User Space 实现,不少项目会选择把存储引擎构建在 LSM Tree 的数据结构上。LSM Tree 运行在文件系统之上。User Space 和 Kernel 比起来更灵活,能够用各类语言;升级也很方便,只须要重启一下进程就能够,不须要重启服务器;User Space 的故障只会影响到服务进程自己,并不会影响到 Kernel 的运行。但这种方式的问题就是性能不够好,因为 IO 仍是须要通过 Kernel,因此会产生上下文切换,这个切换就会引入性能的开销。优化
接下来,咱们来讲一下 LSM Tree。LSM Tree 的数据结构以及实现咱们在这里就作不详细介绍了。总的来讲,LSM Tree 是不少存储引擎的核心。
spa
LSM Tree 的好处就是实现起来是相对简单的,有不少开源的实现能够参考,并且它对小块数据写入优化作的很是好,会将小块数据合并,并批量写入。
然而 LSM Tree 并非银弹,它最大的问题因为他的数据结构而致使的『读放大』和『写放大』。这个问题会有多严重呢。咱们能够来看一下这个图,这是一个对『读写放大』的测试结果。从图中能够看到,若是写入 1GB 的数据,最终会产生 3 倍的数据写入量,也就是 3 倍的『写放大』。若是写入 100G 的话,则会被放大到 14 倍,也就是说若是写 100G 的数据,实际上在磁盘上会产生 1.4TB 的写流量。而『读放大』会更加严重,在这个场景下会放大到 300 多倍。这就违背了咱们最开始提到了咱们但愿提升硬件效率的诉求。
LSM Tree 虽然有各类各样的好处,可是因为存在严重的『读写放大』问题,因此咱们并不会采用LSM Tree 来作数据存储引擎。咱们能够借鉴 LSM Tree 中优秀的思想,结合咱们本身的需求,实现一套存储引擎。这个包含了数据分配,空间管理,IO 等逻辑。
接下来,咱们看到这个这个图中还有一个文件系统。这个文件系统是实如今内核中的,在块设备之上。你们比较常见的文件系统包括 ext4,xfs,btrfs 等,不少存储引擎也是实如今文件系统之上的。然而咱们须要思考一下咱们是否真的须要一个文件系统。
首先,文件系统所提供的功能远远多于存储引擎的需求。例如文件系统提供的 ACL 功能,Attribute 功能,多级目录树功能,这些功能对于一个专用的存储引擎来讲,都是不须要的。这些额外的功能常常会产生一些 Performance Overhead,尤为是一些全局锁,对性能影响很是严重。
其次,大部分文件系统在设计的时候,都是面向单一磁盘的设计方式,而不是面向多块磁盘的。而通常存储服务器上都会部署 10 块,甚至更多的磁盘,并且有多是 SSD,有多是 HDD,也多是混合部署。
第三,不少文件系统在异步 IO 上支持的并很差,尽管支持异步 IO 的接口,但实际使用过程当中,偶尔仍是会有阻塞的状况发生,这也是文件系统里一个很是很差的地方。
最后一个问题,文件系统为了保证数据和元数据的一致性,也会有 Journaling 的设计。但这些 Journaling 也会引入写放大的问题。若是服务器上挂载了多个文件系统,单个文件系统的 Journaling 也没法作到跨文件系统的原子性。
最终咱们在设计存储引擎的时候,咱们选择了抛弃文件系统,抛弃 LSM Tree,本身在作一个理想中的存储引擎,去掉没必要要的功能,尽量的避免写放大。把咱们想要的功能直接实如今块设备上。
咱们并无想要本身实现 Block Layer 这一层,这是由于 Linux Kernel 中,Block Layer 是很是薄的一层,里面实现的算法也很是简单,这些算法也都有参数可调,也都有办法关闭掉,因此不会有太多额外的性能开销。
左边这个图就是 ZBS 目前的实现方式。但这种方式最大的问题仍是性能,Block Layer 和 Driver 都运行在 Kernel Space,User Space 的存储引擎的 IO 都会通过 Kernel Space,会产生 Context Switch。将来咱们会转向右边这个图的方式,经过 SSD 厂家提供的 User Space 驱动,结合 PMD(Poll Mode Driver)引擎,以提供更好的性能。
接下来,咱们看一下 ZBS 的 User Space 存储引擎具体的实现。
IO Scheduler 负责接收上层发下来的 IO 请求,构建成一个 Transaction,并提交给指定的 IO Worker。IO Worker 负责执行这个 Transaction。Journal 模块负责将 Transaction 持久化到磁盘上,并负责 Journal 的回收。Performance Tier 和 Capacity Tire 分别负责管理磁盘上的空闲空间,以及把数据持久化到对应的磁盘上。
目前 SmartX 研发团队正在开发下一代 User Space 存储引擎,感兴趣的同窗能够经过评论区进行交流,也欢迎你们投递简历到 jobs@smartx.com
想了解更多信息,也可访问 SmartX 官网:https://www.smartx.com