/ 中存储网

云存储的黑暗面:元数据保障研究

2015-08-30 14:33:29 来源:分布式系统架构知识微信号

本文主旨并非阐述一个可用的架构设计,而是展示出对象存储的元数据保障中将会遇到的问题。问题往往比答案更重要。多数问题只要认识清楚,解决并非难事。但需要注意的是,没有哪个问题存在十全十美的彻底解决方案。我们所采取的策略更多的是将一个关键性问题转化成另一个次要和易于处理的问题,或者将一个问题发生的可能性尽可能降低。很多问题是无法彻底解决的,我们的现实目标是将其发生的概率下降到业务可接受的范围。因此,希望大家能够更多地关注和发掘所存在的问题,而不是紧盯方案和设计。我们相信,认清了问题之后,找到解决办法并非难事。

在开始讨论元数据保障的问题之前,我们先要牢牢记住一句话:“任何事物都会出错。”这个真理时时刻刻在发挥着惊人的作用。

元数据

元数据系统的好坏关系到整个对象存储系统的可靠性、可用性和一致性,并且会影响到性能。因此,元数据部分是对象存储系统的核心,也是架构和保障的重中之重。

元数据最基本的作用在于数据对象的定位。对象存储的任务是保存用户提交的数据对象,并以用户指定的名称(Key)对其标识。用户如需获取一个数据对象,要向对象存储系统提交Key,存储系统便会根据Key找到相应的数据对象,然后反馈给用户。存储系统根据Key找到数据对象存放位置的过程,便是依托元数据完成的。图1是元数据方案架构图。

云存储的黑暗面:元数据保障

元数据的核心内容是Key=>Pos(存储位置)的映射,本质上是一个Map。此外,元数据还承载了数据对象归属(容器和用户)、大小、校验值等元信息,用于记账、校对、修复和分析等辅助操作。

说到这里,大家或许会有疑问:现今的存储系统都流行去中心化,设法去掉元数据,为何还要谈元数据的保障?原因其实很简单,当今各种去中心化的存储设计,在云存储中并不适用。其中原因颇为繁复,已超出本文范畴,这里只做简单说明。

去中心化的方案利用算法确定数据对象的位置。最常见的算法是一致性哈希。这些方案的问题在于,如果仅考虑数据对象的存取,逻辑很简单,实现上也简单。但如果考虑到可靠性、可用性、一致性,以及运维,就会变得复杂。

数据对象的存储副本总会损坏和丢失。一旦丢失,需要尽快修复。最极端的情况是某一磁盘失效,进而造成大量的数据副本需要修复。去中心化方案将数据对象的副本同磁盘一一对应起来,结果就是修复的数据只能灌入一块磁盘。单块磁盘的吞吐能力有限,从而造成一块磁盘的数据需要近一天的时间方能修复。而在大型存储系统中,这段时间内可能会有更多的磁盘发生损坏,因而可能造成数据的丢失。

同样因为数据对象与服务器和磁盘绑定,一旦一台服务器下线,便会降低相应对象的存取可用性。有些方案临时将数据对象映射到其他服务器,从而提高可用性,但临时状态的管理、其间的异常处理等问题的复杂性远远超出了去中心化所带来的便利。

当存储系统扩容时,去中心化方案需要在服务器之间迁移大量的数据,以求各磁盘的可用容量达到平衡。迁移规模随扩容规模增大而增加。迁移过程消耗系统资源,并且存在中间状态,使得数据存取逻辑复杂化。而且漫长的迁移过程中一旦发生异常,其处理过程相当繁复。

在一致性方面,去中心化方案碍于副本数较少,在某些特定情况下会造成一致性问题,而且难以修复。关于这一点,后面会具体论述。

对于list操作、数据校验、记账等辅助操作,各种去中心化的方案都很不友好。因为数据量庞大,无法通过遍历所有数据计算用量、分析统计。因此,一些去中心化方案会设置数据对象和用户的关联数据库,以方便使用量计算。这些数据库实际上就是一种元数据库,同样面临着元数据保障的问题。

去中心化方案的诸多问题并非无法解决,总能找到合适的方法应对。但其实这只是将压力转移到运维阶段,使得运维成为瓶颈。我们说存储系统是“三分研发,七分运维”,运维是重中之重,无论从设计还是研发角度,都应当以方便运维,减少运维压力为先。表1中对比了去中心化方案和元数据方案。

云存储的黑暗面:元数据保障

元数据的特性

让我们回到元数据上来。元数据相对于数据对象本身,总量较少。根据统计,元数据和数据对象的大小之比大体在1:100到1:10000之间,具体的比率,取决于数据对象的平均大小。因此,元数据相比数据对象本身更加容易操作和处理。

当用户索取数据对象时,存储系统需要检索元数据库,找到相应的元数据条目,获得存储信息后,进行读取。因此,元数据的操作是查询操作,也就是数据库操作。正因为元数据的量较少,并且都是结构化数据,使得这种检索得以方便地实现。

即便如此,当存储量达到几十PB级别时,元数据的数据量也将达到几十上百TB的级别。这已远超出单台服务器的存储能力。因此,元数据库天生便是一个分布式集群。在分布式数据库集群中,很难维持复杂的数据结构,特别是在可靠性、可用性、一致性,以及性能的约束下,同时满足这些要求是极难做到的。在动辄数以PB计的云存储系统中,只得维持最简单的对象存储形态,这也就是大型的云存储系统不支持文件系统的原因。

元数据的重要性在于它是整个数据存储的基准:整个系统拥有哪些数据对象,归属哪些容器、哪些用户。一个数据对象,元数据说有,就有了;元数据说没有,就没有了。所以,元数据的可靠性代表着存储系统的可靠性。数据校验、空间回收等操作都依赖于元数据。元数据一旦有所差错,必然造成整个数据存储的错失和混乱。

同时,几乎所有操作都需要操作元数据,或读取,或写入,是存储系统的关键路径。如果元数据系统下线,那么整个存储系统就会宕机。因此,元数据部分的可用性决定了存储系统的可用性。

性能方面,几乎所有的操作都涉及到元数据,整个元数据系统的访问压力远远超过存储系统。因而,元数据的性能决定了存储系统的整体响应。

元数据的一致性也决定了存储系统的一致性。一致性决定了用户在不同时间对同一个对象访问是否得到同样的结果。当一致性发生问题时,用户在不同的时间可能获得某个对象的不同版本,甚至无法获得有效的对象。这些违背用户预期的情况往往造成用户业务逻辑的混乱,引发不可预期的结果。而在某些特定的情况下,一致性的错乱会增加数据丢失的可能性。

作为存储系统的枢纽,元数据占到整个系统大部分的维护工作量。元数据系统可维护性上的提升对于整体的运维有莫大的帮助。

元数据保障的真正困难在于上述这些特性的平衡和协调。这些特性之间有时会相互补充,但更多的时候会相互矛盾和冲突。试图解决其中一个方面的问题,可能导致其他方面受到损害。解决问题的过程充满了大量的折中手段。

接下来具体说一说云存储元数据保障所面临的问题和一般应对手段。

主从模型

首先我们必须解决可靠性和可用性问题,尽可能保证保存下来的元数据不丢失,也尽可能让服务始终在线。这里有一个关键点:怎样才算保存下来?这个问题看似简单,实则至关重要。一般我们会认为所谓保存下来就是存储到磁盘或其他持久存储设备上。但在一个在线系统中无法以此界定。在一个在线服务中,数据完成保存是指明确向客户端反馈数据已经正确保存。一旦向客户端发出这样的反馈,那么便是承诺数据已经保存下来。在这个界定之下,元数据的可靠性保障则颇为复杂。

前面说过,元数据系统是一个数据库系统,保障数据库系统可靠性最常用手段就是主从模型(图2),即读写主数据库,而主数据库将数据复制到从数据库,从而确保数据库没有单点。主从模型同时也保障了可用性,当主服务器下线时,从服务器可以切换成主服务器,继续提供服务。这样的保障手段对于一般的在线应用大体上够用了,但对于云存储的元数据而言,则存在诸多问题。其中缘由颇为复杂,让我们从最基本的可靠性开始。

云存储的黑暗面:元数据保障

主从模型是依靠主服务器向从服务器复制每次写入的数据,以确保数据存留不止一份。但这里有个同步和异步的问题。所谓“异步”是指主服务器完成数据写入之后,即刻向客户端反馈写入成功信息,然后在适当的时候(通常都是尽快)向从服务器复制数据。客户端得到保存完成的信息后便欢欢喜喜做后面的事情去了。而此刻相应的数据只有主服务器上保存着,如果发生磁盘损坏之类的问题,便会丢失那些尚未来得及复制到从服务器的数据。之后如果客户端来提取这条数据,服务端却无法给出,对存储系统而言,是很丢人的事。

即便没有发生硬件损坏的情况,依然可能丢失数据。由于硬盘之类的存储介质速度缓慢,无法跟上数据访问的节奏,所以数据库都会使用内存进行缓存。当服务器发生掉电或者崩溃,数据库发生非正常的退出时,都会造成缓存中没有写入磁盘的数据丢失,进而造成数据的丢失。

在存储系统长期的运行过程中,硬件肯定会坏,掉电必然会发生,系统也难保不会崩溃。所以这种异步的数据复制过程必然会有数据的丢失。这对于在线存储服务而言是无法容忍的。

既然异步存在这样的问题,那么同步地复制数据是否会好些呢?所谓“同步”是指主服务器完成数据写入后,并不立即反馈用户,而是先将数据复制到从服务器。等到从服务器正确写入,再向用户反馈数据写入完成。这样,任何时刻都会至少有两台服务器拥有某一条数据,即便一台服务器出了问题,也可以确保数据还在。如果有多台从服务器自然就万无一失了。

同步模式解决了可靠性问题,却引入了新问题。最基本的问题就是同步的数据复制造成延迟的增加:主服务器需要等待从服务器完成写入才能反馈。

让我们先放下性能问题,毕竟在线对象存储对于主从同步的延迟还没有那么敏感。剩下的问题则是根本性的。同步模式解决了可靠性问题,下一步需要考虑可用性问题。对于在线存储服务而言,必须要确保系统不受单点故障的影响。同步模式则恰恰受制于此。为了确保可靠性,必须主从服务器都写入成功,才能向用户反馈成功。如果从服务器下线,那么这个条件就被破坏,不得不向用户反馈失败,直到从服务器重新恢复。而当主服务器下线后,从服务器则设法升级为主服务器继续服务。但此时只有一台服务器,也不能满足数据不少于两份的要求。如果允许主从之间容忍从服务器的下线,那么可靠性又受到了削弱。这样产生了令人啼笑皆非的结果,主从模型是为了提高可靠性和可用性,现在反到受制于单点故障。

解决这一问题的方法也不是很难:增加从服务器的数量,并且允许主从复制失败。如此,当一台从服务器下线后,其他从服务器依然可以接收数据,确保任何时刻一份数据都至少拥有两个以上的副本,以维持可靠性。不过,具体操作上并没有那么简单,主从之间复制成功的数量必须满足一个阈值。这个阈值也必须满足一个条件才能保证数据一致性,关于这个要点后面会具体阐述。

又有新的问题出现。当主服务器下线时,一台从服务器便会被升级成为主服务器。但前面说了,为了可用性,并不要求主从服务器间的复制每次都成功,那么新的主服务器的数据可能是不完整的。这样对于用户而言,便会出现数据一致性的问题:原先成功写入的数据却读不到了,或者读到的都是很旧的版本,已被覆盖掉的东西。给用户造成的印象便是数据丢失了,这是严重的问题。

最直观的解决途径是设法补上那些数据。但考虑到元数据的量,这样的修补是无法在短期内完成的。另一个更复杂却真正有效的方法是读取数据时,同时读所有的服务器,不管主从。然后将所得的结果整合在一起,找出正确的版本。

云存储的黑暗面:元数据保障

事情还没有完结,存放在数据库中的数据经年累月地运行之后,会逐步退化。磁盘的失效,磁道的损坏,乃至人为的操作失误,都可能造成数据副本的丢失。换句话说,即便是主服务器,我们也无法完全保证数据的完整性和一致性。无论如何都无法回避主从服务器之间的一致性问题。

无论是主服务器,还是从服务器,终究需要对缺失的数据副本进行修补,否则数据的退化终究会造成数据的遗失。这种修补的过程也是困难重重,后面会进一步分析。

最后,最重要的就是运维。架构、设计、开发之类的都是短期过程,虽说系统需要不断演进,但毕竟都是阶段性的工作。而运维,则是持久的、不间断的任务。一个在线服务的正常运行最终还要依赖运维,运维决定了服务的品质。因此,一个设计方案的可维护性具有很高的优先级。

在可维护性方面,主从模型除了常规的系统维护事务外,还有很多额外的运维过程。主服务器下线,需要将某一台从服务器切换为主服务器,这个过程是一个运维过程。理论上,一个主从集群可以实现自动切换。但实际使用中,这种切换并不能确保成功,往往需要人为干预,切换失败也是常有的事。对于计划中的下线,如升级软硬件等,运维强度尚不算大,但对于软硬件故障造成的突发下线,主从切换往往是紧急运维事件。一旦失败,便是服务下线的大事故。其中充满了风险和不可控因素,并不利于存储服务的高可用。

一个在线系统的架构,最好的情况就是无论是正常运行,还是发生意外故障,都能够以同一种方式运行,无须额外的应急处理。用通俗的话说,在线系统必须能够不动声色地扛住局部异常,为运维人员赢得回旋处理的余地。对于云存储的元数据系统尤是如此。元数据位居关键路径,又存在持久化的状态,还受到各种特性要求的约束,它的架构着实是一项富于挑战的任务。

多副本模型

下面我们考查一个模型,暂且简单地称其为多副本模型。因为这种模型,就是利用众多元数据副本来保证可靠性和可用性的。与主从模型不同的是,多副本模型的各副本之间没有主次之分,所有的副本都处于相同的地位。因而,在向多副本模型写入元数据时,是同时向这些副本发起写入。而读取元数据时,也是向这些副本同时读(图1)。

云存储的黑暗面:元数据保障

图1 多副本模型

多副本模型基于这样一个简单而直观的思路:单点会造成数据丢失,并引发可用性问题,那么就将数据同时写入多个服务器,以防单点的出现。不同于主从模型中,读写都针对一台主服务器,从服务器只是间接地参与可靠性和可用性的保障,多副本模型的每台服务器任何时刻都在直接发挥着保障作用。

由于每次读写都施加在所有的副本服务器上,任何时刻都有不止一份数据被保存下来,所以可靠性自然就解决了。同样,任何时刻都有不止一台服务器在运行,它们同时下线的可能性极小(暂且不考虑机房级别的整体故障),所以可用性也无需额外措施,便可以得到保障。正是因为每台副本服务器都是平等的,所以任何一台服务器下线都不会对整个集群的可用性和可靠性产生影响,系统仍然依照正常情况下的方式运行,别无二致。

这是多副本模型相对于主从模型的最大优势。无论哪台服务器下线,都不会引起系统运行状态的变化,运维人员可以从容地进行善后处理,没有服务下线的顾虑。而软硬件升级之类的日常维护工作,也不需要大动干戈地进行主从切换之类的危险操作,直接将服务器下线,进行更新便可。多副本模型的系统甚至可以容忍多台服务器下线,而不会造成系统运行的波动。具体的容忍限度取决于配置设定,关于这一点,后文有详细论述。

任何事物都有两面,既然多副本模型有它的优点,自然也有缺点。多副本的问题主要在一致性方面。由于我们允许副本服务器写入失败,再加上各种原因造成的数据退化,所以副本服务器之间的数据会不一样。当我们读取的时候以哪个为准呢?

这是一个值得考虑的问题。我们无法确定哪台副本服务器包含了完整新鲜的数据。实际上不可能有这样的服务器存在。因而我们也就无法从任何一台服务器中准确无误地读出所需的数据。唯一的办法就是同时读所有的副本服务器,综合所得的副本数据,以获得所需的信息。

如何综合副本数据呢?首先要确定基准。基准就是判定有效数据的标准,有时间基准和空间基准之分。时间基准用来处理元数据的先后覆盖,而空间基准用于处理副本之间的对应关系。在描述如何确定基准之前,先定下这样一个准则:对于一个数据对象的元数据,只有时间最近的那条是有效的。有了这样的准则,建立基准就容易了。我们可以为每一次元数据写入加上一个时间戳,并且确保一次元数据写入的各个副本拥有相同的时间戳,同时还需保证历次写入的元数据拥有不同的时间戳。只要能确保这两个条件,数据的基准就容易确立了。

具体的方法并不复杂,将读取到的副本数据放在一起比对,时间戳上最近的那些副本,就是所需要的。但并非所有的最大时间戳都是有效的。假设这样一种情况:一个元数据系统有5个副本,一次写入时,由于种种原因,只有两个副本写成功了。在读取这条元数据时,恰好那两台写成功的副本服务器下了线。这种情况下,这条元数据就无法读到了。其结果就是元数据不见了,直到那两台服务器重新上线。

为了避免这种情况的出现,我们需要引入一个规则,以确保无论怎样都能读到正确的数据。

这样的方法有不少,最常用的是WRN算法(图2)。这种算法比较简单,容易实现,使用也较广。算法的具体操作如下。

云存储的黑暗面:元数据保障

图2 WRN算法

假设有N个副本,当需要访问数据时,同时写或者读所有副本。如果写入时,有超过W个副本写成功,那么就认为这次写入是成功的,否则就算失败,向客户端反馈写入失败。读取时也一样,如果有超过R个副本读取成功,就认为这次读取是成功的,否则就算失败,或者数据不存在(具体是失败,还是不存在,需根据副本读取的结果加以判别)。W和R必须满足一个条件:W+R>N。只要满足这个条件,成功写和成功读的副本之间必然存在重叠,因而肯定可以读到至少一个有效的数据。

在具体使用中,WRN算法还有不少需要注意的细节。首先,具体读取各副本时,最简单的策略是取得该副本服务器中最新的那条元数据,然后依靠WRN算法整合这些所得的数据。这种做法在一般情况下可以正常工作,但在一些异常情况中会存在一致性问题。一种情况是一次写入时,没有满足成功写入W份副本的条件,那么这次写入算作失败。但其中写成功的副本因为时间戳更近,读取该副本时,会覆盖先前成功写入的那些副本。于是,最新写入失败的那个元数据读取时没有达到足够数量,而先前成功写入的元数据因为某些副本被这次失败的残留副本遮盖,也无法达到R数。于是便会出现无法取得有效元数据的情况。

这种情况属于低概率事件,但云存储系统经年累月不间断地运行,任何低概率的事件都会发生,而且必然发生。一旦发生,便会引发数据错乱的情况,影响用户的使用和服务的声誉。

针对这样的问题,有一些解决手段。最基本的手段是对于失败的写入操作,将各副本进行回滚。也就是将那些已成功写入的元数据条目副本删除。这种做法可以在一定程度上有效地降低问题发生的概率。但基于“任何东西都会出错”这样一个事实,我们认为回滚也会失败。回滚失败意味着依然会发生元数据无效的问题。

另外,还有一个解决方法:读取副本时不是只读最近的那一条元数据,而是读出几条。把各副本读到的元数据根据时间戳相互对位,还原出最原始的写入状态。在此基础上,剔除那些失败的写入数据,得到正确的数据。

还原写入历史的操作要求元数据在数据库中采用只增的方式,以便保留历史的条目。每次元数据的写访问,无论是新增还是覆盖,都是增加一条记录。与原有的元数据记录不同的是,这条记录携带新的时间戳。对于删除操作,则同样增加一条数据,并且设置记录中的“删除”标志,系统将根据此标志判断记录的删除操作。(只增也有助于避免事务,减少数据库锁的使用,对提升性能有所帮助。)

但与任何一种问题解决手段一样,这种还原历史操作的方法也不能彻底消除一致性的问题,原因是数据退化。假设一次写入正好成功了W个副本,那么这次写入尽管成功了,但仍处于临界状态。如果有一个原先成功的副本发生了丢失,那么之后这次写入将会被认为是失败的,而被忽略。尽管这是更加鲜有的情况,但确实会发生,而且无法直接消除。一般情况下,我们也只能容忍这种情况的存在,毕竟发生的概率已经非常非常小了。

一些辅助手段可以进一步减少这类问题发生的可能性。如果一次写入的成功数超过了W,但依旧有少量失败,那么要实时地对这些失败写入进行修复,例如重试,或者使用异步的重试队列对失败进行修复。这种手段和失败回滚一起可以很有效地减少容易产生混淆的临界状态的存在。

失败修复和回滚之类的错误处理手段实际上并非很多人认为的那么有效,错误处理本身也会失败。如果试图以错误处理消除问题的隐患,那么必须也要对错误处理的失败情况加以处理。于是,就会有错误处理的错误处理,错误处理的错误处理的错误处理……如此便无穷无尽,这自然不是解决问题之道。错误处理的作用是减小问题发生的概率,最终将其减小到一个可以接受的范围。

但考虑到数据退化,仅有简单的修复是不够的。一则错误处理不可能永远成功,二则并非每一次数据丢失都会被感知。我们需要一个最终的一致性修复手段,这个手段可以弥补其他异常处理失败造成的问题。但这样的手段也会失败,为了能够最终确保错误得到修复,这种手段的失败处理方式就是它自己。一般情况下,最终手段不需要有很强的实时性要求,它的任务在于应对那些概率很低的情况。在对象存储系统中,这个手段就是定期合成元数据快照。

元数据快照的做法是这样的:定期导出所有副本的元数据,按照时间戳匹配各条元数据,然后依照WRN算法逐条将所有副本整合,抛弃那些失败的残留数据,剔除被覆盖的元数据。最终生成一个完整一致的快照。然后再重新载入元数据库,替换快照所涵盖的数据。

快照生成的主要问题是时间。元数据的量为数不少,通常都会在TB级别之上。对这样的数据规模进行匹配和计算,往往需要在一个集群中以Map-Reduce处理。考虑到成本,这样的集群规模不可能很大,因而处理的时间将会比较长,可能达数小时。而且,数据的导出和加载也需要较长的时间。同时,作为一个定期的运维过程,如何自动化,减少人力参与也是非常重要的。总体上,如何快速并且自动地合成快照是颇有挑战的任务。

快照还有一些额外的作用,包括:生成每个容器的数据量、对象数等统计信息;生成每块硬盘的对象清单,用于磁盘失效后的数据恢复和数据对象的校对。

快照的生成周期通常设置在一天。那么一条元数据的不一致情况最多只会存在一天,一天之后,该条元数据的所有副本又会恢复到一致的状态。而只要一天内没有发生多台服务器同时丢失数据的情况,元数据的可靠性便可以得到很好的保障。我们相信一天内2台以上服务器发生数据丢失的可能性极小。反倒是人为的操作失误更容易造成这类事故。而这就是另一个问题了,不在本文的讨论范围内。

除了上述这些奇妙问题之外,还有一个在WRN理论中没有被提及的问题。多数涉及WRN的论文都说这个算法可以保证强一致性,但实际上这是错的。WRN算法如果要保证强一致性需要有一个条件,就是对每一个副本的写入要么成功,要么失败。在实际的使用过程中,副本的写入却有第三种状态。

对于一个副本的写入必然是跨越网络的。于是,一次副本写入实际包含三个步骤:向副本服务器发送请求;副本服务器写入数据库;反馈写入的结果。如果前两个步骤发生错误,那么这次副本写入操作就是完完全全的失败。但如果前两个步骤都成功了,但第三个步骤出现了问题,如网络连接中断,或者数据报文丢失等,客户端会认为这次操作是失败的。但实际上这条数据已正确地写入了数据库。如果此时反馈信息恰巧达不到W数,那么就会回复用户这次元数据写入失败。而实际数据库中,成功写入的副本数却是满足W数的。在下一次读取这个元数据时,却读到了被认为失败的元数据。也就是说,外界认为一个不存在的元数据,在系统中却是真实存在的。这种一致性问题同样是无法消除的。失败回滚操作可以减少问题的发生概率,但终究无法彻底杜绝。

整体上,多副本模型轻而易举地解决了可靠性和可用性,却将问题集中到一致性上。一致性问题有很多既定的解决手段,每一种手段都有各自的问题,无法彻底将一致性问题解决。但综合这些手段,可以很好地将一致性提升到近似的强一致性级别,从而满足实际的需要。

WRN算法在具体使用中,N、W、R的选择颇有些讲究。首先,N数不能太少。我们来看几种不同的WRN设置:

N=3,W=2,R=2,W+R-N=1

N=6,W=4,R=4,W+R-N=2

N=9,W=6,R=6,W+R-N=3

这三种配置都满足W+R>N。但效果却完全不同。对于N=3的配置,只能容忍1台服务器下线。另外一个更麻烦的问题是,尽管写入时有两个副本成功了,一个副本失败,这算是成功的。但如果其中一个成功写入的副本发生了丢失,这时候残存的只有一个副本,那么我们在读取时,实际上无法判别这一个副本是写入失败残存的副本,还是副本退化造成的。N=3时,这种混淆很容易发生。

而随着N数的增大,所能够容忍的副本下线和副本退化的数量就多了。在既定时间段内,同时下线多个副本,或者同时丢失多个副本的概率就小很多了。因此,从一致性保障角度而言,N数越大越好。当然,考虑到资源,N数终究不可能无限多,一般6-9是比较容易接受的数字。尽管副本数如此多,但毕竟元数据相对数据存储本身有着多个数量级的差异,所占用的服务器资源毕竟还是少数。

在这里,我们可以看到那些去中心化方案在一致性保障方面的缺陷:因为数据本身的量较大,所以考虑到成本,副本数不会太多,通常会选择N=3。很明显,3这个数字对于一致性保障而言还是相当脆弱的。

至于W和R的具体数值,一般没有太多的教条,但它们受制于存储系统的部署。通常,对于云存储系统而言,为了保障尽可能高的可靠性和可用性,会设法将存储集群按照副本分散到不同的子集群中。每个子集群拥有独立的交换机、独立的配电线路,最好在物理位置上也相互独立。最极端的情况就是分布在不同的IDC中(当然不是所有的云计算服务商都有这样的资源)。这样的子集群通常称为zone。设置这样的zone的目的是为了消除在基础设施上的单点故障。

在这样的部署结构下,W和R的选择应当确保在一个zone里的副本服务器数量不大于W+R-N。这样的设置,是为了保证一个zone下线后,系统依然有足够的副本服务器满足WRN算法。

主从模型vs.多副本模型

从前面关于主从模型和多副本模型的介绍可以看到两者的差异。主从模型中,主服务器必然会由于各种原因而下线。一旦主服务器下线,那么就必须即刻采取行动,将从服务器切换成主服务器,保证系统依旧在线。但这种切换操作同样也会失败,而且失败的可能性并不小。

多副本模型更易于保障可靠性和可用性。在多副本模型下,一个成功写入的元数据任何时刻都会有多个副本存在,任何时刻都会有多个实例在提供服务。服务器单点故障不会对元数据系统的运行产生影响,整个系统还是按照正常的运作方式运行。这一点非常重要,这意味着可以将一个实时的在线运维事件变成了可以暂缓处理的离线运维事件。任何一台元数据服务器出现故障,也不需要立刻做出反应。系统继续正常运行,运维人员则可以从容地处理问题。这是确保高可用性的关键所在。

主从模型本可以简化程序的结构,因为客户端只需要向主服务器发出请求,访问元数据。但如同前面所描述的,可靠性、可用性和一致性的要求促使主从之间进行并发访问,以弥补这些特性方面的漏洞。这实际上就是将原本在元数据系统之外的多副本访问逻辑移到主服务器中,却凭空增加了延迟,以及主从切换的麻烦。

在一致性方面,主从模型在没有考虑软硬件异常和数据退化的情况下,基本上没有什么难点。但因为现实中没有软硬件异常是不可能的,数据退化也是时刻在发生的。于是,无论主、从服务器都可能缺少数据,更无法单纯通过主服务器来维持数据的一致性。为了获得尽可能高的一致性,主从模型也需要通过整合主从副本来实现一致性保障,而副本修复也同样是不可缺少的一部分。实际上,无论是主还是从,都应当看作是一个元数据的副本,既然是多个副本,那么也就存在一致性的问题。多副本遇到的问题,主从模型同样也会遇到,这就是主从模型不如多副本模型来得合用的原因。

主从模型的主要优势是可以执行复杂的数据库逻辑,如关联、聚合等。但这在多副本模型下非常难。主从模型下,一个复杂的查询得到执行后,将幂等的结果数据同步到从服务器。而对多副本模型来说,每个副本独自处理查询,会产生混乱的结果,因为复杂查询往往是非幂等的。

对象存储之所以可以使用多副本,是因为对象存储的业务模型已被简化到不需要复杂查询的地步。所有写入操作都只是增加一条元数据而已,天生的幂等操作。对象存储放弃目录结构,只实现Key/Value模型,也是为了避免复杂查询的存在。

总结

在对象存储中,元数据用于保存数据存储位置、数据属性等信息。元数据的好坏关系到整个对象存储系统的可靠性、可用性、一致性等方面。元数据有如此重要的功能,必须有强有力的保障加以支撑。

元数据的存在虽然表面上增加了系统的复杂性,但实际上是将一致性保障、数据存储基准、可靠性检测和修复、数据统计等重要的功能集中到更小的数据量上,更加易于实现和操作。这在去中心化的方案中是难以实现的。

元数据的两种主要模型包括主从模型和多副本模型。主从模型,作为常规的数据库高可用手段,并不能满足云存储系统的诸多特性。而多副本模型则能更好地平衡各方面需求,同时也很好地平衡了研发、部署和运维等方面的要求。

多副本模型的核心难点集中在一致性保障方面,针对这些问题都有相应的解决手段,但都无法完全消除问题的存在。综合运用各种保障方法,可以极大地减少一致性问题的发生概率,最终将其降低到服务可以接受的程度。