首页 > > > 关于《“存储”的前半生》,你可能得看看这篇干货文~

本文目的: 本文试图解构存储的几种基本模型,来勾勒存储性能的约束因素、功能覆盖和未来趋势。
本文暂不涉及分布式存储相关,所述皆以单机存储为基本出发点。
鉴于存储体系的庞杂,本文不便一一阐述,寄希望于围绕若干主线分析讨论,不周之处在所难免。
本文所言存储是指,将数据以某种形式存储在持久化介质上,并提供持续稳定的访问。
存储设计与技术选型,通常与业务强关联。
比如Cache业务中,数据表现为文件或切片,存储介质表现为SATA盘、SSD盘,提供读、写、删三种基本操作,访问途径通常是API或者Restful API。注意不提供append操作,数据允许全部丢失。
|
比如面向用户的存储业务中,基本操作可能会从文件扩展到目录,比如目录的创建、删除、列表等。
诸如文件追加写、文件重命名、文件移动、目录重命令、目录移动这类在POSIX中常见的操作,在存储设计中并不常见,对这些操作的支持,意味着整个存储设计会发生根本性改变。
所有的存储,本质上由两部分组成:name node和data node。
Name node的作用就是从文件路径映射到文件内容的实际存储位置:
Name node: path ------- location
比如,读文件前,我们把文件的完整路径告诉给name node,name node检索后将结果返回,这样我们就知道文件存储在哪块磁盘、哪个物理文件(可选)、偏移量是多少、文件长度是多少等信息:
(disk_no, file_no, offset, len)= NameNode(path)
那么data node的作用又是什么呢?data node就是用来管理磁盘空间的。
比如,写文件时,我们把文件长度告诉给data node,data node经过一番折腾后返回,告诉我们在哪块磁盘、哪个物理文件(可选)、起始偏移量是多少的地方有所需要的空间:
(disk_no, file_no, offset) = DataNode(file_size)
来看基本操作的流程:
3.1 读操作:
(3.1.1)通过file path,检索name node,获得location信息
(3.1.2)访问location,获取文件内容
3.2 写操作
(3.2.1)通过file size,要求data node分配适当的空间,返回location
(3.2.2)通过file path,检索并插入到name node,绑定file path和location
(3.2.3)将文件内容写入到location指向的磁盘位置
3.3 删操作
(3.3.1)通过file path,检索name node并删除file path,同时返回绑定的location信息
(3.3.2)通过location,要求data node回收对应的磁盘空间
形形色色的存储,就是由各种形态的name node和data node组合而成,我们考虑三种模型:
存储模型一(M1):
以RFS为代表的inode模型,即name node以树形结构存储完整的文件路径,data node管理块状磁盘设备。检索从name node根部开始,根据文件路径,沿树逐级查找。Data node管理整个磁盘空间的分配与回收。根据POSIX文件系统标准,inode大小不应小于255字节,考虑到树形结构组织的需要,必然大于256字节,这样,inode的实际大小最小就是512字节(对齐)。Inode的数量取决于所存储的文件的平均尺寸。
由于inode较大,name node需要的空间较大,data node需要的空间取决于page大小的设定,总的来说,大的空间需求量带来设计上的挑战。
存储模型二(M2):
以HFS为代表的inode模型,即name node以树形结构存储完整文件路径的哈希值,data node管理块状磁盘设备。检索时,根据文件路径的哈希值,直接从name node中查找。Data node管理整个磁盘空间的分配与回收。理论上,Inode的大小可以控制在64字节,其中16字节存储哈希值。Inode的数量取决于所存储的文件的平均尺寸。
Inode较小,带来的红利是整个name node可以预先加载到物理内存,检索性能高;data node需要的空间取决于page大小的设定。
存储模型三(M3):
以ATS为代表的极简模型,即name node以树形结构存储完整文件路径的哈希值,data node则退化为一个磁盘位置指针。检索时,根据文件路径的哈希值,直接从name node中查找。而data node仅负责提供写入时的磁盘位置信息,即磁盘空间的分配退化版,不再支持(精确)回收。理论上,Inode的大小可以控制在64字节,其中16字节存储哈希值。Inode的数量取决于所存储的文件的平均尺寸。
Inode较小,可以把整个name node预先加载到物理内存,检索性能高;退化版的data node带来的红利是不占用空间,而且总是顺着磁道在当前位置插入,带来的代价是不支持空间的回收(把磁盘想象成一个环形存储,总是覆盖写)、以及每次读磁盘伴随着一次写磁盘(读取的数据插入到data node指向的当前磁盘位置)。
这个问题反证即可:如果只有空间的分配,没有回收,那么空间的有限性决定了分配过程必然会在有限的时间内终止,这与实际应用场景不符。 大家的问题应该集中在回收谁、如何回收上。 对于M1而言,name node的inode较大,信息载荷较大(参见下面的信息熵),因此在name node中判定“回收谁”上比较容易,至于“如何回收”,就是data node算法的问题,M1的data node管理精细,可以做到精确回收。 对于M2而言,name node的inode较小,信息载荷较小,但在name node中判定“回收谁”还是可以做到的,M2的data node管理同样精细,可以做到精确回收。 对于M3而言,name node的inode较小,信息载荷较小,同样可以做到在name node中判定“回收谁”,但是M3的data node退化为一个表示当前位置的磁盘指针,已经没有办法做到精确回收,因此,在name node中判定“回收谁”没有意义了。或者说,对于M3而言,已经做不到精确判定“回收谁”,那么就降格为“随便回收”吧,而且只能由data node负责(注意:name node的判定没有意义,无须name node参与)。Data node只有一个磁盘位置信息,它的影响力也就在这个磁盘位置前后,因此data node磁盘位置指针稍微向前挪动一点,回收掉前面的数据即可,这也是它唯一的选择。
M3的data node退化为一个磁盘位置指针,比特数与M1和M2的data node不在一个量级上,这样的信息载荷不足以做到精确回收。 综上两点,循环覆盖写就是M3的必备特征,ATS的实现方式别无选择。
未必。这与M3的具体实现有关。举例:从data node的磁盘位置指针出发,经过圆心划一条直线,将环形存储一份为二,如果读磁盘的区域落在与data node同属的半圆上,则不写磁盘;如果落在另半圆上,则写磁盘。这样,我们有50%的概率不写磁盘。从实现的角度来看,还要考虑覆盖写是否会引起数据被截断的不完整性等约束以及解决办法。从算法复杂度来看,还是指数级的,并没有下降到多项式级。 |
以上每种模型都有一种或多种实现方式,此处我们不关心实现。不妨依次将M1、M2、M3排成一行,我们来看“演进”过程:
从M1到M2:name node空间需求下降到1/8,目录管理和列表功能全部损失
从M2到M3:data node退化归零,磁盘管理功能损失一半
从M1到M3的“演进”过程,就是以功能损失换取空间下降的过程、随机读随机写降格到随机读追加写的过程,其它不在本文讨论范围内的存储模型也遵循同样的规律。
|
通常我们对比存储,是从功能与性能两方面对比分析,本小节限于功能对比,性能对比留给下小节。
先说从M1向M3“演进”的合理性:空间,尤其是物理内存空间,是稀缺的、有限的资源,而目录操作一般是低频操作,因此损失目录操作换取更小的内存占用,是合理、高性能的选择。对于Cache业务数据可丢失的特点,允许磁盘数据无预警覆盖,因此损失磁盘空间回收功能也是可以接受的。
再说从M1向M3“演进”的不合理性:内存价格每三年一个周期,下降是必然的,而且内存容量会继续增大,不排除被其他新技术取代的可能;目录操作虽然是低频操作,但却是用户或业务的“刚需”,损失掉的功能就需要以辅助的手段补回来,比如外挂数据库,本质上就是为了具备M1的name node的所有功能,付出流程代价。极小化磁盘空间分配功能、损失掉磁盘空间回收功能,则直接限定了存储的适用范围,代价不言而喻。
相对于功能分析带有明显用户或业务需求而言,性能对比分析则复杂得多,我们需要实践与理论结合。
站在存储外面看性能,黑盒分析维度有:
Latency
QPS
Disk IO
Network IO
CPU Occupation
Mem Occupation
Connection Num
站在存储里面看设计对性能的影响,白盒分析维度有:
内存管理
中断/用户态与内核态切换
顺序读写
裸盘管理
AIO(异步IO)
DIRECT IO
我们做白盒分析。在此之前,有必要介绍一下pageCache的功效。
pageCache本身是为了平衡高速内设(比如CPU、内存)与低速外设(如磁盘)之间的性能差异而产生的。内核从外设读数据时,先存进pageCache,然后再送给应用层;写数据时,先存进pageCache,再慢慢同步到外设。pageCache可以使用所有未被使用的物理内存页,物理内存不够时,可以通过释放干净页和同步脏页到外设的方式,淘汰一批pageCache。磁盘属于机械设备,不可避免地存在时延随机抖动,pageCache可以减少抖动,从而提供相对可接受的稳定访问。
4.1 DIRECT IO
通常,对文件的读写主要经过应用层缓存(可选)、内核pageCache缓存、磁盘。如果开启DIRECT IO,则可以跳过pageCache。
那么,什么时候需要开启DIRECT IO呢?有两种可能:
担心数据在pageCache中,不能及时同步到磁盘,导致pageCache数据和磁盘不一致。在诸如掉电等异常情况下,引起元数据、文件数据等毁坏。
在应用层开辟大内存空间,自行管理缓存。
可能(1)不予考虑。可能(2)是基于对业务的充分自信,相信自己的缓存与淘汰策略明显优于pageCache的策略。
4.2 AIO(异步IO)
磁盘AIO是有争议的。SATA磁盘只有一个磁头,无论如何异步化,读写都需要移动磁头到指定位置,读写指定长度的数据,假设磁盘速率稳定,异步和同步耗时相同。在单进程单线程模型中,AIO不会有性能上的提升。在单进程多线程中,可以按照“生产者-消费者”模型,设立消费者线程专门读写磁盘,前端生产者线程负责网络数据收发,这又导致生产者和消费者之间的速率要匹配,否则生产者疯狂生产数据,消费者扛不住,因为耗时是固定不变的。反观POSIX文件系统,主要是在内核做读写操作的合并:磁盘上两个或多个相邻的数据的读或写可以合并为一次读或写,以减少磁头移动的次数和距离。
应用层磁盘AIO有点鸡肋的感觉,要发挥它的功效有难度。
4.3 裸盘管理
POSIX文件系统属于通用型,要考虑和支持各种情况,势必臃肿低效,而客户或业务、特别是通过restful API这类非标准POSIX API访问的存储需求,支持的接口相对较少,磁盘一般按照块设备管理,给定偏移量和长度就可以完成读写访问,这就给裸盘管理提供了理由。
裸盘管理跳过了pageCache,直接通过内核,在应用层和磁盘之间交换数据。根据业务需要,裸盘管理可能伴随应用层缓存,来代替pageCache的功效。
4.4 顺序读写
目前,一般都是把文件内容放在连续的磁盘空间中,对外不提供append操作接口和随机删改文件内容的接口。
各存储都会关注顺序读写,因此彼此性能差距可以忽略。
4.5 中断/用户态和内核态切换
时钟中断是指我们需要通过时钟中断来获取当前时钟信息。频繁的时钟中断会降低系统性能,因此服务器普遍采用墙上时钟的方式来获取近似时钟、减少中断次数。存储服务器在时钟中断上实现相近,性能差距可以忽略。
缺页中断是指访问进程空间的某个地址时,发现没有映射到物理页,从而产生缺页中断,要求内核分配相应的内存页。如果要访问的地址是从磁盘映射到进程空间的,则还会触发内存与磁盘之间的数据交换。缺页中断触发的流程比较长,对性能影响比较大。换言之,如果能把数据全部放进物理内存,缺页中断彻底消失,性能最高。
系统调用中断是指通过系统调用,比如文件的read/write访问接口,从用户态切换到内核态。在用户态与内核态互相切换的过程中,必然伴随一次数据的内存拷贝。内存拷贝是耗时操作,对性能影响较大。
Intel针对intel ssd提供SPDK,允许用户直接在用户空间访问SSD盘,既减少了用户态和内核态之间的数据交换,又减少了冗长的中间环节。从intel实测数据和厂商数据来看,性能相当优异。
4.5 内存管理
无论是name node和data node元数据的检索,还是存储软件在运行过程中产生的内存分配与回收,都离不开内存的管理。迄今为止,除了CPU,随机访问最快的就是内存,各存储都在内存的占用、使用上做足了文章。
通行的做法是,应用层申请一大块内存,自己进行分配与回收管理,这样可以最大程度减少缺页中断、内存碎片化。被访问最频繁的数据固定在内存,可以获得最高的性能提升。
事实上,抛开差异不明显的次要因素和实现因素,真正影响存储性能的因素有三个:
内存大小
内存拷贝次数
磁盘IO次数
内存大小则是决定性因素:内存越大,内存命中率越高,中断/用户态与内核态切换越少,磁盘IO次数越少,整体性能自然越高。
从极限角度也可以理解为什么内存大小是决定性因素:内存看作磁盘的缓存,内存大到可以装下整个磁盘时,所有访问都可以在内存完成,性能最高;内存小到为零时,所有访问都必须落盘,性能最低。
当然,存储的性能并不与内存线性相关,它还与存储的实现方式和业务场景高度相关: 实际运行中,当数据经过一段时间的预热后,内存的增加可能不会再提升存储性能了。
回顾RFS(M1型存储)和ATS(M3型存储)测试数据,我们可以看到RFS性能并不比ATS差,相反很多方面更胜一筹。在name node检索算法上,RFS和ATS的复杂度都是O(logn),不相上下;RFS的name node是ATS的8倍,不能全放在内存中,访问容易触发缺页中断,性能较ATS差距大;ATS启用DIRECT IO,直接与磁盘交互,RFS充分利用pageCache,数据预热后直接从pageCache获取;ATS采用单进程多线程AIO模式,RFS采用单进程多协程同步IO模式,估计差距不大;ATS读总伴随着一次写,RFS不存在额外写。
这样的测试结果和对比分析,留给我们许多启示
5.1 启示一:M1型存储并非不可取,内存才是性能关键
内存是性能关键自不待言。从M3向M1方向“演进”的过程,就是一个功能逐渐增加的过程。从应用的角度来说,功能越丰富,外挂就越少,对业务的支持越方便。M1更高效合理地利用内存,也可以获得不俗的性能表现。
5.2 启示二:DIRECT IO和AIO可有可无
如果是应用层内存缓存,DIRECT IO很有必要,数据不用在pageCache和应用层重复缓存,否则DIRECT IO不用开启。
对于AIO,不建议开启。
5.3 启示三:内核的pageCache缓存机制没那么差
如果都是内存命中,应用层内存命中比pageCache命中路径更短、性能更高:少了一次系统调用中断和一次从内核态到用户态的数据拷贝。
如果都是内存未命中,则二者相差无几。
如果一个命中、另一个未命中呢?二者谁的命中率更大呢?从RFS和ATS实测结果推算,RFS的命中率更高一点,因为pageCache可以利用所有未被使用的物理内存,这也说明pageCache甄别数据热度的算法没那么差。
5.4 启示四:用户空间效率高,轮询不比中断差
用户空间执行效率高这是不争的事实:缓存从内核pageCache移到用户空间,内存分配与回收从内核内存管理移到用户空间,协程可以认为是线程在用户空间的翻版,……,这一切的一切都在说明:人们受不了内核了,自己动手干吧!
更恐怖的是,intel的DPDK和SPDK:DPDK将网络IO从内核移到了用户空间,SPDK将SSD IO从内核移到了用户空间。
为什么说这事儿恐怖?因为以前的网络IO和磁盘IO是通过硬件中断、事件驱动的方式与应用层交互的。从内核移出后,相当于去掉了驱动机制:没人通知应用层什么时候干什么事了,应用层只能靠自己瞎忙活!应用层大招没有,土鳖的办法倒有一个:轮询。
事实证明,轮询的效率不比中断差。中断曾经是“众多调度方案中,表现最好的一个”,为什么现在不如轮询了呢?
真实的原因只有一个:外设不再是低速的代名词了!对比一下,intel SSD最新款Optane的随机读写性能仅仅比物理内存低20%。外设的高性能,使得不经过内核的平衡与调度,也能扛住应用层不停轮询,并提供很好的性能。
计算机硬件在悄然发生深刻变化,软件在演进,存储的架构设计也必然会随之发生变化。

SACC2017

SACC2017
作为国内最受欢迎的架构师盛会,2017 第九届中国系统架构师大会 (SACC) 将于将于 2017 年 10 月 19-21 日在北京新云南皇冠假日酒店震撼来袭。
大会以 “云智未来” 为主题,云集国内外顶级专家,围绕云计算、人工智能、大数据、移动互联网、产业应用等热点领域展开技术探讨与交流。本届大会共设置 2 大主会场,18 个技术专场;邀请来自互联网、金融、制造业、电商等多个领域,100 余位技术专家及行业领袖来分享他们的经验;并将吸引 4000 + 人次的系统运维、架构师及 IT 决策人士参会,为他们提供最具价值的交流平台。
点击阅读原文立享6.8折购票优惠~
原文作者:;如有侵权,请联系管理员删除。