当没有冗余反而更可靠时——冗余的迷思
风险是一个难以把握的概念,要恰当地评估特定场景,需要大量的训练、思考和分析。由于风险评估往往十分困难,我们常常用单纯增加基本冗余来替代风险分析,并假定自己已经适当地缓解了风险。但事实往往并非如此。冗余的增加常常伴随着复杂性或额外故障模式的引入,而这些新的故障形式有可能带来的风险,比所增加的冗余所消除的风险还要多。存储系统尤其容易陷入这类决策过程,这很令人遗憾,因为几乎没有哪种系统像存储那样既如此容易发生故障,又如此重要而需要加以保护。
RAID 是一个绝佳的例子,说明缺乏全局性的风险思维会如何导致一些奇怪的决策。如果我们看一个并不罕见的场景,就会发现防范磁盘故障这一目标实际上可能导致风险的上升,即便施加了额外的冗余也是如此。在这个场景中,我们将比较一个由十二块三 TB SATA 硬盘组成的单一阵列。我们常常听到有人在这种情形下选择 RAID 5,以求获得“最大的容量和性能”,同时又“对故障有足够的防护”。
这里的想法是,RAID 5 可以防范单块磁盘的丢失,故障磁盘可以更换,而且阵列会在第二块磁盘发生故障之前完成自我重建。这在理论上很美好,但对于这种规模、容量达三十六 TB 的阵列而言,真正的风险并不像人们通常猜想的那样来自多块磁盘同时故障,而是来自在单块磁盘故障后无法可靠地重建阵列,或者来自在没有任何单块磁盘发生故障的情况下阵列本身的故障。第二块磁盘发生故障的风险是低的,并非不存在,但相当低。如今的磁盘高度可靠。一旦一块磁盘发生故障,确实会增加第二块磁盘发生故障的可能性,这一点有充分的记载,但我不希望这种风险误导我们,使我们偏离对真正风险的审视–即重建操作失败的风险。
在 RAID 5 重建操作期间令我们担忧的,是可能发生不可恢复读取错误(URE)。一旦发生,重建操作便会停止,阵列陷入无法使用的状态–阵列上的所有数据都会丢失。在常见的 SATA 磁盘上,URE 发生率为 10^14,即每读取十二 TB 数据便会发生一次。这意味着一个正在重建的六 TB 阵列,大约有百分之五十的概率遭遇 URE 而失败。百分之五十的失败概率高得离谱。设想一下,如果你的汽车每次行驶都有百分之五十的概率掉轮子。所以,对于一个使用 10^14 URE SATA 磁盘的小型(以今天的标准而言)六 TB RAID 5 阵列,假如我们丢失了一块磁盘,即便立即更换磁盘,我们也只有百分之五十的概率使阵列得以恢复。这还没有把第二块磁盘故障的风险算进去,仅仅是 URE 失败的风险。它还假定该磁盘除了重建操作之外完全处于空闲状态。如果这些磁盘在重建的同时还在忙于处理其他任务,那么发生不测——无论是 URE 还是第二块磁盘故障——的概率便会急剧上升。
对于一个十二 TB 的阵列,重建操作期间彻底丢失数据的概率开始接近百分之百–这意味着在那种情况下 RAID 5 根本毫无作用。总归还是有幸存的机会,但概率非常低。在六 TB 的情况下,你可以把一次重建操作比作一场俄罗斯轮盘赌:一颗子弹,六个弹膛,而你必须扣三次扳机。到了十二 TB,你就得扣六次!这些都不是好赔率。
但我们谈论的并不是十二 TB 的阵列。我们谈论的是一个三十六 TB 的阵列–这听上去很大,可这却是如今某个人在家里都能轻易拥有的容量,更不用说在企业中了。如今每一家主要的服务器制造商,以及几乎所有低成本存储厂商,都在生产这一容量区间、售价不到一万美元的存储系统。在一个三十六 TB 的阵列上、因单块磁盘故障而重建 RAID 5 阵列,就好比玩俄罗斯轮盘赌:一颗子弹,六个弹膛,而你要扣十八次扳机!你的数据几乎没有什么生还的机会。再加上重建如此规模的阵列所需的惊人时长,在那段重建窗口期内第二块磁盘发生故障的风险便开始成为一个相当严重的威胁。我见过一些估算,某些系统上的重建时间高达数周乃至数月。要在这么长的时间里运行而不能再失去任何一块磁盘,实在太久了。当我们谈论的是数小时或数天时,风险相当低,但依然存在。而当我们谈论的是数周或数月的持续摧残时——因为重建操作对磁盘的负荷极重——故障率便会急剧攀升。
对于这种规模的阵列,我们实际上可以认为,单块磁盘的丢失就意味着整个阵列的丢失,从而使我们根本得不到任何磁盘故障防护。现在,如果我们换用性能相同或更优、容量相同或更大的磁盘,组成同样对磁盘丢失没有防护的 RAID 0,我们只需用到十一块磁盘,而非组成 RAID 5 阵列所需的十二块同样的磁盘。这意味着,我们不再是十二块硬盘——每块大约有百分之三的年故障率——而只有十一块。仅此一点就使我们的 RAID 0 阵列更可靠,因为可能发生故障的磁盘更少了。我们不仅磁盘数量更少,而且无需写入奇偶校验块,回读时也无需跳过奇偶校验块,从而在相同利用率下让 RAID 0 阵列的机械磨损极其轻微地降低,赋予它一点点额外的可靠性优势。这个十一盘的 RAID 0 阵列在容量上将与十二盘的 RAID 5 阵列完全相同,但吞吐量和延迟会略好一些。这是一个全面的胜利。外加少用一块磁盘所节省的成本。
因此,我们在这里看到的是,在大型阵列(指容量大,而非磁盘数量多)中,RAID 0 在某些场景下实际上超越了 RAID 5。当使用常见的 SATA 磁盘时,这种情况甚至在家庭高级用户和许多小型企业所遇到的容量上就会出现。如果我们改用企业级 SATA 磁盘或 SAS 磁盘,那么出现这种情况的容量数值就会变得非常高,在今天还不足为虑,但再过几年,当磁盘容量变得更大时,就会成为问题。然而这恰恰凸显出 RAID 5 在我们今天所见的容量规模下是何等危险。人人都明白 RAID 0 那骇人的风险,但要让人理解 RAID 5 的问题严重到它实际上可能不如 RAID 0 可靠,却可能相当困难。
仅仅就重建操作而言,RAID 5 在这种规模的阵列中可能不如 RAID 0 可靠,而这还只是个开始。在这样一个庞大的阵列中,重建时间可能耗费如此之久、对磁盘造成如此沉重的负荷,以至于第二块磁盘故障也开始成为一种可测量的风险。此外,还存在由阵列控制器错误所引发的额外风险,这类错误可能利用重建算法摧毁整个阵列,即便此时并没有任何磁盘发生故障。由于 RAID 0(或 RAID 1、RAID 10)并没有重建算法,它们不会遭受这一额外风险。这些风险很难量化,但重要的是,它们都是额外的风险,当你使用一个更复杂的系统时便会层层累积,而一个没有冗余的、更简单的系统从一开始就更可靠。
既然我们已经确立了 RAID 5 可能不如 RAID 0 可靠,我接下来要指出 RAID 0 那些显而易见的危险。RAID 总体上是用来缓解单块孤立硬盘故障的风险的。我们都惧怕单块磁盘就这么故障、所有数据随之丢失。RAID 0 作为一大串没有任何冗余形式的磁盘条带,将单块磁盘故障导致数据丢失的风险,放大到了若干块磁盘之上,其中任何一块磁盘故障都会导致所有磁盘上数据的彻底丢失。所以,在我们上面那个十一盘的例子中,如果十一块磁盘中的任何一块发生故障,一切便都付诸东流。不难看出,这比单单使用一块孤零零的磁盘要危险得多。
我在这里试图指出的是,冗余并不等于可靠。某样东西是冗余的,比如 RAID 5,这并不能保证它总是比某样并非冗余的东西更可靠。
我在这里最喜欢的类比是看龙卷风中的房子。在第一种情形里,我们用砖块和灰浆盖一座房子。在第二种情形里,我们盖两座冗余的房子,用稻草建成(看来我们的建造者是几头猪。)当龙卷风(或大灰狼)袭来时,哪一种更有可能给我们留下一座屹立不倒的房子?显然,一座砖块灰浆的房子相比冗余的稻草房子拥有某些显著的可靠性优势。冗余无关紧要,最终起作用的是可靠性。
冗余常常具有误导性,因为它易于量化却难以定性。冗余是一个非黑即白的问题:它是冗余的吗?是或否。简单明了。可靠性则没有这么简单。可靠性关乎故障率和可能性。它关乎统计与分析。由于很难以有意义的方式量化可靠性,尤其是在向业务人员推销一个项目时,冗余便常常成为这一复杂概念的一个简单替代品。
用冗余来转移对可靠性的质疑,这种做法最终还会以非常曲折的方式应用到子系统上。人们不去让一个“系统”变得冗余,而是渐渐习惯于让一个高度可靠且低成本的子系统变得冗余,并把子系统的冗余当作适用于整个系统。其中最常见的例子就是 SAN 产品中的 RAID 控制器。厂商往往不去配置一个冗余的 SAN(即两台 SAN),而是让那个在普通服务器中并不常做冗余的部件变得冗余,然后就声称这台 SAN 是冗余的–意思是一台包含冗余的 SAN,而这与前者完全不是一回事。
这里一个恰当的类比是,把拥有冗余汽车——即两辆完整、可用的汽车——与拥有一辆后备箱里放着一只备用水泵、以防主水泵失灵的汽车相比较。显然,一只备用水泵不是坏事。但与备好第二辆随时可开的汽车相比,它对汽车故障所提供的防护实在微不足道。在前一种情形里,整个系统都是冗余的,包括车身底盘。而在另一种情形里,我们只是让车身底盘内部一个高度可靠的部件变得冗余而已。它甚至比不上备一只备胎——备胎好歹是一个故障可能性更高的汽车部件。
正如 RAID 5 可靠性的迷思以及系统/子系统可靠性的迷思一样,SAN 和 NAS 这类共享存储技术也常常以同样的方式被对待,尤其是在涉及虚拟化的时候。有一个常见的场景:人们着手一个虚拟化项目,随即本能地惊慌起来,因为单台虚拟化主机代表着单点故障——一旦它发生故障,许多系统将同时全部宕掉。
使用“单点故障”这个词会引发一种恐慌情绪,是引导一场对话走向的绝佳手段。但我们爱称之为 SPOF 的单点故障,虽然是我们在可能时乐于消除的东西,却未必是世界末日。想想我们那座砖房。它是一个 SPOF。我们那两座稻草房子则不是。然而,一阵微风摧毁我们冗余方案的速度,比摧毁我们那可靠的 SPOF 还要快。寻找 SPOF 是一种发现系统中脆弱点的好办法,但不要觉得在每一种场景中每一个 SPOF 都必须做成冗余的。大多数企业会发现,让许多 SPOF 各就各位才能获得最佳价值。我们真正的目标是在适当成本下实现可靠性,而正如我们所见,冗余并不能替代可靠性,它只是我们可以用来实现可靠性的一种工具。
许多人在做虚拟化时所遵循的理论是,他们拿起自己的虚拟化主机,然后说“这台主机是一个 SPOF,所以我需要有两台,并使用高可用性特性来实现透明的故障切换!”这种想法之所以被激起,是因为那家领先的虚拟化厂商赚钱首先靠的是出售昂贵的 HA 附加产品,其次靠的是它隶属于一家大型存储厂商–因此,兜售并非必要、甚至可能危险的额外共享存储对他们来说是一大笔金钱上的进账,而这很可能正是他们从一开始就大力倡导虚拟化领域的原因。带共享存储的冗余虚拟化主机听上去很美,但出于若干原因,它可能是极其被误导的。
第一个原因是,被移除的最初那个 SPOF——虚拟化主机——被一个新的 SPOF——共享存储——所取代。这毫无成效。假定我们使用的是质量相当的服务器和共享存储,我们所做的不过是改变了风险所在的位置,而非改变它的大小。存储系统发生故障的可能性大致等于原先那台服务器发生故障的可能性。但除了像在玩贝壳游戏一样把 SPOF 挪来挪去之外,我们还做了某件糟糕得多的事情–我们引入了链式或级联的故障依赖关系。
在我们最初的场景中,我们只有一台服务器。如果服务器保持正常运转,我们就没事;如果它发生故障,我们就完了。简单明了。现在我们有了两台虚拟化主机、一台单独的存储服务器(SAN、NAS,或别的什么)以及一个把它们连接起来的网络。我们已经确定,共享存储发生故障的风险大致等于我们最初场景中整个系统的风险。但如今我们又多了网络以及那两个前端虚拟化节点这些额外的依赖关系。这些部件中的每一个都比脆弱的共享存储更可靠(任何带机械硬盘的东西都将是脆弱的),但它们风险较低并不是问题所在,问题在于这些风险是会组合叠加的。
只要这三个部件(存储、网络或前端节点)中的任何一个发生故障,一切便都会随之故障。对此的解决之道是让共享存储自身变得冗余,并让网络自身变得冗余。只要投入足够的工作,我们就能克服由于增加共享存储而引入的脆弱性和风险,但共享存储本身并不是一种风险缓解手段,而是一种风险本身,它必须被加以缓解。复杂性的螺旋就此开始,而要把这个新系统提升到与原先那个单台服务器系统同等可靠性所付出的相关成本,可能是天文数字。
既然我们如今有了所有这些冗余,我们就还有一个风险需要操心。管理所有这些冗余、所有这些活动部件,所需的知识、技能和准备,要远远多于管理一台简单的单台服务器。我们已经从一个简单的解决方案走向了一个极其复杂的方案。以我自己的亲身经历来看,这类解决方案真正的危险并非来自硬件故障,而是来自人为失误。我们不仅几乎没有采取什么措施来避免人为失误导致这个新系统发生故障,反而还增添了无数个人可能会意外地把整个系统——连同所有冗余——一举搞垮的环节。我曾亲眼目睹;我也听说过那些惊心动魄的故事。系统越复杂,人就越有可能意外地搞砸一切。
作为 IT 专业人员,至关重要的是我们要退后一步,审视完整的系统,权衡可靠性与风险,并把冗余仅仅看作在追求可靠性过程中所使用的一种工具。冗余本身并非万灵药。简单性同样不是。可靠性是一个需要应对的复杂难题。避免简单化的替代做法,是从掩盖可靠性问题转向正视并解决它们的重要的第一步。
