1.2 数据存储的目标
正如1.1节所述,数据存储具有极高的重要性,因此一代又一代的人对其进行持续的创新设计,以达到高性能、高易用性、高可靠性等目标。本节将简单介绍这些目标以及数据存储的相关软硬件设计,这些内容也将贯穿整本书的其他章节。
1.2.1 高性能
数据存储的性能指标主要包括吞吐率与延迟,其中吞吐率指单位时间内数据存储系统能完成的操作数目,而延迟是指完成单个操作所需要的时间。高性能数据存储追求更高的吞吐率与更低的延迟,这是由持续增长的上层应用需求所驱动的。例如,“双十一”购物节的成交额逐年提升,这就要求存储系统的吞吐率匹配其上升的趋势;在延迟方面,用户越来越关注服务质量,因此存储系统要尽快地完成每一个数据访问操作,最小化用户所能感知的延迟。
为了达到高性能指标,需要对存储硬件和存储软件共同进行设计。如图1.1所示,现有的存储硬件种类多样,在性能、容量和价格方面各有优劣,例如HDD(Hard Disk Drive,硬盘驱动器)的延迟为10 ms左右,而SSD的延迟仅有10~100 μs。因此,提升存储系统性能最自然的方法就是采用更快速的存储硬件。但这面临着两个关键问题:首先,存储硬件的性能越好,通常它的成本会越高,导致整体系统价格昂贵,难以普及使用;其次,存储硬件的吞吐率越大,一般其容量会越小,这将限制整体系统能容纳的数据量,难以满足持续增长的海量数据的需求。因此,高性能目标的达成还需要存储软件的设计。
图1.1 存储金字塔
由于单个存储硬件的性能有限,存储软件经常将大量存储硬件组合起来,以提供更高的吞吐率。最早的技术包括美国加利福尼亚大学伯克利分校提出的RAID技术[1]。其中RAID 0将数据分散至多块磁盘,同时发挥多块磁盘的性能。除此之外,分布式存储系统也被普遍使用,该存储系统通过网络将多台存储服务器连接起来,向外部提供极高的聚合吞吐率,例如图1.2展示了我国超级计算机“神威·太湖之光”的分布式文件系统架构,它由80个I/O转发节点、144个存储节点,以及72个磁盘阵列构成,每个磁盘阵列包含60块磁盘。
提升性能还可以通过充分利用数据局部性原理设计缓存、预取等机制。数据局部性包括时间局部性和空间局部性:时间局部性是指当某份数据被访问后,在不久后将被再次访问,例如在线购物平台上某些热门商品对应的数据会被频繁地读写;空间局部性是指当某份数据被访问后,其相邻的数据也将在不久后被访问,例如当某个文件被访问后,和它处于同一个文件夹中的其余文件也很有可能被访问。利用时间局部性,存储软件在各个层级设计高效的缓存机制,将经常被访问的数据放置在更快速的系统组件中,由此能同时提升吞吐率和降低延迟。例如,在单个存储服务器中,Linux文件系统将文件数据以页缓存(Page Cache)的形式缓存在内存中,并通过LRU(Least Recently Used,最近最少使用)算法等替换算法提高缓存的命中率;在数据中心中,经常存在由Memcached等软件构建的分布式缓存集群,用于作为后端基于磁盘的存储系统的读缓存。利用空间局部性,存储软件也在各个层级设计了预取机制,也就是将未来可能要访问的数据提前从低速组件调换至高速组件。例如Linux文件系统采用了readahead预取算法,当文件被顺序访问时,会将后面的内容提前调至页缓存中。
图1.2 “神威·太湖之光”的分布式文件系统架构[2]
1.2.2 高易用性
数据存储另一大目标就是高易用性,即上层应用应该方便地与存储系统“打交道”,将数据以一种便捷的方式写入存储系统,并支持后续的高效读取。为了提供高易用性,人们设计了不同的数据存储接口,如图1.3所示。其中语义最简单的是块接口,即整个存储空间被抽象成大小相等的数据块,每份数据块具有唯一的数字标识符,应用通过数字标识符读写对应的数据块。块接口难以表达大部分应用的数据语义,因此通常作为其他存储接口的底座,而不直接暴露给上层应用。键值接口的易用性高于块接口:它维护键值对的集合,每份键值对包括键和值两部分,均是长度可变的数据块,应用通过键来定位至对应的键值对,进行插入、更新、查询以及删除等操作。由于语义清晰、用法灵活,键值接口被广泛使用,例如亚马逊将在线购物相关的数据存储在键值系统Dynamo[3]中。文件接口将数据组织成目录树结构,主要包括目录文件和普通文件,应用可以创建、删除和读写文件。其中普通文件存储着文件数据,目录文件记录着该目录下的普通文件以及其他目录文件的名字,每个文件具有权限等属性,用于访问控制。文件系统这种层次化结构十分适合普通的计算机用户,因此几乎所有的桌面操作系统都自带文件系统,为用户提供管理数据的易用方式。此外,为了进一步方便用户使用,还存在针对键值接口和文件接口的扩展,比如有的键值存储系统支持通过多个键查询同一份数据,即二级索引功能;有的文件系统支持事务语义,即保证多个文件操作的原子性。
图1.3 3种常见的数据存储接口
除了存储接口方面,支持数据共享也是易用性的关键。存储阵列虽然解决了容量和性能的限制,但无法进行数据共享,即位于不同位置的用户无法访问同一份数据。随着网络技术和存储协议的发展,数据存储开始朝网络化共享的方向迈进。例如NAS(Network Attached Storage,网络附属存储)支持通过局域网共享文件。此外,更大规模的分布式存储系统也在追求极致的共享能力,例如,Facebook的分布式文件系统Tectonic[4]能够提供数据中心级别的数据存储功能,同时支持不同应用,达到了极高的资源利用率。
1.2.3 高可靠性
数据存储的高可靠性是指在系统面对异常情况时,数据不丢失且服务不中断。异常情况有很多种,包括磁盘失效、服务器崩溃、网络故障及人为事故等,根据Facebook的报告,在一个拥有3000台服务器的生产集群中,一天最多会有110台服务器崩溃。若数据存储的可靠性没得到保证,会造成严重的后果,例如,2017年亚马逊的S3存储系统发生了持续5小时的故障,影响数十万个网站的正常访问,造成约1.5亿美元的损失。
实现高可靠性的基本方式是提供数据冗余,主要包括多副本和纠删码两种机制。如图1.4所示,在多副本机制中,每份数据被重复存储在多个不同位置,因此当某个位置的数据由于某种异常事件无法被访问时,其余位置的数据仍然能够提供服务;而在纠删码机制中,系统将多份数据通过某种编码方式计算出若干份校验块,并将这些数据与相关校验块存储在不同位置;当无法访问某份数据时,其内容可通过校验块和其余数据重新计算获得。相比于多副本机制,纠删码机制具有更低的存储空间开销,但计算和恢复开销更高。对于一个成熟的存储系统,在每个层级都具有相应的可靠性保障:在存储设备层,利用LDPC(Low-Density Parity-Check Code,低密度奇偶校验码)等纠错机制检测数据是否出现错误并尝试修复数据;在服务器层,基于RAID机制的磁盘阵列机制可以容忍某个存储设备的失效;在集群层,通过分布式多副本机制或分布式纠删码处理服务器的异常事件。此外,数据还有可能被周期性地备份和归档。
图1.4 多副本与纠删码
尽管上述机制从理论上能保证数据存储的可靠性,但存储软件是由程序员通过代码编写而成的,若代码中存在漏洞,仍然会造成数据内容发生错误。例如存储软件并发访问处理不当会导致数据内容混乱。因此,为了获得高可靠性,存储软件还需要进行大量的正确性测试,甚至采用一些形式化的手段。亚马逊构建新一代对象云存储系统时,运用了轻量级的形式化方法去验证存储软件在接口语义、数据一致性、并发访问正确性等多个方面是否符合预期[5]。
1.2.4 其他目标
除了高性能、高易用性和高可靠性,数据存储还需满足如下的目标。
高性价比。数据存储追求极致的性价比,希望在更低的单位价格下提供更高的性能。为了达到高性价比,常用的做法是进行存储分级,即将热数据保存在高速存储设备上(如SSD),而将冷数据保存在低速存储设备上(如磁带)。
高安全性。数据存储还需要考虑安全性,以防止用户隐私数据被泄露等严重事件的发生。为了获得高安全性,常用的做法包括数据加密、访问控制、隐私计算等。
高可扩展性。正如前文所述,分布式存储系统通过组合多台服务器,实现高性能数据访问。设计分布式存储系统的重要目标之一就是高可扩展性,即当加入新的服务器时,系统整体性能能够按相应比例提升。为了达到高可扩展性,存储软件需要避免某些服务器成为性能瓶颈,常用的做法包括负载均衡机制、数据动态迁移机制等。