元数据与一致性哈希的比较
云存储服务是云计算的重要组成部分。技术上,云存储属于大型分布式在线存储范畴。云存储是一大类特殊的共享存储。作为提供存储资源的服务,云存储需要保证用户存放的数据可靠,不丢失。同时,云存储必须确保实时在线,任何宕机都会给用户造成损失。因而,云存储的基本要求是高可靠和高可用。此外,云存储是海量数据的存储,规模巨大。而且,出于成本和现金流量的考虑,云存储的集群规模必须随着用户数据量的不断增加而扩展。云存储
·
云存储服务是
云计算
的重要组成部分。技术上,云存储属于大型分布式在线存储范畴。云存储是一大类特殊的共享存储。作为提供存储资源的服务,云存储需要保证用户存放的数据可靠,不丢失。同时,云存储必须确保实时在线,任何宕机都会给用户造成损失。因而,云存储的基本要求是高可靠和高可用。此外,云存储是海量数据的存储,规模巨大。而且,出于成本和现金流量的考虑,云存储的集群规模必须随着用户数据量的不断增加而扩展。云存储的架构,设计和技术运用都是围绕这四个基本要求展开。反之,无论多么漂亮先进的技术,只要可能影响这些目标的实现,都不能应用于云存储。
在我开始接触存储的时候,一致性哈希(以及著名的Dynamo)是非常热门的技术。技术上一致性哈希很漂亮,简洁,并且高效。但在实际应用中,却是另一种表现。本文将对集中式的元数据存储方案和一致性哈希作对比分析,以期说明元数据是更加适合云存储的选择。
1. 对象存储,块存储
实用的云存储可以分作两类:对象存储和块存储。对象存储存储是地道的数据仓储,仅仅存放key/value数据:用户有一个数据对象,需要存储起来,他就给这个对象起一个名字(key),然后将对象连同名字一起存放入对象存储。当需要的时候,用这个名字作为key,向存储系统索要。而对象存储系统必须在需要的时候将数据返还给用户,除非用户已将此数据从存储系统中删除。
块存储则是充当操作系统底下的块设备(笼统地说,就是磁盘),供系统使用。块存储实际上就是一种SAN(Storage Attach Network),将集群的存储空间分配给用户,挂载到操作系统,作为磁盘使用。因为块存储需要模拟磁盘的行为,因此必须确保低延迟。
尽管两种云存储有完全不同的目标、用途和特性,但在分布式存储基本特性方面都面临着相同的问题,这里的讨论对两者都有意义。为了简便起见,这里只讨论对象存储的情况。但很多内容和结论可以外推到块存储。
2. 存储的基础
云存储功能非常简单,存储用户的数据而已。但简单归简单,几个要点还是需要做的。当用户将一个key-value对上传至存储时,存储系统必须找合适的服务器,保存数据。通常会保存在多台服务器上,以防止数据丢失。这叫多副本。
于是,一个关键的问题就是如何选择存放数据的服务器。服务器的选择是很有技术含量的事,需要兼顾到几个要点:首先,数据必须在服务器之间平衡。不能把数据集中到少数几台服务器,造成一部分服务器撑死,而另一部分饿死。其次,在用户读取数据时,可以方便快捷定位。随后,满足云存储服务高可靠高可用大规模的特点。最后,尽可能简单。
于是,对于每个对象,都有一个key到数据存储位置的映射: key->pos。映射方式很多,最直接的,就是将每一个对象的key->pos数据对保存下来。这些数据通常被称为"元数据"。
但还有一些更巧妙的方式:根据key的特征,将key空间划分成若干分组,并将这些分组对应到不同的存储节点上。这种方式可以笼统地成为”Sharding"。如此,可以直接按照一个简单规则定位到服务器。常用的分组方式之一是按key的区间划分,比如a开头的是一组,b开头的是一组等等。而另一种更具"现代感"的分组方式,就是对key哈希后取模。哈希方案实际上就是哈希表的自然延伸,将桶分布到多台服务器中。
这两大类映射方式实质上是在不同的粒度上进行映射。"元数据"在对象粒度上,而sharding则是在一组对象的粒度。这两种不同的粒度,决定了它们有着完全不同的特性。也决定了它们在实际应用中的表现。
3. 元数据和一致性哈希
于是,在云存储方案中产生了两大流派:元数据模型和Sharding模型。而Sharding模型中,一致性哈希最为流行。一致性哈希本身很难直接用作实际使用,进而产生了很多衍生方案,其中包括著名的"Dynamo"。这里用“一致性哈希方案”指代所有基于一致性哈希的设计。
元数据方案是对象级别的key->pos映射,也就是一个会无休止增长的"map"。每多一个对象,就会多一条元数据。通常会将元数据保存在一组数据库中,方便检索和查询。元数据方案没有什么特别的地方,其核心是元数据存储部分。这部分设计的好坏,关系到系统整体的特性。关于元数据存储的设计不是本文的重点,本文将集中探讨元数据方案和一致性哈希方案的比较。
标准的一致性哈希模型是对key进行哈希运算,然后投射到一个环形的数值空间上。与此同时,对节点(存储服务器)进行编码,然后也做哈希运算,并投射到哈希环上。理论上,只要哈希算法合适,节点可以均匀地分布在哈希环上。节点根据自身在哈希环上的位置,占据一个哈希值区间,比如从本节点到下一个节点间的区间。所有落入这个区间的key,都保存到该节点上。
在这个模型中,key到数据存储逻辑位置的映射不通过存储,而是通过算法直接得到。但是,逻辑位置(哈希环上的位置)到物理位置(节点)的转换无法直接得到。标准的做法是任选一个节点,然后顺序寻找目标节点,或者采用二分法在节点之间跳转查找。这种查找方式在实际的存储系统中是无法忍受的。所以,实用的存储系统往往采用的是一个混合模型(暂且称之为“混合方案”):系统维护一个哈希区间->节点的映射表。这个映射本质上也是一种元数据,但是它是大粒度的元数据,更像是一个路由表。由于粒度大,变化很少,所以即便用文本文件都可以。这个映射表也需要多份,通常可以存放在入口服务器上,也可以分散到所有的存储节点上,关键是保持一致即可,这相对容易。
一致性哈希解决了标准哈希表改变大小时需要重新计算,并且迁移所有数据的问题,只需迁移被新加节点占据的哈希区间所包含的数据。但是,随着新节点的加入,数据量的分布会变得不均匀。为了解决这个问题,包括Dynamo在内的诸多模型,都采用了"虚拟结点"的方案:将一个节点分成若干虚拟节点。当节点加入系统时,把虚拟节点分散到哈希环上,如此可以更加"均匀地"添加一个节点。
一致性哈希及其衍生方案,将数据按一定规则分片,按一定规则将分片后的数据映射到相应的存储服务器上。因此,它只需要维护一个算法,或者一个简单的映射表,便可直接定位数据。更加简单,并且由于少了查询元数据,性能上也更有优势。
但是,这只是理论上的。
如果我们只考虑存储的功能(get,put,delete),一致性哈希非常完美。但实际情况是,真正主宰云存储架构的,是那些非功能性需求:
1、 大规模和扩展性
2、 可靠性和一致性
3、 可用性和可管理
4、 性能
在实际的云存储系统中,非功能需求反而主导了架构和设计。并且对key-pos映射的选择起到决定性的作用。
4 规模和扩展
首先,云存储最明显的特点是规模巨大。对于一个公有云存储服务,容纳用户数据是没有限度的。不可能以"容量已满"作为理由,拒绝用户存放数据。因此,云存储是“规模无限”的。意思就是说,云存储系统,必须保证任何时候都能够随意扩容,而不会影响服务。
另一方面,云存储作为服务,都是由小到大。一开始便部署几千台服务器,几十P的容量,毫无意义。资源会被闲置而造成浪费,对成本和现金流量产生很大的负面作用。通常,我们只会部署一个小规模的系统,满足初始的容量需求。然后,根据需求增长的情况,逐步扩容。
于是,云存储系统必须是高度可扩展的。
面对扩展,元数据方案没有什么困难。当节点增加时,系统可以更多地将新来的写请求引导到新加入的节点。合适的调度平衡策略可以确保系统平衡地使用各节点的存储空间。
但对于一致性哈希和它的衍生方案而言,却麻烦很多。原始的哈希表一旦需要扩容,需要rehash。在基于哈希的分布式存储系统中,这就意味着所有数据都必须重新迁移一遍。这当然无法忍受的。一致性哈希的出现,便可以解决此问题。由于对象和服务器都映射到哈希环上,当新节点加入后,同样会映射到哈希环上。原来的区段会被新节点截断,新加入的节点,会占据被切割出来的哈希值区间。为了完成这个转换,这部分数据需要迁移到新服务器上。相比原始的哈希,一致性哈希只需要传输被新服务器抢走的那部分数据。
但是终究需要进行数据迁移。数据迁移并非像看上去那样,仅仅是从一台服务器向另一台服务器复制数据。云存储是一个在线系统,数据迁移不能影响系统的服务。但迁移数据需要时间,为了确保数据访问的延续性,在迁移开始时,写入操作被引导到目标服务器,而源节点的待迁移部分切换至只读状态。与此同时,开始从源节点迁移数据。当用户试图读取迁移范围内的数据时,需要尝试在源和目标节点分别读取。这种单写双读的模式可以保证服务不受影响。但是,必须控制数据迁移的速率。如果迁移操作将磁盘吞吐能力跑满,或者网络带宽耗尽,服务必然受到影响。
另一个问题是,除非成倍地增加节点,否则会存在数据不平衡的问题。为了平衡数据,需要迁移更多的数据,每台节点都需要迁出一些,以确保每个节点的数据量都差不多。虚拟节点的引入便是起到这个作用。于是实际的数据迁移量同增加的容量数成正比,系数是当前存储系统的空间使用率。
于是,一个1P的系统,三副本,70%的消耗,扩容200T,那么需要迁移大约140T*3=420T的数据,才能使数据存储量得到平衡。如果使用现在常用的存储服务器(2T*12),需要新增21台。如果它们都并发起来参与迁移,单台迁移速率不超过50MBps,那么这次扩容所需的时间为420T/(50M*21)=400000秒,大约4.6天。这是理想状况,包括软硬件异常,用户访问压力,迁移后的检验等情况,都会延长迁移时间。很可能十天半月泡在这些任务上。而在数据迁移完成,老服务器的存储空间回收出来之前,实际上并未扩容。那么万一扩容实在系统空间即将耗尽时进行,(别说这事不会碰到,一个懒散的供货商,或者厂家被水淹,都可能让这种事情发生。云计算什么事都会遇到),那么很可能会因为来不及完成扩容而使系统停服。云存储,特别是公有云存储,扩容必须是快速而便捷的。
更复杂的情况是迁移过程中出错,硬件失效等异常情况的处理过程会很复杂,因为此时数据分布处于一种中间状态,后续处理必须确保系统数据安全一致。
系统的扩容规模越大,困难程度越大。当系统有100P的规模(很多系统宣称可以达到的规模),而用户数据增长迅猛(公有云存储有明显的马太效应,规模越大,增长越快),在几百上千台服务器之间迁移成P的数据,是多么骇人的场景。
数据迁移会消耗网络带宽,吃掉磁盘负载,搅乱服务器的cache等等,都是云存储的大忌,能不做尽量不做。
元数据方案一般情况下不需要做迁移。迁移只有存储服务器新旧更替,或者租借的服务器到期归还时进行。由于数据对象可以放置在任何节点上,因而可以把一台节点需要迁移的数据分散到其他节点上。并且可以从其他副本那里多对多并发地传输数据。负载被分散到整个集群,对服务的影响更小,也更快捷。实际上,这个逻辑和数据副本修复是一样的。
很显然,相比一致性哈希方案动不动来回迁移数据的做法,元数据方案提供一个动态平衡的机制,可以无需数据迁移,服务器一旦加入集群,可以立刻生效,实现平静地扩容。
5 可靠性和一致性
可靠性(本文特指数据的可靠性,即不丢失数据)无疑是云存储的根本。用户将数据托付给你,自然不希望随随便便地丢失。维持可靠性是云存储最困难的部分。(当然,更难的是在保持高可靠性的前提下确保高可用)。
在任何一个系统中,硬件和软件都无法保障完全可靠。芯片会被烧毁,电路可能短路,电压可能波动,老鼠可能咬断网线,供电可能中断,软件会有bug,甚至宇宙射线会干扰寄存器…。作为存储的核心部件,硬盘反而更加脆弱。在标准的服务器中,除了光驱和风扇以外,硬盘是唯一的机电元件。由于存在活动部件,其可靠性不如固态电路,更容易受到外界的干扰。所以,硬盘时常被看作消耗品。在一个有几万块硬盘的存储集群中,每周坏上几块硬盘是再寻常不过的事。运气不好的时候,一天中都可能坏上2、3块。
因此,只把数据保存在一块盘中是无法保障数据可靠的。通常我们都会将数据在不同的服务器上保存多份,称之为“副本”。原则上,副本越多,越可靠。但是,过多的副本会造成存储成本的增加,并且降低性能,增加维持一致性的难度。一般会保持3副本,是一个比较均衡的数字。
但是,在确定的副本数下,真正对可靠性起到关键作用的,是副本丢失后的恢复速度。比如,一个3副本存储系统中,当一个磁盘损坏后,它所承载的数据对象就只剩2个副本了。在这块盘修复之前,如果再坏一块盘,恰巧和尚未修复的盘有共同的数据对象,那么这些数据就只剩一个副本在支撑着,这是非常危险的状态。更有甚者,一个3副本的存储系统,在运行过程中,即便没有硬盘损坏,总会有一些对象由于种种原因处于两副本状态,尚未来得及修复。比如写入数据时,有一个副本写完后发现校验码不对,需要重写。此时,如果一块盘损坏了,上面正好有这些2副本的对象。于是,从这一刻开始,这个对象只有1副本。坏盘上的数据被修复之前,另一块盘包含此对象的硬盘也坏了,那么数据就丢了。尽管这种概率很小,但是云存储系统是经年累月地运行,大规模加上长时间,任何小概率事件都会发生。而且在实际运行的系统中,很多因素会导致硬盘寿命远小于理论值,比如机架震动、电源不稳、意外失电、辐射等等。而且,对于成批采购的硬盘,通常都会集中在一段时间内一起进入失效期,多块磁盘同时损坏的概率会大幅提高。
如果数据修复的速度足够快,可以抢在另一块盘损坏之前,修复丢失的副本,那么数据丢失的概率会大大减小。(严格地讲,无论修复时间有多短,在修复期间坏第二块盘的概率始终存在,只是修复时间越短,这个概率越小。关键是要让它小到我们可以接受的程度)。
一致性哈希方案中,如果将一块磁盘同一个hash区间一一绑定。因此,数据恢复时,只能先更换坏盘,然后从其他副本处读取数据,写入新磁盘。但是,磁盘的持续写入能力通常也只有50-60MBps。如果是一块有1.5T数据的硬盘失效,那么恢复起来至少需要30000秒,将近9个小时。考虑到服务器的整体负载和网络状况,时间可能会接近12小时。在这个时间尺度里,第二块盘,甚至第三块盘损坏的概率还是相当大的。而且,硬盘更换的时间取决于机房管理的响应能力,这往往是一个薄弱环节。
如果方案中让节点同hash区间一一对应,在节点内部,再将数据分散存储到一块磁盘中,那么当需要恢复副本时,节点将损坏的磁盘上的数据对象分散存放到其他磁盘上。这样,可以先发起修复,从其他副本那里都到数据,并发地向多个磁盘恢复数据,磁盘的吞吐限制会得到缓解。但问题的解决并不彻底。首先,网络会成为瓶颈。1个千兆网口最多支持到120MBps,这个速率也仅仅比磁盘快一倍,而且实际使用中也不能将带宽全部用光,否则影响服务,毕竟其他硬盘还是好的,还需要正常工作。原则上可以通过增加网口数量拓宽吞吐量,但这增加了网络设备方面的成本。而且这也仅仅是增加3、4倍的吞吐能力,我们真正需要的是近10倍地缩小恢复时间。至于光纤,那不是普通公有云存储所能奢望的。即便网络吞吐问题解决了,还有一个更核心的问题。因为数据在节点内部任意分散到各磁盘上,那么节点就需要维护一个key->磁盘的映射,也就是节点内的局部元数据。这个元数据需要服务器自己维护,由于单台服务器的资源有限,元数据的可靠性、可用性和一致性的维持非常麻烦。一套存储一套元数据存储已经够麻烦的了,何况每个节点一套元数据。一旦这个元数据丢失了,这个节点上所有的数据都找不到了。理论上,这个元数据丢失了,也不会影响全局,可以利用一致性哈希算法到其他副本那里恢复出丢失的数据。但不得不将原来服务器上的所有数据都传输一遍,这个数据量往往有一二十T,恢复起来更困难。更现实的做法是直接扫描各磁盘,逆向地重建元数据。当然,这将是一个漫长的过程,期间整台节点是不可用的,此间发生的写入操作还需的事后重新恢复(具体参见本文“可用性”部分)。
副本恢复最快,影响最小的方法就是,不让数据对象副本绑死在一台节点上,只要数据对象可以存放到任意一台节点,那么便可以在节点之间做多对多的数据副本恢复。集群规模越大,速度越快,效果越好。基于元数据的方案,每一个对象同节点,乃至硬盘的映射是任意的,在副本恢复方面,有得天独厚的优势。而一致性哈希严格地将数据同节点或磁盘绑定,迫使这种并发无法进行。
有一些基于混合方案的衍生方案可以解决一致性哈希在副本修复速度问题:将哈希环划分成若干slot(bucket,或者其他类似的称谓),数量远远大于将来集群可能拥有的节点数,或磁盘数。(好吧,我们说过规模无限,无限自然不可能,只要足够大就行了,比如2^32)。slot到节点或者磁盘的映射,通过一个映射表维护。各副本集群中的slot分布互不相同。当一个磁盘损坏时,找出所包含的slot,将这些slot分散到其他节点和磁盘上去,这样便可以并发地从其他节点以slot为单位恢复副本。在损坏磁盘更换之后,再将一些slot,可以是原来的,也可以是随意的,迁移到新硬盘,以平衡整体的数据分布。迁移的时候,副本已经恢复,迁移操作的时间压力就很小了,可以慢慢来,磁盘吞吐的瓶颈也不会有什么的影响。
但相比之下,元数据方案修复副本之后不存在数据迁移这一步,在这方面对象级元数据的存在使副本恢复简单很多。
同数据可靠性相关的一个问题是一致性。一致性问题可以看作可靠性问题的一个部分。因为当各副本的数据版本不一致时,便意味着数据对象的当前版本是缺少副本的。(实际上,从存储角度来讲,一个数据对象的不同版本,就是不同的数据)。确保一个数据对象的一致性最实用的方法是所谓W+R>N。即N个副本中,一个版本写入时确保有W个成功,而读取时确保有R个成功,只要满足W+R>N的情况,便可以保证始终可以读到最后写入成功的版本。
W+R>N的使用有一个问题,需要并发地从所有副本上尝试读取数据,然后通过读取的数据对象版本(或者时间戳)的比对,以确定是否满足一致性公式的要求。如果所读取的数据有几十上百MB,甚至上G的对象,一口气读出所有副本,而最终只取其中一个,着实浪费,系统压力会整整大上N倍。解决的方法是先做一次预读,读出所有副本的版本信息,进行一致性比对,确定有效副本后,再行数据本身的读取。
元数据方案在一致性上简单的多。元数据为了保证可靠,也会使用多副本。因为元数据很小,可以保持更多的副本数,比如5个,甚至7个。如此多的副本,基本上不必担心其可靠性。重点在于一致性。同样也是采用W+R>N的策略,但却将元数据读取和一致性保障在一次访问中解决。对于数据存储服务器而言,任务更多地是在保障每一个副本和版本的完整。
随着时间的推移,数据会发生退化,有各种原因造成副本的丢失。一致性也是一样。对于热数据,经常被访问,存储数据出错很快就会发现。但冷数据需要依靠定期检查发现错误。这个核对工作在元数据方案里,就是比对元数据和节点的每个盘上的对象清单,元数据保存的永远是最新版本,只要不匹配,就可以认定出错,立刻加以修复。但在一致性哈希方案中,需要交叉核对一个哈希区间的三个副本所包含的对象清单,以确定哪个对象是最新副本。然后再修正数据问题。
当一个节点由于种种原因下线,那么期间的所有写入操作都无法完成。此时,元数据方案处理起来很简单:另外挑选一台合适的节点,写入副本,更新相应的元数据项后,操作完成,一切太平。
一致性哈希方案要复杂得多。对于一个哈希区间的一个副本,被固定在一个节点上。换句话说,一组副本必须存放在特定的一个节点上,不能随意放置。如果需要定位到其他节点上,必须整区间的迁移。这个特性的结果就是,当这台节点下线后,无法写入相应的副本,也无法随意地将副本写到其他节点上。后续处理有2种方法:1、将副本或者key写入一个队列,等节点恢复后,发起修复操作,从其他的副本获得数据,补上缺失的副本。问题是这个队列必须有足够的可靠性,否则待修复key丢失,相应的对象会长时间缺少副本,直到数据一致性检测发现问题。这会增加一致性检测的压力,使得原本就复杂的过程雪上加霜;2、按一定规则写入其他节点,待恢复后原样迁移回来。这个方案相对简单些,但整个调度逻辑比较复杂,涉及到数据节点之间的协调。但是这种点到点的数据恢复,会给暂存服务器产生压力,不利于稳定运行。不管哪种方案,数据迁移或者复制都是免不了的。这中间的异常处理,负载控制等都需要花费心思。
元数据方案在节点失效时的处理要比一致性哈希方案单纯简洁的多。节点失效是云存储的常态,处理方式越简洁越好。在大集群中,几个节点同时下线都是很平常的事,一致性哈希如此复杂的节点失效管理,就是运维的地狱。
6. 可用性和可管理性
可用性在某些方面同可靠性有着共同的 解决方案 ,比如多副本可以消除单点,提高可用性。但是它们在其他方面却存在着矛盾。当一个副本写入失败,从可靠性角度而言,应当设法重试,或者索性告诉用户写入失败。但这样势必造成响应变慢,或者可用性降低。(响应变慢超过一个程度后,便会被认为是失败,不管最终是否成功)。此外,为了保障可靠性的诸多措施,比如副本修复,数据迁移等,会消耗系统资源,从而影响到可用性。
在上面对于可靠性的分析中可以看到,由于副本被绑定在特定节点上,一致性哈希要确保同元数据相当的可靠性的话,不得不放弃一些可用性,将有副本写入出错的操作返回失败。因为元数据方案的数据存储位置没有限制,对于多数的副本写入失败可以通过重选服务器得到更正。一致性哈希则无此便利,要么放弃一定的可用性,要么承担可靠性的风险。
根本上而言,一致性哈希在副本写入上制造了局部的隐式单点,尽管是短期的或者临时的,但依旧会对系统产生影响。一个设计良好的分布式系统会尽量地减少单点出现的可能性,这直接关系到系统的持续和瞬时可用性。元数据方案可以确保数据分布上不存在任何形式的单点。对于一个对象而言,任何节点都没有特殊性,这种无差别化才能真正保证消除单点。
在云存储系统中,使用公式R+W>N的地方,便是影响系统可用性的核心要点。可用性和一致性往往是一对冤家。在这个地方,需要向N台服务器同时发出请求,而最终的有效性取决于服务器反馈的情况。一般来说,N越大,越容易在确保一致性的前提下,维持可用性。(实际上是取决于X=R+W-N的值,越大越好。这个值表示当X+1个副本下线或丢失后,系统将不能保证临时的或永久的一致性)。但是,N-R和N-W越小,对可用性的影响越大。如果N=R,那么只要有一台服务器下线,会造成系统不可读取。如果N-R=3,那么即便是3台服务器下线,系统还能正确读取。对于W也一样。在这里,一致性和可用性是一对矛盾。当副本数足够多(N数可以很大),可以很容易地获得较高的X数。比如N=7, R=W=5,X=3,N-R=2,N-W=2,意味着即便有2台服务器下线,也能保证读写的有效性,同时确保一致性。
但如果N=3,无论如何也只能让R=3或者W=3,此时尽管可以获得X=3的一致性保障级别,但即便一台服务器下线,也会造成系统不可用。如果R=W=2,可以保证1台服务器下线,系统还是可用,但一致性保障级别降低到了X=1。
在元数据方案中,元数据的副本数(N)可以大些,故障和异常造成的副本下线,或者丢失,对系统的可用性和一致性产生的影响很小,对于这类问题的处理的紧迫性会低一些。相反,一致性哈希的一致性依赖于数据存储节点,面对大大小小的数据对象,无法使用很多副本,通常都会用3。在这个N数下,无论一致性还是可用性,都很脆弱。
对于可用性而言,最后,也是最重要的一点是运维管理。运维的好坏直接决定了可用性。前文已述,一致性哈希在一些关键的系统维护点上相比元数据方案多出不少环节。在同样运维水平和强度下,一致性哈希更加容易出现可用性问题。这方面对可用性的影响很难明确地给出量化的评判,但是运维是云存储系统高可用保障的核心,运维的复杂性往往决定了最终9的个数。
现在来看看元数据方案的运维特点。元数据方案相比标准一致性哈希方案多出了一个元数据存储系统。元数据通常有更多的副本,副本数越多,一致性的维持越发困难。一般的方案都是依赖异步执行的版本同步机制,尽快同步各副本。但是,为防止同步失效,会定期全面扫描核对所有元数据,确保元数据不会退化。这个过程伴随者很大的数据吞吐和计算量。但这个过程是离线操作,并且没有非常严格的时间要求,即便失败,也不会过多地影响系统的运行。它的时间裕度比较大。元数据量不会很大,在合适的算法下,一致性比对的时间也不会很长,通常不超过2、3小时。并且可以根据需要增减所需的服务器数量。
一致性哈希运维的重点则在另一头。一致性哈希模型及其各种变形由于需要确保数据平衡,不得不在扩容时进行大范围的数据迁移。在不影响服务的情况下,迁移速率必须受到限制,导致迁移时间很长。但有时数据迁移的时间要求很高。因为在迁移完成之前,迁移的数据源所占的空间还不能回收。此时所添加的空间暂时无法使用。而且数据回收也需要花费时间。在可用空间紧迫的情况下,迁移速度的压力会很大。一旦迁移过程中出现异常,将会雪上加霜。在长达几天的数据迁移过程中发生磁盘损坏的可能性非常大,修复操作将迫使数据迁移减速,甚至停止。修复的优先级更高。迁移操作涉及很多服务器的并发,协调和控制工作很复杂。而且不同的迁移原因会有不同的迁移策略。再加上各种异常处理,运维管理内容很多,将是非常痛苦的事。随着规模越来越大,最终可能超出运维能力的极限,无力维持存储的可用性。
元数据方案在绝大多数情况下不会进行数据迁移。少掉这样一个复杂重载的环节,对于系统维护的压力会小很多。
因此,尽管元数据方案多出了一个元数据的存储集群,但它相比一致性哈希方案反而更容易维持可用性。
7. 性能
性能是大多数程序员都关心的东西,往往被放在很高的位置。但在云存储中,性能反倒是级别较低的问题。存储是io密集型的系统,无论如何优化代码,都不可避免地受到物理设备的限制。
云存储的性能要点首先在于并发。做好并发,确保所有请求不会相互间阻塞,便可以从大的方面保证性能。元数据方案中,元数据存取存在大量的并发访问。每个用户的数据访问请求,在这里会转化成N个并发请求。系统不得不管理巨大数量的并发操作,并且小心维护并发之间的逻辑关系。传统上使用的线程池很难满足需要,不得不采用异步模型,从而增加开发难度。
一致性哈希在这方面有自身的优势。因为N数不大,并发的放大效应相比元数据方案要小很多。当然前面也说了,小N数会减小一致性的裕度,也不利于可用性。
云存储性能优化另一个重要的要点是,减少一次用户请求中跨服务器访问的次数。跨越网络的访问必然存在延迟,跨的次数越多,整体延迟越大。同时也会增加网络的负担。元数据方案天生多一次元数据检索操作,因而在这方面处于劣势。但一致性哈希在读取对象时,也没有好到哪里去。前面讨论一致性的时候已经说过,为了执行W+R>N的逻辑,又不能读取所有副本,只能采取一次并发预读操作。这样一致性哈希的跨服务器访问次数也同元数据方案扯平了。只是并发数少一些。这是以降低容错能力为代价的。
元数据在性能方面的主要劣势是元数据的访问。系统对元数据的访问主要是检索操作,因此通常会采用数据库,或者具备检索功能的数据存储引擎。这些模块在重载情况下有性能限制。由于副本数多,简单地扩展服务器数量代价较大。好在这些部分的优化,特别是数据库优化,有很成熟的经验,花些功夫也是能做好的。
8. 其他
在功能方面,一致性哈希还有一个不足。尽管一致性哈希可以实现对象存储的基本功能,但存储服务有时需要提供一些额外的功能,比如列出一个用户所保存的对象key清单。如果有元数据,那么这些用户的信息可以保存在元数据里,需要的时候检索这些信息即可。但是,在一致性哈希方案中,所有的key在系统中已经被哈希,分散到所有服务器上。如果要获得用户的对象清单,那么就不得不在所有存储节点上执行相应的检索操作,想象一下一个几千,甚至几万台节点的存储集群,执行一次这样的操作,会是什么结果?
对于存放了大量数据对象的用户,列出对象清单的操作可能没有什么直接意义。或许不提供此类功能,用户也能忍受。但是,另一个用户相关的问题却无法回避。这就是计费。计费就是计算出一个用户所有的对象所占据的容量,然后制作成费用清单,提供给计费系统。
计费最简单的办法是通过用户的访问记录,累积出来,写入就加,删除就减。但是实际没那么简单。对于一个key的覆盖写入,那么必须先扣除原有的对象大小,然后再加上新对象的大小。
更重要的,由于访问记录可能出错或者丢失,这种相对计算方式随着时间的增长会产生累积误差。需要定期校准。校准的方式就是统计所有用户的对象大小,做绝对容量的计算。由于对象大小存放在所有数据存储节点中,这又是一个恐怖的全系统的数据扫描。同时还必须确保对象属性的一致性,以免计费出错。
计费涉及到钱,通常用户会非常敏感,应当尽可能不出错。校准需要尽量频繁。但是全集群的计费校准代价实在太高,还是应当尽量少做。于是这对矛盾怎么调和,对于开发运维人员都是个巨大的挑战。
在元数据方案中,计费所需信息都可以保存在元数据中,计费操作只局限在元数据上。元数据量少,可以直接将数据dump出来,在额外的服务器上计算。这种操作相对轻量很多,可以更频繁地执行,提高用户账单的准确性。
9. 总结
经过大体的分析,可以看到在仅考虑云存储功能需求的情况下,一致性哈希具备简单高效的优势。但是,大型的云存储系统的功能不是影响其架构的主要因素。包括规模、可扩展性、可靠性、可用性和一致性等方面的非功能性需求才是云存储需要重点考虑的地方。一致性哈希及其各种变形方案,在这些方面存在诸多缺陷。尽管每个方面的缺陷并非不可逾越的障碍,往往都可以通过一些设计和运维手段加以解决。但是系统复杂度和运维难度随之增加。最关键的,每个方面的问题整合起来所产生的叠加效应,会轻而易举地摧垮一个云存储系统。
一般而言,一致性哈希适合一些有既定规模,不太需要扩展,数据尺寸不大的场合。这些特性意味着一致性哈希无法很好地适用于大型的云存储系统。元数据方案尽管在架构上复杂些,但其优点是灵活。而这种灵活性更加适合云存储的需要。
在我开始接触存储的时候,一致性哈希(以及著名的Dynamo)是非常热门的技术。技术上一致性哈希很漂亮,简洁,并且高效。但在实际应用中,却是另一种表现。本文将对集中式的元数据存储方案和一致性哈希作对比分析,以期说明元数据是更加适合云存储的选择。
1. 对象存储,块存储
实用的云存储可以分作两类:对象存储和块存储。对象存储存储是地道的数据仓储,仅仅存放key/value数据:用户有一个数据对象,需要存储起来,他就给这个对象起一个名字(key),然后将对象连同名字一起存放入对象存储。当需要的时候,用这个名字作为key,向存储系统索要。而对象存储系统必须在需要的时候将数据返还给用户,除非用户已将此数据从存储系统中删除。
块存储则是充当操作系统底下的块设备(笼统地说,就是磁盘),供系统使用。块存储实际上就是一种SAN(Storage Attach Network),将集群的存储空间分配给用户,挂载到操作系统,作为磁盘使用。因为块存储需要模拟磁盘的行为,因此必须确保低延迟。
尽管两种云存储有完全不同的目标、用途和特性,但在分布式存储基本特性方面都面临着相同的问题,这里的讨论对两者都有意义。为了简便起见,这里只讨论对象存储的情况。但很多内容和结论可以外推到块存储。
2. 存储的基础
云存储功能非常简单,存储用户的数据而已。但简单归简单,几个要点还是需要做的。当用户将一个key-value对上传至存储时,存储系统必须找合适的服务器,保存数据。通常会保存在多台服务器上,以防止数据丢失。这叫多副本。
于是,一个关键的问题就是如何选择存放数据的服务器。服务器的选择是很有技术含量的事,需要兼顾到几个要点:首先,数据必须在服务器之间平衡。不能把数据集中到少数几台服务器,造成一部分服务器撑死,而另一部分饿死。其次,在用户读取数据时,可以方便快捷定位。随后,满足云存储服务高可靠高可用大规模的特点。最后,尽可能简单。
于是,对于每个对象,都有一个key到数据存储位置的映射: key->pos。映射方式很多,最直接的,就是将每一个对象的key->pos数据对保存下来。这些数据通常被称为"元数据"。
但还有一些更巧妙的方式:根据key的特征,将key空间划分成若干分组,并将这些分组对应到不同的存储节点上。这种方式可以笼统地成为”Sharding"。如此,可以直接按照一个简单规则定位到服务器。常用的分组方式之一是按key的区间划分,比如a开头的是一组,b开头的是一组等等。而另一种更具"现代感"的分组方式,就是对key哈希后取模。哈希方案实际上就是哈希表的自然延伸,将桶分布到多台服务器中。
这两大类映射方式实质上是在不同的粒度上进行映射。"元数据"在对象粒度上,而sharding则是在一组对象的粒度。这两种不同的粒度,决定了它们有着完全不同的特性。也决定了它们在实际应用中的表现。
3. 元数据和一致性哈希
于是,在云存储方案中产生了两大流派:元数据模型和Sharding模型。而Sharding模型中,一致性哈希最为流行。一致性哈希本身很难直接用作实际使用,进而产生了很多衍生方案,其中包括著名的"Dynamo"。这里用“一致性哈希方案”指代所有基于一致性哈希的设计。
元数据方案是对象级别的key->pos映射,也就是一个会无休止增长的"map"。每多一个对象,就会多一条元数据。通常会将元数据保存在一组数据库中,方便检索和查询。元数据方案没有什么特别的地方,其核心是元数据存储部分。这部分设计的好坏,关系到系统整体的特性。关于元数据存储的设计不是本文的重点,本文将集中探讨元数据方案和一致性哈希方案的比较。
标准的一致性哈希模型是对key进行哈希运算,然后投射到一个环形的数值空间上。与此同时,对节点(存储服务器)进行编码,然后也做哈希运算,并投射到哈希环上。理论上,只要哈希算法合适,节点可以均匀地分布在哈希环上。节点根据自身在哈希环上的位置,占据一个哈希值区间,比如从本节点到下一个节点间的区间。所有落入这个区间的key,都保存到该节点上。
在这个模型中,key到数据存储逻辑位置的映射不通过存储,而是通过算法直接得到。但是,逻辑位置(哈希环上的位置)到物理位置(节点)的转换无法直接得到。标准的做法是任选一个节点,然后顺序寻找目标节点,或者采用二分法在节点之间跳转查找。这种查找方式在实际的存储系统中是无法忍受的。所以,实用的存储系统往往采用的是一个混合模型(暂且称之为“混合方案”):系统维护一个哈希区间->节点的映射表。这个映射本质上也是一种元数据,但是它是大粒度的元数据,更像是一个路由表。由于粒度大,变化很少,所以即便用文本文件都可以。这个映射表也需要多份,通常可以存放在入口服务器上,也可以分散到所有的存储节点上,关键是保持一致即可,这相对容易。
一致性哈希解决了标准哈希表改变大小时需要重新计算,并且迁移所有数据的问题,只需迁移被新加节点占据的哈希区间所包含的数据。但是,随着新节点的加入,数据量的分布会变得不均匀。为了解决这个问题,包括Dynamo在内的诸多模型,都采用了"虚拟结点"的方案:将一个节点分成若干虚拟节点。当节点加入系统时,把虚拟节点分散到哈希环上,如此可以更加"均匀地"添加一个节点。
一致性哈希及其衍生方案,将数据按一定规则分片,按一定规则将分片后的数据映射到相应的存储服务器上。因此,它只需要维护一个算法,或者一个简单的映射表,便可直接定位数据。更加简单,并且由于少了查询元数据,性能上也更有优势。
但是,这只是理论上的。
如果我们只考虑存储的功能(get,put,delete),一致性哈希非常完美。但实际情况是,真正主宰云存储架构的,是那些非功能性需求:
1、 大规模和扩展性
2、 可靠性和一致性
3、 可用性和可管理
4、 性能
在实际的云存储系统中,非功能需求反而主导了架构和设计。并且对key-pos映射的选择起到决定性的作用。
4 规模和扩展
首先,云存储最明显的特点是规模巨大。对于一个公有云存储服务,容纳用户数据是没有限度的。不可能以"容量已满"作为理由,拒绝用户存放数据。因此,云存储是“规模无限”的。意思就是说,云存储系统,必须保证任何时候都能够随意扩容,而不会影响服务。
另一方面,云存储作为服务,都是由小到大。一开始便部署几千台服务器,几十P的容量,毫无意义。资源会被闲置而造成浪费,对成本和现金流量产生很大的负面作用。通常,我们只会部署一个小规模的系统,满足初始的容量需求。然后,根据需求增长的情况,逐步扩容。
于是,云存储系统必须是高度可扩展的。
面对扩展,元数据方案没有什么困难。当节点增加时,系统可以更多地将新来的写请求引导到新加入的节点。合适的调度平衡策略可以确保系统平衡地使用各节点的存储空间。
但对于一致性哈希和它的衍生方案而言,却麻烦很多。原始的哈希表一旦需要扩容,需要rehash。在基于哈希的分布式存储系统中,这就意味着所有数据都必须重新迁移一遍。这当然无法忍受的。一致性哈希的出现,便可以解决此问题。由于对象和服务器都映射到哈希环上,当新节点加入后,同样会映射到哈希环上。原来的区段会被新节点截断,新加入的节点,会占据被切割出来的哈希值区间。为了完成这个转换,这部分数据需要迁移到新服务器上。相比原始的哈希,一致性哈希只需要传输被新服务器抢走的那部分数据。
但是终究需要进行数据迁移。数据迁移并非像看上去那样,仅仅是从一台服务器向另一台服务器复制数据。云存储是一个在线系统,数据迁移不能影响系统的服务。但迁移数据需要时间,为了确保数据访问的延续性,在迁移开始时,写入操作被引导到目标服务器,而源节点的待迁移部分切换至只读状态。与此同时,开始从源节点迁移数据。当用户试图读取迁移范围内的数据时,需要尝试在源和目标节点分别读取。这种单写双读的模式可以保证服务不受影响。但是,必须控制数据迁移的速率。如果迁移操作将磁盘吞吐能力跑满,或者网络带宽耗尽,服务必然受到影响。
另一个问题是,除非成倍地增加节点,否则会存在数据不平衡的问题。为了平衡数据,需要迁移更多的数据,每台节点都需要迁出一些,以确保每个节点的数据量都差不多。虚拟节点的引入便是起到这个作用。于是实际的数据迁移量同增加的容量数成正比,系数是当前存储系统的空间使用率。
于是,一个1P的系统,三副本,70%的消耗,扩容200T,那么需要迁移大约140T*3=420T的数据,才能使数据存储量得到平衡。如果使用现在常用的存储服务器(2T*12),需要新增21台。如果它们都并发起来参与迁移,单台迁移速率不超过50MBps,那么这次扩容所需的时间为420T/(50M*21)=400000秒,大约4.6天。这是理想状况,包括软硬件异常,用户访问压力,迁移后的检验等情况,都会延长迁移时间。很可能十天半月泡在这些任务上。而在数据迁移完成,老服务器的存储空间回收出来之前,实际上并未扩容。那么万一扩容实在系统空间即将耗尽时进行,(别说这事不会碰到,一个懒散的供货商,或者厂家被水淹,都可能让这种事情发生。云计算什么事都会遇到),那么很可能会因为来不及完成扩容而使系统停服。云存储,特别是公有云存储,扩容必须是快速而便捷的。
更复杂的情况是迁移过程中出错,硬件失效等异常情况的处理过程会很复杂,因为此时数据分布处于一种中间状态,后续处理必须确保系统数据安全一致。
系统的扩容规模越大,困难程度越大。当系统有100P的规模(很多系统宣称可以达到的规模),而用户数据增长迅猛(公有云存储有明显的马太效应,规模越大,增长越快),在几百上千台服务器之间迁移成P的数据,是多么骇人的场景。
数据迁移会消耗网络带宽,吃掉磁盘负载,搅乱服务器的cache等等,都是云存储的大忌,能不做尽量不做。
元数据方案一般情况下不需要做迁移。迁移只有存储服务器新旧更替,或者租借的服务器到期归还时进行。由于数据对象可以放置在任何节点上,因而可以把一台节点需要迁移的数据分散到其他节点上。并且可以从其他副本那里多对多并发地传输数据。负载被分散到整个集群,对服务的影响更小,也更快捷。实际上,这个逻辑和数据副本修复是一样的。
很显然,相比一致性哈希方案动不动来回迁移数据的做法,元数据方案提供一个动态平衡的机制,可以无需数据迁移,服务器一旦加入集群,可以立刻生效,实现平静地扩容。
5 可靠性和一致性
可靠性(本文特指数据的可靠性,即不丢失数据)无疑是云存储的根本。用户将数据托付给你,自然不希望随随便便地丢失。维持可靠性是云存储最困难的部分。(当然,更难的是在保持高可靠性的前提下确保高可用)。
在任何一个系统中,硬件和软件都无法保障完全可靠。芯片会被烧毁,电路可能短路,电压可能波动,老鼠可能咬断网线,供电可能中断,软件会有bug,甚至宇宙射线会干扰寄存器…。作为存储的核心部件,硬盘反而更加脆弱。在标准的服务器中,除了光驱和风扇以外,硬盘是唯一的机电元件。由于存在活动部件,其可靠性不如固态电路,更容易受到外界的干扰。所以,硬盘时常被看作消耗品。在一个有几万块硬盘的存储集群中,每周坏上几块硬盘是再寻常不过的事。运气不好的时候,一天中都可能坏上2、3块。
因此,只把数据保存在一块盘中是无法保障数据可靠的。通常我们都会将数据在不同的服务器上保存多份,称之为“副本”。原则上,副本越多,越可靠。但是,过多的副本会造成存储成本的增加,并且降低性能,增加维持一致性的难度。一般会保持3副本,是一个比较均衡的数字。
但是,在确定的副本数下,真正对可靠性起到关键作用的,是副本丢失后的恢复速度。比如,一个3副本存储系统中,当一个磁盘损坏后,它所承载的数据对象就只剩2个副本了。在这块盘修复之前,如果再坏一块盘,恰巧和尚未修复的盘有共同的数据对象,那么这些数据就只剩一个副本在支撑着,这是非常危险的状态。更有甚者,一个3副本的存储系统,在运行过程中,即便没有硬盘损坏,总会有一些对象由于种种原因处于两副本状态,尚未来得及修复。比如写入数据时,有一个副本写完后发现校验码不对,需要重写。此时,如果一块盘损坏了,上面正好有这些2副本的对象。于是,从这一刻开始,这个对象只有1副本。坏盘上的数据被修复之前,另一块盘包含此对象的硬盘也坏了,那么数据就丢了。尽管这种概率很小,但是云存储系统是经年累月地运行,大规模加上长时间,任何小概率事件都会发生。而且在实际运行的系统中,很多因素会导致硬盘寿命远小于理论值,比如机架震动、电源不稳、意外失电、辐射等等。而且,对于成批采购的硬盘,通常都会集中在一段时间内一起进入失效期,多块磁盘同时损坏的概率会大幅提高。
如果数据修复的速度足够快,可以抢在另一块盘损坏之前,修复丢失的副本,那么数据丢失的概率会大大减小。(严格地讲,无论修复时间有多短,在修复期间坏第二块盘的概率始终存在,只是修复时间越短,这个概率越小。关键是要让它小到我们可以接受的程度)。
一致性哈希方案中,如果将一块磁盘同一个hash区间一一绑定。因此,数据恢复时,只能先更换坏盘,然后从其他副本处读取数据,写入新磁盘。但是,磁盘的持续写入能力通常也只有50-60MBps。如果是一块有1.5T数据的硬盘失效,那么恢复起来至少需要30000秒,将近9个小时。考虑到服务器的整体负载和网络状况,时间可能会接近12小时。在这个时间尺度里,第二块盘,甚至第三块盘损坏的概率还是相当大的。而且,硬盘更换的时间取决于机房管理的响应能力,这往往是一个薄弱环节。
如果方案中让节点同hash区间一一对应,在节点内部,再将数据分散存储到一块磁盘中,那么当需要恢复副本时,节点将损坏的磁盘上的数据对象分散存放到其他磁盘上。这样,可以先发起修复,从其他副本那里都到数据,并发地向多个磁盘恢复数据,磁盘的吞吐限制会得到缓解。但问题的解决并不彻底。首先,网络会成为瓶颈。1个千兆网口最多支持到120MBps,这个速率也仅仅比磁盘快一倍,而且实际使用中也不能将带宽全部用光,否则影响服务,毕竟其他硬盘还是好的,还需要正常工作。原则上可以通过增加网口数量拓宽吞吐量,但这增加了网络设备方面的成本。而且这也仅仅是增加3、4倍的吞吐能力,我们真正需要的是近10倍地缩小恢复时间。至于光纤,那不是普通公有云存储所能奢望的。即便网络吞吐问题解决了,还有一个更核心的问题。因为数据在节点内部任意分散到各磁盘上,那么节点就需要维护一个key->磁盘的映射,也就是节点内的局部元数据。这个元数据需要服务器自己维护,由于单台服务器的资源有限,元数据的可靠性、可用性和一致性的维持非常麻烦。一套存储一套元数据存储已经够麻烦的了,何况每个节点一套元数据。一旦这个元数据丢失了,这个节点上所有的数据都找不到了。理论上,这个元数据丢失了,也不会影响全局,可以利用一致性哈希算法到其他副本那里恢复出丢失的数据。但不得不将原来服务器上的所有数据都传输一遍,这个数据量往往有一二十T,恢复起来更困难。更现实的做法是直接扫描各磁盘,逆向地重建元数据。当然,这将是一个漫长的过程,期间整台节点是不可用的,此间发生的写入操作还需的事后重新恢复(具体参见本文“可用性”部分)。
副本恢复最快,影响最小的方法就是,不让数据对象副本绑死在一台节点上,只要数据对象可以存放到任意一台节点,那么便可以在节点之间做多对多的数据副本恢复。集群规模越大,速度越快,效果越好。基于元数据的方案,每一个对象同节点,乃至硬盘的映射是任意的,在副本恢复方面,有得天独厚的优势。而一致性哈希严格地将数据同节点或磁盘绑定,迫使这种并发无法进行。
有一些基于混合方案的衍生方案可以解决一致性哈希在副本修复速度问题:将哈希环划分成若干slot(bucket,或者其他类似的称谓),数量远远大于将来集群可能拥有的节点数,或磁盘数。(好吧,我们说过规模无限,无限自然不可能,只要足够大就行了,比如2^32)。slot到节点或者磁盘的映射,通过一个映射表维护。各副本集群中的slot分布互不相同。当一个磁盘损坏时,找出所包含的slot,将这些slot分散到其他节点和磁盘上去,这样便可以并发地从其他节点以slot为单位恢复副本。在损坏磁盘更换之后,再将一些slot,可以是原来的,也可以是随意的,迁移到新硬盘,以平衡整体的数据分布。迁移的时候,副本已经恢复,迁移操作的时间压力就很小了,可以慢慢来,磁盘吞吐的瓶颈也不会有什么的影响。
但相比之下,元数据方案修复副本之后不存在数据迁移这一步,在这方面对象级元数据的存在使副本恢复简单很多。
同数据可靠性相关的一个问题是一致性。一致性问题可以看作可靠性问题的一个部分。因为当各副本的数据版本不一致时,便意味着数据对象的当前版本是缺少副本的。(实际上,从存储角度来讲,一个数据对象的不同版本,就是不同的数据)。确保一个数据对象的一致性最实用的方法是所谓W+R>N。即N个副本中,一个版本写入时确保有W个成功,而读取时确保有R个成功,只要满足W+R>N的情况,便可以保证始终可以读到最后写入成功的版本。
W+R>N的使用有一个问题,需要并发地从所有副本上尝试读取数据,然后通过读取的数据对象版本(或者时间戳)的比对,以确定是否满足一致性公式的要求。如果所读取的数据有几十上百MB,甚至上G的对象,一口气读出所有副本,而最终只取其中一个,着实浪费,系统压力会整整大上N倍。解决的方法是先做一次预读,读出所有副本的版本信息,进行一致性比对,确定有效副本后,再行数据本身的读取。
元数据方案在一致性上简单的多。元数据为了保证可靠,也会使用多副本。因为元数据很小,可以保持更多的副本数,比如5个,甚至7个。如此多的副本,基本上不必担心其可靠性。重点在于一致性。同样也是采用W+R>N的策略,但却将元数据读取和一致性保障在一次访问中解决。对于数据存储服务器而言,任务更多地是在保障每一个副本和版本的完整。
随着时间的推移,数据会发生退化,有各种原因造成副本的丢失。一致性也是一样。对于热数据,经常被访问,存储数据出错很快就会发现。但冷数据需要依靠定期检查发现错误。这个核对工作在元数据方案里,就是比对元数据和节点的每个盘上的对象清单,元数据保存的永远是最新版本,只要不匹配,就可以认定出错,立刻加以修复。但在一致性哈希方案中,需要交叉核对一个哈希区间的三个副本所包含的对象清单,以确定哪个对象是最新副本。然后再修正数据问题。
当一个节点由于种种原因下线,那么期间的所有写入操作都无法完成。此时,元数据方案处理起来很简单:另外挑选一台合适的节点,写入副本,更新相应的元数据项后,操作完成,一切太平。
一致性哈希方案要复杂得多。对于一个哈希区间的一个副本,被固定在一个节点上。换句话说,一组副本必须存放在特定的一个节点上,不能随意放置。如果需要定位到其他节点上,必须整区间的迁移。这个特性的结果就是,当这台节点下线后,无法写入相应的副本,也无法随意地将副本写到其他节点上。后续处理有2种方法:1、将副本或者key写入一个队列,等节点恢复后,发起修复操作,从其他的副本获得数据,补上缺失的副本。问题是这个队列必须有足够的可靠性,否则待修复key丢失,相应的对象会长时间缺少副本,直到数据一致性检测发现问题。这会增加一致性检测的压力,使得原本就复杂的过程雪上加霜;2、按一定规则写入其他节点,待恢复后原样迁移回来。这个方案相对简单些,但整个调度逻辑比较复杂,涉及到数据节点之间的协调。但是这种点到点的数据恢复,会给暂存服务器产生压力,不利于稳定运行。不管哪种方案,数据迁移或者复制都是免不了的。这中间的异常处理,负载控制等都需要花费心思。
元数据方案在节点失效时的处理要比一致性哈希方案单纯简洁的多。节点失效是云存储的常态,处理方式越简洁越好。在大集群中,几个节点同时下线都是很平常的事,一致性哈希如此复杂的节点失效管理,就是运维的地狱。
6. 可用性和可管理性
可用性在某些方面同可靠性有着共同的 解决方案 ,比如多副本可以消除单点,提高可用性。但是它们在其他方面却存在着矛盾。当一个副本写入失败,从可靠性角度而言,应当设法重试,或者索性告诉用户写入失败。但这样势必造成响应变慢,或者可用性降低。(响应变慢超过一个程度后,便会被认为是失败,不管最终是否成功)。此外,为了保障可靠性的诸多措施,比如副本修复,数据迁移等,会消耗系统资源,从而影响到可用性。
在上面对于可靠性的分析中可以看到,由于副本被绑定在特定节点上,一致性哈希要确保同元数据相当的可靠性的话,不得不放弃一些可用性,将有副本写入出错的操作返回失败。因为元数据方案的数据存储位置没有限制,对于多数的副本写入失败可以通过重选服务器得到更正。一致性哈希则无此便利,要么放弃一定的可用性,要么承担可靠性的风险。
根本上而言,一致性哈希在副本写入上制造了局部的隐式单点,尽管是短期的或者临时的,但依旧会对系统产生影响。一个设计良好的分布式系统会尽量地减少单点出现的可能性,这直接关系到系统的持续和瞬时可用性。元数据方案可以确保数据分布上不存在任何形式的单点。对于一个对象而言,任何节点都没有特殊性,这种无差别化才能真正保证消除单点。
在云存储系统中,使用公式R+W>N的地方,便是影响系统可用性的核心要点。可用性和一致性往往是一对冤家。在这个地方,需要向N台服务器同时发出请求,而最终的有效性取决于服务器反馈的情况。一般来说,N越大,越容易在确保一致性的前提下,维持可用性。(实际上是取决于X=R+W-N的值,越大越好。这个值表示当X+1个副本下线或丢失后,系统将不能保证临时的或永久的一致性)。但是,N-R和N-W越小,对可用性的影响越大。如果N=R,那么只要有一台服务器下线,会造成系统不可读取。如果N-R=3,那么即便是3台服务器下线,系统还能正确读取。对于W也一样。在这里,一致性和可用性是一对矛盾。当副本数足够多(N数可以很大),可以很容易地获得较高的X数。比如N=7, R=W=5,X=3,N-R=2,N-W=2,意味着即便有2台服务器下线,也能保证读写的有效性,同时确保一致性。
但如果N=3,无论如何也只能让R=3或者W=3,此时尽管可以获得X=3的一致性保障级别,但即便一台服务器下线,也会造成系统不可用。如果R=W=2,可以保证1台服务器下线,系统还是可用,但一致性保障级别降低到了X=1。
在元数据方案中,元数据的副本数(N)可以大些,故障和异常造成的副本下线,或者丢失,对系统的可用性和一致性产生的影响很小,对于这类问题的处理的紧迫性会低一些。相反,一致性哈希的一致性依赖于数据存储节点,面对大大小小的数据对象,无法使用很多副本,通常都会用3。在这个N数下,无论一致性还是可用性,都很脆弱。
对于可用性而言,最后,也是最重要的一点是运维管理。运维的好坏直接决定了可用性。前文已述,一致性哈希在一些关键的系统维护点上相比元数据方案多出不少环节。在同样运维水平和强度下,一致性哈希更加容易出现可用性问题。这方面对可用性的影响很难明确地给出量化的评判,但是运维是云存储系统高可用保障的核心,运维的复杂性往往决定了最终9的个数。
现在来看看元数据方案的运维特点。元数据方案相比标准一致性哈希方案多出了一个元数据存储系统。元数据通常有更多的副本,副本数越多,一致性的维持越发困难。一般的方案都是依赖异步执行的版本同步机制,尽快同步各副本。但是,为防止同步失效,会定期全面扫描核对所有元数据,确保元数据不会退化。这个过程伴随者很大的数据吞吐和计算量。但这个过程是离线操作,并且没有非常严格的时间要求,即便失败,也不会过多地影响系统的运行。它的时间裕度比较大。元数据量不会很大,在合适的算法下,一致性比对的时间也不会很长,通常不超过2、3小时。并且可以根据需要增减所需的服务器数量。
一致性哈希运维的重点则在另一头。一致性哈希模型及其各种变形由于需要确保数据平衡,不得不在扩容时进行大范围的数据迁移。在不影响服务的情况下,迁移速率必须受到限制,导致迁移时间很长。但有时数据迁移的时间要求很高。因为在迁移完成之前,迁移的数据源所占的空间还不能回收。此时所添加的空间暂时无法使用。而且数据回收也需要花费时间。在可用空间紧迫的情况下,迁移速度的压力会很大。一旦迁移过程中出现异常,将会雪上加霜。在长达几天的数据迁移过程中发生磁盘损坏的可能性非常大,修复操作将迫使数据迁移减速,甚至停止。修复的优先级更高。迁移操作涉及很多服务器的并发,协调和控制工作很复杂。而且不同的迁移原因会有不同的迁移策略。再加上各种异常处理,运维管理内容很多,将是非常痛苦的事。随着规模越来越大,最终可能超出运维能力的极限,无力维持存储的可用性。
元数据方案在绝大多数情况下不会进行数据迁移。少掉这样一个复杂重载的环节,对于系统维护的压力会小很多。
因此,尽管元数据方案多出了一个元数据的存储集群,但它相比一致性哈希方案反而更容易维持可用性。
7. 性能
性能是大多数程序员都关心的东西,往往被放在很高的位置。但在云存储中,性能反倒是级别较低的问题。存储是io密集型的系统,无论如何优化代码,都不可避免地受到物理设备的限制。
云存储的性能要点首先在于并发。做好并发,确保所有请求不会相互间阻塞,便可以从大的方面保证性能。元数据方案中,元数据存取存在大量的并发访问。每个用户的数据访问请求,在这里会转化成N个并发请求。系统不得不管理巨大数量的并发操作,并且小心维护并发之间的逻辑关系。传统上使用的线程池很难满足需要,不得不采用异步模型,从而增加开发难度。
一致性哈希在这方面有自身的优势。因为N数不大,并发的放大效应相比元数据方案要小很多。当然前面也说了,小N数会减小一致性的裕度,也不利于可用性。
云存储性能优化另一个重要的要点是,减少一次用户请求中跨服务器访问的次数。跨越网络的访问必然存在延迟,跨的次数越多,整体延迟越大。同时也会增加网络的负担。元数据方案天生多一次元数据检索操作,因而在这方面处于劣势。但一致性哈希在读取对象时,也没有好到哪里去。前面讨论一致性的时候已经说过,为了执行W+R>N的逻辑,又不能读取所有副本,只能采取一次并发预读操作。这样一致性哈希的跨服务器访问次数也同元数据方案扯平了。只是并发数少一些。这是以降低容错能力为代价的。
元数据在性能方面的主要劣势是元数据的访问。系统对元数据的访问主要是检索操作,因此通常会采用数据库,或者具备检索功能的数据存储引擎。这些模块在重载情况下有性能限制。由于副本数多,简单地扩展服务器数量代价较大。好在这些部分的优化,特别是数据库优化,有很成熟的经验,花些功夫也是能做好的。
8. 其他
在功能方面,一致性哈希还有一个不足。尽管一致性哈希可以实现对象存储的基本功能,但存储服务有时需要提供一些额外的功能,比如列出一个用户所保存的对象key清单。如果有元数据,那么这些用户的信息可以保存在元数据里,需要的时候检索这些信息即可。但是,在一致性哈希方案中,所有的key在系统中已经被哈希,分散到所有服务器上。如果要获得用户的对象清单,那么就不得不在所有存储节点上执行相应的检索操作,想象一下一个几千,甚至几万台节点的存储集群,执行一次这样的操作,会是什么结果?
对于存放了大量数据对象的用户,列出对象清单的操作可能没有什么直接意义。或许不提供此类功能,用户也能忍受。但是,另一个用户相关的问题却无法回避。这就是计费。计费就是计算出一个用户所有的对象所占据的容量,然后制作成费用清单,提供给计费系统。
计费最简单的办法是通过用户的访问记录,累积出来,写入就加,删除就减。但是实际没那么简单。对于一个key的覆盖写入,那么必须先扣除原有的对象大小,然后再加上新对象的大小。
更重要的,由于访问记录可能出错或者丢失,这种相对计算方式随着时间的增长会产生累积误差。需要定期校准。校准的方式就是统计所有用户的对象大小,做绝对容量的计算。由于对象大小存放在所有数据存储节点中,这又是一个恐怖的全系统的数据扫描。同时还必须确保对象属性的一致性,以免计费出错。
计费涉及到钱,通常用户会非常敏感,应当尽可能不出错。校准需要尽量频繁。但是全集群的计费校准代价实在太高,还是应当尽量少做。于是这对矛盾怎么调和,对于开发运维人员都是个巨大的挑战。
在元数据方案中,计费所需信息都可以保存在元数据中,计费操作只局限在元数据上。元数据量少,可以直接将数据dump出来,在额外的服务器上计算。这种操作相对轻量很多,可以更频繁地执行,提高用户账单的准确性。
9. 总结
经过大体的分析,可以看到在仅考虑云存储功能需求的情况下,一致性哈希具备简单高效的优势。但是,大型的云存储系统的功能不是影响其架构的主要因素。包括规模、可扩展性、可靠性、可用性和一致性等方面的非功能性需求才是云存储需要重点考虑的地方。一致性哈希及其各种变形方案,在这些方面存在诸多缺陷。尽管每个方面的缺陷并非不可逾越的障碍,往往都可以通过一些设计和运维手段加以解决。但是系统复杂度和运维难度随之增加。最关键的,每个方面的问题整合起来所产生的叠加效应,会轻而易举地摧垮一个云存储系统。
一般而言,一致性哈希适合一些有既定规模,不太需要扩展,数据尺寸不大的场合。这些特性意味着一致性哈希无法很好地适用于大型的云存储系统。元数据方案尽管在架构上复杂些,但其优点是灵活。而这种灵活性更加适合云存储的需要。
更多推荐
已为社区贡献1条内容
所有评论(0)