Ceph:可靠的, 可扩展的高性能分布式系统

此文翻译于Sage Weil在2006年发表于OSDI上的文章(Ceph: A Scalable, High-Performance Distributed File System),由于用于本人的毕业设计文献翻译,所以严禁复制和转载,违者必究!

摘要

我们已经开发出ceph,一个能提供卓越的性能,可靠性和可扩展性的分布式文件系统。ceph通过一个伪随机数据分布函数(CRUSH)取代分配表来最大化分离数据和元数据的管理。CRUSH函数被设计用于异构,动态,由不可靠对象存储设备(OSDs)构成的集群中。我们通过将数据副本,错误检测和恢复功能分发给正在运行专门的本地对象文件系统的OSDs来利用设备的智能性。动态的分布式元数据集群提供极度高效地元数据管理和无缝地适应广泛的通用计算和科学计算文件系统工作负载。在各种工作负载下的性能测试表明,Ceph的具有优良的I/O性能和可扩展的元数据管理,支持每秒25万以上的元数据操作。

介绍

系统设计者一直都在试图改善文件系统的性能,这对于极为广泛的一类应用的整体性能很重要。学术界尤其是高性能计算组织在分布式存储系统的性能和扩展性保持领先,一般预示着未来几年的更加通用的需要。传统的解决方案,比如NFS,提供一个直接的模型,服务器对外一个文件系统层次使得客户机可以映射到它们的本地名字空间。即使是被广泛使用,客户机/服务器的中心化本质已被证明是扩展性能的极大阻碍。

最近更多的分布式文件系统已经采用基于对象存储的架构,原先传统的硬盘已经被智能的对象存储设备(OSDs)替换,OSD是结合了一个CPU,网络接口和带有本地缓存的磁盘或阵列的设备。OSD替换传统的客户端进行读写字节的块层接口,使用读写字节数范围更大的对象,将底层块分配的决定给设备本身。客户端通常和元数据服务器(MDS)交互去进行元数据操作(open,rename),同时直接和OSD交互进行文件的IO(read and write),极大地提高了整体的性能。

由于元数据负载分布小或者没有时,采用这种模型的系统会遭受扩展的限制。受限于传统的文件系统设计原则比如分配列表和i节点表不倾向授权给OSD的智能分配,从而导致更加受限的扩展和性能,增加了可靠性的代价。

我们提出ceph,一个能提供优越性能和可靠性同时有望成为拥有独一无二扩展性的分布式文件系统。我们的架构是基于这样的假设:在PB级规模的系统本质上是动态,大型系统都不可避免的是逐步建成的,节点故障是常态而不是异常,工作负载的质量和特点是随着时间的推移不断变化的。

Ceph使用生成函数取代文件分配表来实现数据和元数据操作的分离。这样一来,Ceph可以利用OSD的智能性来分散数据访问,更新序列化,副本和可靠性,故障检测和恢复的复杂度。Ceph采用高度自适应的分布式元数据集群架构,显著提高了元数据访问的扩展性,从而提高整个系统的扩展性。我们讨论了架构设计时针对的目标和负载假设,分析其对系统的扩展性,性能和可靠性的影响,并结合相关的经验实现了功能系统的原型。

系统概述

ceph文件系统有三个组件:客户端其中每个实例开放了一个接近POSIX文件系统接口到主机或进程中; OSD集群OSDs共同存储所有数据和元数据; 元数据服务器集群管理命名空间(文件名和目录),同时协调安全性,一致性和连贯性。我们说ceph的接口是接近POSIX接口的是因为我们发现恰当地扩展接口和选择性放松一致性语义是为了更好贴近应用的需要,并且提高了系统的性能。

该架构的主要目标是扩展性(数百计的PB级或更多),性能和可靠性。可扩展性在多个维度被考虑,包括整体的存储容量和系统的吞吐率以及单个客户端,目录或文件的表现。我们的目标工作量可能包括极端情况下成千上万的主机并发读取或写入到同一文件或在同一目录创建文件。这样的场景在运行科学应用的超级计算集群中很普遍,也越来越表明是未来的通用计算负载。更重要的是,我们认识到分布式文件系统的工作负载本质上是动态的,在活跃的应用程序和数据集随着时间改变的同时,数据和元数据的访问也会显著变化。ceph直接处理扩展性问题的同时,通过三个基本的设计特性:数据和元数据分离动态分布式元数据管理可靠的,自治的,分布式对象存储实现了高性能,可靠性和可用性。

数据和元数据的分离 ceph最大化分离文件元数据管理和文件数据存储。元数据操作(open,rename,etc.)被一个元数据服务器集群统一管理,与此同时客户端直接和OSDs交互实现文件IO(reads and writes)。基于对象的存储早已承诺通过授权底层的块分配决定给单个设备来提高文件系统的扩展性。然而,和现有的基于对象文件系统[4,7,8,32]所采用短对象列表取代长文件块表不同,ceph完全消除了分配表。ceph中的文件数据被条带化到可预见的对象上,通过专用的数据分布函数CRUSH[29]分配对象给存储设备。这允许任何一方来计算(而非查找)包含文件内容的对象名和对象位置,消除了维护和发布对象列表的需要,简化了系统的设计并减少了元数据集群负载。

动态分布式元数据管理 因为文件系统元数据操作构成一般文件系统负载[22]的一半,所以高效的元数据管理对于系统整体性能很关键。ceph采用了基于动态子树划分的新型元数据集群架构,该架构能够在成十上百的MDSs中自适应,智能地分布管理文件系统目录层次的责任。一个动态的层次划分保留了在每个MDS负载里的局部性,方便高效的更新和积极地预取来提高对常见负载的性能。重要的是元数据服务器之间的工作负载分布是完全基于当前访问模式,允许ceph在任何负载下有效地利用可获得的MDS资源,从而实现MDS的近线性伸缩。

可靠的,自治的分布式对象存储 由成千上万的设备构成的大系统本质上是动态的: 它们是逐步构建的,随着新存储的部署和旧设备的退下而伸缩,设备出错是频繁的和预期的,以及大型卷数据的被创建,移动和删除。所有这些因素都需要数据分布演变成能有效利用可获得资源并且保持数据副本所需要的水平。ceph将数据迁移,副本,错误检测和错误恢复的责任交给OSDs集群,OSDs集群存储数据的同时向上提供一个更高层的逻辑对象存储给客户端和元数据服务器。这个方法允许ceph更高效地利用每个OSD的智能(CPU和内存)来实现可靠的,可获得线性伸缩的对象存储。

我们描述了ceph客户端的操作,元数据服务器集群和分布式对象存储以及它们是如何被我们的架构关键特性所影响。也描述了我们原型机现状。

客户端操作

我们通过描述Ceph的客户端操作介绍Ceph组件的整体运作以及与应用的交互。每个主机执行ceph客户端应用代码,提供给应用程序一个文件系统接口。在ceph原型中,客户端代码完全运行在用户空间,能够通过直接链接访问或者经过FUSE(一个用户空间文件系统接口)挂载文件系统访问。每个客户端维护自己的文件数据缓存,独立的内核页或缓冲区高速缓存,使得应用可以直接链接客户端访问。
logo

文件IO和权限

当一个进程打开一个文件时,客户端发送一个请求到MDS集群。一个MDS遍历文件系统层次结构将文件名翻译成文件索引节点,该索引节点包含唯一的索引编号,文件持有者,模式,大小和关于如何将文件数据映射到对象的条带策略信息。MDS或许会询问客户端指定哪些操作是被允许的权限(如果没有指定的)。权限目前包含4位控制:客户端的读,缓存中读,写,和缓冲区写入。未来将会添加安全密匙权限需要客户端向OSDs证明他们有权限读写数据(目前原型认可所有客户端)[13,19]。后续MDS关于文件IO的工作仅限于保持文件一致性和实现正确语义的管理权限。

ceph概括了一些将文件数据映射到一系列对象上的条带化策略。为了避免文件分配元数据的使用,对象名字简单的取为文件索引编号+条带编号。通过使用全球知名的映射函数CRUSH将对象副本分配给OSDs。比如,如果一个或多个客户端打开一个文件进行读访问,一个MDS授予他们读和缓存文件的内容的权限。客户端利用索引节点编号,布局和文件大小能够命名对象和找到所有包含文件数据的对象,并且从OSD集群中直接读。任何不存在的对象和字节范围都被定义为文件空洞或零。类似地,如果一个客户端打开一个用于写的文件,它是被授权有缓冲的写权限,它产生在文件里任何偏移上的所有数据简单地被写入到合适的OSD中的合适的对象上。客户端放弃了文件关闭的权限并提供给MDS最大的文件大小(最大的写偏移),该大小重新定义了对象集是否存在文件数据以及包含文件数据多少。

客户端同步

POSIX语义符合实际地要求读必须是先前写过的数据,并且写是原子性(重叠和并发写的结果将反应在发生的特定顺序)。当一个文件被多个客户端打开同时写或者部分读部分写时,MDS将会撤销先前授权的读缓存或写缓冲的权限,强制客户端的文件IO保持同步。也就是说每个应用的读写操作会被堵塞直到被OSD承认,这样在OSD存储每个对象的同时高效地分摊了更新序列化和同步的负担。当写跨越对象边界时,客户端获得受影响对象(由各自的OSD提供)的独占锁,并立即提交写操作和解锁操作来实现所需要的序列化。对象锁类似地通过获取锁和异步刷新数据来屏蔽大写的延迟。

毫不惊讶的是异步IO是应用的性能杀手,尤其是那些小读和小写,由于至少是一个OSD的来回带来的延迟惩罚。虽然读写共享在通用的工作负载中相对少见,在科学计算中更加普遍,往往性能很关键。由于这个原因,经常需要在应用程序不需要严格标准一致性时放松一致性要求。虽然ceph支持通过全局切换实现放松一致性,并且很多其他的分布式文件系统在这个问题上冒险,这是一个不确定和不令人满意的解决方法:要么性能损失或者系统范围的丢失一致性。

正是这个原因,高性能计算组织提出了一组针对POSIX IO接口的高性能计算扩展集合,这个扩展子集是由ceph实现的。最值得注意的是,这其中就包括一个O_LAZY标志开放允许应用程序明确放宽共享写文件普通的一致性要求。当IO不是同步运行的时候,性能敏感的应用程序管理自身一致性(HPC负载中常见的模式是通过写同一文件的不同部分)允许缓冲写和缓存读。如果需要的话,应用程序可以使用另外两个系统调用明确同步:lazyio_propagate将在对象存储中刷新一个给定的字节范围,lazyio_synchronize将确保先前的传递会影响后续的读。ceph同步模型在同步IO的客户端之间提供正确的读写和共享写语义保留了简洁性,并且扩展了应用接口来放松性能敏感的分布式应用的一致性。

命名空间操作

客户端和文件系统的命名空间的交互是由元数据服务器集群管理。读操作(e.g.,readdir,stat)和更新(e.g.,unlink,chmod)是被MDS同步以后采用来确保序列化,一致性,正确的安保和安全性。为了简化,没有元数据锁和租赁权限给客户端。特别对于HPC的负载,回调提供了潜在较高复杂度开销的最小上界。

相反,ceph优化了最常见的元数据访问情形。一个readdir随后跟着每个文件的stat(e.g.,ls -l)是极为常见的访问模式并且是众所周知的性能杀手。ceph中的readdir只需要单个MDS请求就可以获取包含索引节点内容的整个目录。默认情况下,如果一个readdir后面紧接着一个或多个stat,简要的缓存信息被返回,否则这些信息被丢弃。虽然稍微放松一致性导致中间的索引节点修改被忽视,为了提高性能我们乐意这个交易。这个行为明确的被readdirplus扩展解决,能够返回目录项的lstat结果(就像一些具体的OS实现的getdir功能)。

ceph能够允许一致性通过缓存元数据进一步放宽,很像早期版本的NFS通常缓存30秒。然而这个方法在某种程度上破坏了对于应用很关键的连贯性,比如那些使用stat去决定一个文件是否更新的应用–它们要么表现不正确要么等待旧的缓存数据超时。

我们没有选择再次提供正确的行为和扩展实例的接口,因为这可能反过来影响性能。当多个客户端打开同一文件进行stat操作时很清楚地描述了这个选择。为了返回正确的文件大小和修改时间,MDS撤销所有写权限,暂时停止更新并且搜集写入者中最新的文件大小和修改时间。最高值随着stat回复被返回并且权限被发布允许进一步的进展。虽然停止多个写入者或许看起来很剧烈,但是有必要确保序列化。(对于单个写入者。一个正确的值可以无需打断进程的被写入客户端修复。)没有必要连贯性的应用(POSIX接口没有对准它们的需要的受害者)可以使用statlite,statlite采用一位掩码指定不需要保持一致的索引节点域。

动态地分布式元数据

元数据操作往往占了一半的文件系统的工作负载并且处在关键路径,使得MDS集群对于整体性能至关重要。分布式文件系统中的元数据管理也呈现出紧张的扩展挑战: 虽然随着更多的存储设备的规模化,容量和总的IO率能够任意扩张,但是元数据的更大程度的依存关系将使得一致性和连贯性的扩展更加困难。

ceph中的文件和目录的元数据是很小的,几乎全部由目录项(文件名)和索引节点(80 bytes)组成。传统的文件系统中的文件分配元数据没有必要,因为对象名是由索引节点编号构造,使用CRUSH函数分配给OSDs。这个简化了元数据的工作负载并且允许MDS高效地管理很大的工作文件集,而不是文件的大小。我们设计通过两个分层的存储策略进一步寻求最小化和磁盘IO相关的元数据,以及通过使用动态子树分区最大化局部性和缓存效率。

元数据存储

虽然MDS集群目标在于使用内存缓存满足大多数请求,但是为了安全,元数据更新一定得提交到磁盘。一组大的,有界的懒惰地冲刷日志允许MDS用高效和分布式的方式将更新的元数据流到OSD集群中。包含多个百兆字节的单个MDS日志也反复吸收(大多数通用负载)元数据更新以至于当旧日志项被最终冲刷到长期存储中时,很多已经过时。虽然MDS的恢复功能还没有被实现,日志被设计成在一个MDS故障时,其他的节点可以迅速重新扫描日志来恢复错误节点中的内存缓存(用于快速启动)中关键内容,这样一来恢复了文件系统的状态。

这个策略提供了两全其美: 流更新到硬盘是一个高效且串行的方式,大大减少了重写的工作负载,使得长期的磁盘存储布局可以得到优化来方便未来的读。特别的,索引节点嵌入到目录中能允许MDS预取整个目录用于单个OSD读请求和利用在大多数负载中存在的高度目录局部性。每个目录的内容使用和元数据日志以及文件数据一样的条带化和分布式策略写入OSD集群中。索引节点编号按范围分配到MDS,在我们原型中被认为是不变的,在未来,它们可能在文件删除时被很细的重排。辅助锚表维护罕见的有多个硬连接的索引节点,这些硬连接都可以按索引节点编号全局寻找到,而不会有一个巨大的,稀疏的且繁琐的索引节点表来拖累单链接文件。

动态子树分区

我们的主拷贝缓存策略使得每个权威的MDS都有责任管理缓存连贯性和任意片段元数据的串行更新。尽管大多数存在的分布式文件系统采用静态子树划分来下放这个权力(通常强制管理员将数据集雕刻成更小的静态卷),最近一些实验室的文件系统已经使用Hash函数来分布目录和文件元数据,却牺牲了负载分布的局部性。这两种方法都有关键的限制: 静态子树划分未能处理动态负载和动态数据集,而Hash破坏了元数据的局部性和元数据高效预取和存储的关键机会。

logo

ceph的MDS集群是基于动态子树分区策略,能够自适应地在一组节点中分配分层的缓存元数据。每个MDS使用指数时间衰减的计数器来测量目录层次中的元数据的受欢迎度。任何操作都增加受影响索引节点和它到根目录所有祖先的计数器,提供每个MDS一棵描述最近负载分配的权重树。MDS负载值周期性地被比较,适当大小的目录层次子树被迁移来保持负载均衡。共享的长期存储和精心构造的命名空间锁的组合使得这种内存缓存适当内容迁移到新权威对连贯性锁和客户端权限影响最小。为了安全,导入的元数据被写入到新的MDS日志,同时两端附加的日志项确保权威的转换是不受中间错误的破坏(类似于两阶段提交)。基于子树分区的结果是最小化了前缀副本的开销并且维护了局部性。

当元数据在多个MDS节点中复制时,索引节点内容被划分到三组,每组有不同的一致性语义: 安全(持有者,模式),文件(大小,修改时间)和不可变的(索引节点编号,改变时间,布局)。虽然不可变的领域是永远不会改变,安全和文件锁是由独立的有限状态机约束,每个不同的状态集和过渡设计都能容纳不同的访问和更新模式,同时最小化锁争用。比如,路径遍历时需要持有者和模式安全检查但是很少改变,因此需要很少的状态,而文件锁反映更广泛的客户端访问模式。因为它有管理MDS发布客户端权限的能力。

流量控制

在多个节点之间划分多个目录层次能够平衡广泛的工作负载,但是不能总是解决热点和多个客户端访问同一目录和文件导致的瞬间拥塞。当热点需要和不会引起一般情况下的相关开销和目录局部性的损失,ceph使用受欢迎度的知识来提供更宽的分布。经常被读目录的内容(e.g.,many opens)会被多个节点选择性地复制来负载均衡。特别大的目录或者经历相当大的写负载时(e.g.,many file creations)会按照包含的文件名在集群中映射,以损失目录局部性为代价实现负载均衡。这个自适应方法允许ceph涵盖更广的分区粒度,捕捉到文件系统中具体情况和分配的粗细分区的好处。

每个MDS响应时提供给客户端授权的更新信息和相关的索引节点的所有副本和它的祖先,允许客户端知道与它们交互的文件系统的部分元数据分区。未来的元数据操作直接针对权威(对于更新)或者基于给定路径最深的已知前缀随机副本(对于读)。通常客户端知道不受欢迎的元数据位置并且能够直接和适当的MDS联系。然而,客户端访问流行的元数据时会被告知元数据驻留在不同的或多个MDS节点上,有效地限定了客户端相信特定元数据片段驻留在特定MDS的数量,在它们并发之前就已经分散了潜在的热点和瞬间的拥塞。