每天驾驶101公路连接旧金山到门洛帕克的路段,广告牌上微笑的面孔向我保证,2004年的计算机世界一切安好。他们告诉我,网络和服务器可以自我防御、自我诊断、自我修复,甚至在完成所有这些内省之后,还有足够的计算能力来执行其所有者分配的任务。
然后,在到达办公室后,我重新认识到现实:每位IT经理、系统管理员和开发人员都在与计算复杂性的怪兽作斗争。最糟糕的情况是试图在当今复杂的堆栈中识别、找到根本原因并解决问题。无论他们的供应商是谁,我在世界各地交谈过的管理员看起来都不太像广告牌上的那些人,他们似乎对服务器的唯一其他需求可能是一个马提尼酒分配器。
虽然我们无需提醒复杂性给行业带来的成本,但值得思考的是:我们在通往自愈系统的道路上究竟走到了哪里?问题在多大程度上仍然是开放的研究,而不是供应商缺乏执行力或优先级?我们在硬件方面还是软件方面取得了更大的进展?考虑到我们现在拥有的软件,以及需要修改或重写软件的情况,我们能期望获得多么有用的解决方案?
为了正面解决Sun即将发布的Solaris 10版本中的这些问题,我逐渐认识到,虽然行业越来越关注正确的问题集,但现实远落后于炒作,我们还有很长的路要走。 越来越多的,操作系统和其他位于硬件、应用程序和管理员之间的系统级组件的设计者需要发挥至关重要的作用,以促进自愈计算机系统的真正飞跃。
我们可以利用三种基本力量来提高计算服务的可用性:我们可以提高单个组件(硬件或软件)的可靠性和弹性;我们可以引入冗余来应对组件故障;并且我们可以预测性地避免故障或减少恢复所需的时间。然而,行业中的一些重要趋势在很大程度上与提高组件可靠性和冗余的愿望背道而驰。首先,越来越多地使用来自不同来源的商品硬件组件来构建更便宜的系统。同样,现代软件堆栈也正在由商品组件构建——在许多情况下,来自开源或质量参差不齐的现成组件。其次,增加冗余的愿望常常与降低成本、管理难度和解决方案的复杂性,同时最大化其整体性能的需求相悖。
因此,虽然在前两个领域的改进对于任何整体解决方案都很重要,但自愈系统的有用性从根本上来说是关于在第三个领域取得重大进展:减少恢复时间并实施可以诊断、响应甚至预测故障的系统。Aaron Brown、David Patterson 和其他人也提出了类似的建议,即关注这一领域是实现整体可用性方面更显著进展的一种手段。 然而,自愈系统必须理解才能做出有效决策的宇宙的复杂性正在迅速增加。即使是一个基本的系统或刀片服务器也很快将拥有每个芯片多个处理器核心和每个核心多个硬件线程:Sun、AMD、IBM 和 Intel 都在努力工作。即使是部署在单个系统上的服务,其深度和复杂性也在不断增加:每个进程多个线程,每个组件多个进程,以及来自不同作者的各种组件堆叠在一起。
在这个新兴的世界中,每个系统都将能够通过支持更多具有更多内存、计算能力和 I/O 的应用程序服务来完成更多有用的工作。一种方法是简单地将单个系统作为恢复单元;如果任何东西发生故障,要么重启整个系统,要么故障转移到提供冗余的另一个系统。不幸的是,随着每个系统可用的物理资源越来越多,这种方法本质上是浪费的:如果您可以禁用特定的处理器核心、重启单个应用程序,或者在真正需要维修之前避免使用一部分宽敞的内存或特定的 I/O 路径,为什么要重启整个系统呢?细粒度恢复为系统管理员提供了更有效的计算资源和资金利用,并减少了最终用户的停机时间。
现代操作系统的用户和管理员的自愈功能将提供细粒度的故障隔离,并在可能的情况下重启任何遇到问题的组件——硬件或软件。为此,系统必须包括对系统上观察到的错误进行智能、自动、主动的诊断。诊断系统必须用于触发有针对性的自动化响应或引导式人工干预,以减轻特定问题或至少防止问题恶化。最后,这些新的系统功能必须与围绕更简单、更高级别抽象的新系统管理员模型连接起来。
您的操作系统提供线程作为一种编程原语,允许应用程序透明地扩展,并在添加多个处理器、每个芯片多个核心或每个核心更多硬件线程时性能更好。您的操作系统还提供虚拟内存作为一种编程抽象,允许应用程序随着可用物理内存资源的增加而透明地扩展。现在我们需要我们的操作系统提供新的抽象,这些抽象将启用自愈活动或服务中的优雅降级,而无需开发人员重写应用程序或管理员购买昂贵的硬件来试图绕过操作系统而不是与之协同工作。
然而,这些抽象的一个关键要求是,它们使自愈系统能够进行诊断并采取行动,就像您的医生一样,应该首先做到不造成伤害。操作系统和自愈软件只有在比以往更深入地了解硬件/软件依赖关系,以及更多地了解部署在之上的软件堆栈中的关系和依赖关系时,才能实施智能的自我诊断和自愈。为了了解原因,让我们考虑一个简单的例子。
内核可以通过处理各种类型的异常并决定终止进程或将异常传递给进程来检测任何正在运行的进程的故障(在 Unix 术语中,它可以向进程发送信号,例如 SIGSEGV 或 SIGBUS)。这些异常通常会导致进程默认终止,但也可能被更智能的应用程序拦截,以尝试在退出前清理或保存数据。从历史上看,此类信号表明存在编程错误;错误的进程试图从未映射或未对齐的地址读取(例如,解引用空指针或错误指针)。然而,在像 Solaris 这样的现代系统上,您还可以检测并尝试从进程访问内存时发生的异常中恢复,该内存具有底层双位 ECC(纠错码)错误,这种错误可以被检测到,但不能被保护 DRAM 的典型 ECC 代码纠正。类似的场景可能发生在处理器核心本身及其 L1 和 L2 缓存中的错误中,根据底层硬件的功能,可能实现不同程度的恢复。
现在最大的问题是:发出信号或终止进程是否仍然安全?如果您终止进程,可以重启它吗?如何重启?还有什么可能受到影响?我们需要向管理员解释什么?换句话说,自愈系统应该做什么?
为了说明问题的复杂性,让我们考虑几个仅与第一个问题相关的快速场景。如果受影响的内存区域未共享,并且该进程与系统上的其他进程没有关系,那么我们可以终止该进程,并简单地停止使用受影响的物理内存页,而不是将其返回到内核的空闲页列表以供使用。然而,正如我们之前观察到的,大多数进程不再是这样了。如果您只是终止该进程,那么它的突然死亡可能会导致多进程应用程序的一部分突然消失,从而导致应用程序死锁或行为异常。或者,该进程可能正在为其他进程提供某种类型的服务(例如,名称服务、数据库后端),这将导致其他应用程序中的级联故障。
如果您向进程发送信号,您也可能会遇到麻烦;如果信号处理程序包含在尝试打印消息或在清理时保存数据时访问同一块坏内存的代码,则可能会再次发生相同的错误。任何导致核心转储的信号都可能让管理员感到困惑——本例中的软件是硬件问题的无辜受害者,不应浪费时间尝试调试此特定核心文件或应用程序软件代码。
最后,如果错误发生在共享内存区域中,情况会变得更糟。许多现代多进程应用程序都包含自己的重启功能,其中父进程监视并重启其子进程。如果子进程因触摸共享内存而死亡并被重启,则新的子进程很可能立即再次触摸同一位置并重复死亡,再次导致应用程序故障或严重的服务降级。从这几个场景来看,您可能会倾向于说,“将足够的信息传递给应用程序,让它决定该怎么做”,并将问题推给应用程序开发人员或管理员。这使我们回到了最初的问题之一:下一代自愈技术是否需要每个人都重写他们的应用程序?处理那种信号听起来很复杂,容易出错,并且不具有特别的可移植性。
同时,我们的处理器或内存中的底层故障又如何呢?我们如何解决这个问题?管理员应该看到什么?我们的自愈系统不应说,“我由于物理地址 0x12345678 处的致命 ECC 错误而杀死了进程 ID 123”。管理员需要知道他们可以理解的内容:什么服务受到了影响,以及这如何影响其他服务;影响是什么(性能、功能);系统为我们做了什么(重启了什么、故障转移、使用了冗余);以及需要什么样的人工干预(我们还需要更换 DIMM 吗?多久更换?更换哪个?)。
这些示例强调了系统中需要新的抽象,这些抽象可以解决这些问题,而不会继续加重开发人员和管理员的负担。它们还表明,我们需要在两个不同的领域取得进展:快速、智能地响应系统中发生的错误的能力,以及异步地诊断观察到的错误以找出根本问题的能力。一旦自愈系统诊断出有故障的硬件组件或损坏的应用程序,它就可以使用此知识来触发操作,例如禁用组件、故障转移到冗余或通知人工管理员需要维修或补丁。
自愈系统是指能够:
• 用强大的错误检测、处理和纠正取代传统的错误消息,从而产生遥测数据以进行自动诊断
• 从硬件和软件实体的错误遥测数据中提供自动诊断和响应
• 基于对其依赖关系的了解,提供服务的递归细粒度重启
• 为已诊断出的问题及其对服务和资源的影响提供简化的管理交互
为了在这些问题上取得切实进展,在 Sun 公司,我们采用了双管齐下的方法来提高 Solaris 10 的可用性:我们实施了一个可扩展的故障管理器,它可以接收来自系统组件(包括内核)的遥测数据,并将其传递给自愈诊断和响应软件;并且我们实施了一个服务管理器,用于管理 Solaris 系统上服务的描述及其相互依赖关系,并且可以实施智能的自动重启。最后,我们引入了一种称为“契约”的新操作系统原语的概念,该原语允许服务管理器向内核描述对一组托管进程的恢复的期望。我将依次讨论它们,以说明每种方法的优点和挑战,并展示它们如何协同工作,为实现自愈迈出有益的第一步。
我们认为我们的模型可以扩展到任何系统或系统的分层网络组合。故障管理器本身就是一项服务,它接收系统观察到的传入错误遥测数据,并使用适当的算法或专家系统规则来尝试将这些错误自动诊断为根本问题,例如硬件故障或应用程序中可能存在的缺陷。服务管理器管理系统上运行的各种应用程序服务,并使用它们的依赖关系来实现有序的启动、关闭和重启。因此,虽然 Solaris 故障管理器和服务管理器处理 CPU、内存、I/O 设备和单系统服务等本地资源,但我们相信,在为刀片机架或联网数据中心设计自愈功能时,相同的概念也适用,在这些情况下,故障管理器将跟踪已知服务中断列表,而服务管理器将观察和管理提供给网络或数据中心的最高级别服务。
在我们的自愈设计中,故障管理器负责根据错误症状实现问题的异步自动诊断。然后,它使用每次诊断的结果来触发自动响应,例如离线 CPU、设备、内存区域或服务,或与人工管理员或更高级别的管理软件通信。因此,故障管理器管理系统上的问题列表,并将其作为抽象导出给人工管理员和更高级别的管理应用程序,而不是它收到的单个底层错误消息。虽然我们相信这种新的抽象层将大大降低复杂性,但它也构成了与传统错误模型重大变革的基础。
最初的 Unix 设计并未关注硬件或软件故障处理的机制:复杂的错误处理例程和故障诊断功能会损害其简洁的优雅性和可移植性。因此,即使是像错误日志记录这样的基本概念,Unix 中由 20 世纪 80 年代开发的 syslog 服务提供的,也只不过是进程间或联网的 printf(),并且自那时以来几乎没有发展。然而,这种模型已被证明对开发人员来说是一种持久且诱人的陷阱:在某种程度上,编写一个简单地为人类发出随机 printf() 字符串的应用程序总是比深入思考故障语义和用于自动诊断的数据更容易。最常见的情况是,管理员只能尝试使用 awk、grep 和 Perl 在错误日志中查找问题。
不幸的是,在来自现成组件的难以理解和怪异的错误消息之上构建自诊断系统,很可能会产生在诊断实际问题方面无效、在面对软件更改时脆弱,或两者兼而有之的东西。通常,即使消息对人类来说似乎很友好,但驱动智能自动诊断系统所需的原始信息根本不存在。在 Solaris 10 中,我们保留了传统的 syslog 工具,但创建了一个新的信息通道,用于将结构化的遥测事件(由简单且可扩展的协议描述)从内核传递到 Solaris 故障管理器。在已经存在结构化错误事件机制的其他系统中,将故障管理器开发为现有机制的客户端可能更有意义。系统设计人员还必须决定,在提供自动诊断时,是否真的从管理界面中删除了错误遥测数据。删除人类可读的错误消息作为转换为自动诊断的一部分,简化了用户交互,但也可能破坏具有旧行为丰富经验的用户的遗留脚本和期望。
故障管理器(如图 1 所示)记录每个事件,并将遥测数据传递给适当的诊断引擎,这是一个小型软件模块,它尝试诊断问题,然后触发自动响应。最终响应反过来可能包括发回 syslog 或发送到系统控制台供人类阅读的消息,但转换为遥测数据的底层错误不再被视为消息(如果需要,可以从日志中检索它们)。我们为 CPU、内存和 I/O 组件实施了诊断引擎;对于软件应用程序,我们专注于重启,如下所述。
一个示例诊断引擎检查内核发送的用于可纠正和不可纠正内存错误的错误遥测数据。虽然可纠正的内存错误对应用程序是透明的(典型的 DRAM ECC 代码允许纠正单位错误并将良好数据返回给处理器),但具有特定属性的一系列可纠正错误可能表明 DRAM 单元、行、列或连接器即将发生或已经发生故障。根据观察到的事件,我们的诊断引擎可能会要求内核停用特定的物理内存页,通过重新分配新的物理页来将其从正在运行的进程下重新映射出来。此更改对应用程序是透明的;如果页面是干净的,则可以复制旧数据或从页面的后备对象中重新读取。诊断引擎还可以为人类发出消息,请求更换 DIMM。
因此,故障管理器的设计为改进自动诊断的质量和有效性的研究提供了一个起点。自愈系统将需要对有效诊断硬件和软件问题的方法进行重要的基础研究。我们试图通过为系统中的可插拔诊断引擎提供简单的编程模型以及辅助其设计和测试的工具来促进这项工作。行业经验表明,可以在此处应用各种各样的方法,包括应用统计方法、专家系统、控制理论、生物识别计算等。
服务管理器用于在软件错误导致进程核心转储或进程成为不可纠正的硬件错误的受害者并且必须终止时,启用智能、快速的应用程序重启。必须向 Solaris 添加两个新功能才能实现此功能:首先,服务向服务管理器描述自身及其依赖关系的方式;其次,一种将每个进程与充当其重启器的另一个进程相关联的机制,并指示进程在发生不可恢复的硬件或软件错误时是可重启的。
传统的 Unix 系统使用一组 shell 脚本在系统启动时启动服务;这些脚本通常位于 /etc/init.d 和 /etc/rc*.d 目录中。不幸的是,虽然每个脚本通常都实现了一个简单的启动和停止机制,但它们不包含依赖关系的显式表达。脚本的字母顺序用于确定服务启动的顺序。例如,/etc/rc2.d/S10foo 在 /etc/rc2.d/S20bar 之前启动,但无法知道 bar 是否实际上依赖于 foo,或者开发人员是否只是随机选择了一个名称。即使不需要高级重启,这种机制多年来也导致了数十个错误,其中不正确的排序和新服务的引入导致了启动系统服务时的竞争条件和故障。对于重启,情况是绝望的:没有依赖关系,就无法知道要按什么顺序运行哪些脚本才能不造成伤害并实现重启。
细粒度重启的概念是现有研究的主题,但看到它在生产 Unix 系统中实现非常有趣。 我们将基本 Solaris 中的所有服务都转换为使用我们的服务管理器,图 2 显示了生成的服务和依赖关系图的一部分。每个矩形表示一个服务,每条边表示一个依赖关系。具有相似属性的依赖关系被分组在一起并分配名称;未显示属性。这有力地说明了在您甚至开始添加任何内容之前,现代系统上复杂依赖关系的 web。
要下载全尺寸图像,请点击此处。
传统 Unix 机制中依赖关系的缺乏也在其他地方得到了解决,最值得注意的是在 NetBSD 中,启动脚本用依赖关系语句进行了注释。 同样,Windows 提供了服务控制管理器,其中包括服务依赖关系并且可以执行单个进程的重启;Sun 的 Cluster 产品和其他供应商也实现了类似的机制。在检查这些现有方法并考虑我们未来对 Solaris 的需求时,我们确定了对任何服务管理器都至关重要的几个重要设计考虑因素
服务管理器必须提供一种服务向系统描述自身及其依赖关系的方法。 这可以通过编程 API(如 Windows 中)、随服务附带的 shell 脚本或代理(如 NetBSD 和 Sun 的 Cluster 中)或元数据(如 XML 文件,可能与 API 和/或脚本和代理结合使用)来完成。在 Solaris 中,现有的 shell 脚本机制对于我们的需求来说过于非结构化,并且不太可能在我们丰富服务描述以满足未来需求时很好地扩展。我们还希望在不修改应用程序源代码的情况下将现有应用程序调整为我们的服务管理器。出于这些原因,我们选择定义一个 XML 模式,并要求开发人员将 XML 文件与现有应用程序捆绑在一起作为其服务描述。
服务管理器必须提供一种进程故障通知的方式。 如果只需要支持单进程服务,那么使用现有的操作系统 API 等待进程退出可能就足够了。如果需要支持具有任意后代的多进程服务,则需要一个记录进程集合与其重启器之间关系的系统原语。通知机制还必须包含有关故障的足够信息,以实现知情的重启。在 Solaris 中,许多现有服务已经是多进程的,因此开发了一种称为“契约”的新系统抽象用于通知。
服务管理器可以选择性地提供一种将重启责任委托给另一项服务的方法。 委托可用于向整体服务重启工具添加具有不同策略的专用重启器,但对于单个重启器就足够的系统来说,可能不是必需的。我们的服务管理器支持委托,但要求参与的重启器使用编程 API 来接收事件。Solaris inetd(1M) 守护程序使用此 API,并且可以用于集群故障转移和其他目的。
通过编写适当的 XML 文件来伴随服务,大多数典型的现有 Solaris 服务在几个小时内就转换为我们的服务管理器。XML 文件描述了哪些脚本或命令实现了启动和停止功能,并包括文件、系统启动中的概念性里程碑和其他服务的结构化依赖关系列表。我们还为仅交付旧版脚本的服务提供兼容的启动和关闭。与错误消息一样,我们在服务转换方面的初步经验表明,虽然开发人员需要预先进行更多思考,但结果是与系统的更稳定交互,在面对定制和更改时不太脆弱,并通过细粒度重启为管理员和最终用户提供好处,否则这是不可能的。
我们的选择的主要后果之一是,通过对系统施加更高的秩序,我们要求其公民遵守一些新的规则和规定。例如,旧版串行启动允许开发人员在不知不觉中发布对依赖关系概念不正确的应用程序。由于转换如此广泛的现有应用程序和并行化启动,我们发现了几个预先存在的错误以及各种服务的依赖关系和稳健性问题。因此,虽然我们的新机制允许应用程序在不更改其源代码的情况下参与,但很明显,为自愈环境编写的软件必须能够容忍重启并具有对依赖关系的正确描述。将需要新的研究和工具来帮助应用程序开发人员编写和测试用于自愈环境的软件,其中包括细粒度重启。
当 Solaris 服务管理器执行启动每个服务的命令时,会设置一个新的内核资源,称为“契约”,以指示此命令的所有后代进程都是可重启的,并且可以在硬件或软件错误时终止,只要操作系统向契约中命名的重启器发送通知事件即可。因此,契约充当接收重启通知事件的机制,体现了每个进程与其重启器之间的关系,并告知内核进程可以在致命硬件错误时终止,就像我们最早的示例中一样。
契约本身以文件的形式出现在内核管理的伪文件系统 /system/contract/ 中,其中文件表示每个契约事件端点。如果重启器本身死亡,它可以被重启并重新获取其契约。在整个进程树的根部是 Unix init 进程,如果它死亡,内核会自动将其作为进程 ID 1 重启。契约设置决定了各种类型故障的行为:可以接收软件故障、硬件故障和正常进程退出行为的事件。使用这些事件,服务重启器可以确定进程是否因软件或硬件故障而死亡,并做出相应的反应。每个契约都可以配置为终止相应进程组或整个契约中的所有进程(如果其中任何一个失败),从而避免多进程应用程序中的死锁和不一致,并允许整个服务安全重启。
如上一节所述,服务管理器还可以将重启责任委托给后代。委托是通过允许委托的重启器管理与其自身后代关联的契约来实现的。委托 API 还允许复杂的应用程序通过直接响应事件来承担其故障行为的责任,并且它们允许其他类型的服务管理器(如集群故障转移软件、批处理排队系统和网格计算引擎)参与重启。
我们使用委托为传统的 Unix Internet 服务(如 telnet、ftp 和 rlogin)提供重启;inetd 守护程序充当这些服务的委托重启器,除了侦听各种套接字之外。图 3 显示了重启器和进程的示例层次结构,包括 Unix init、服务管理器守护程序 svc.startd 和 svc.configd 以及其他。
Sun 公司在为 Solaris 10 设计可用性特性方面的经验表明,要通过适当利用操作系统来推进自愈技术的最新水平,还需要在创建新的抽象概念方面进行大量工作。只有交付这些新的抽象概念,我们才能充分受益于基础研究在提高可靠性和诊断方面的进展,通过正确的恢复和预测性问题避免来提供更高的可用性,并为开发人员和管理员提供简单而真实的自愈能力。
1. 请参阅 http://sun.com/software/solaris/10/ 和 http://sun.com/msg/。
2. Brown, A., 和 D. Patterson。2001 年。“拥抱失败:面向恢复的计算 (ROC) 的案例”。高性能事务处理研讨会,加利福尼亚州阿西洛玛(2001 年 10 月);请参阅 http://roc.cs.berkeley.edu/。
3. Candea, G., 和 A. Fox。2001 年。“递归可重启性:将重启锤变成手术刀”。第 8 届操作系统热点主题研讨会论文集(2001 年 5 月);请参阅 http://i30www.ira.uka.de/conferences/HotOS/。
4. Mewburn, L。2001 年。“NetBSD rc.d 系统的设计与实现”。2001 年 Usenix 年度技术会议论文集,马萨诸塞州波士顿(2001 年 6 月);请参阅 http://www.mewburn.net/luke/papers/。
喜欢还是讨厌?请告诉我们
[email protected] 或 www.acmqueue.com/forums
MIKE SHAPIRO 是 Solaris 内核开发部门 RAS(可靠性、可用性、可维护性)特性的高级工程师和架构师。他领导了 Sun 预测性自愈架构的设计和构建工作,并且是 DTrace 的共同创建者。他对 Solaris 的贡献包括 DTrace D 语言编译器、内核崩溃子系统、fmd(1M)、mdb(1)、dumpadm(1M)、pgrep(1)、pkill(1) 以及对 /proc 文件系统、核心文件、崩溃转储和 Solaris 硬件错误处理的众多增强功能。他拥有布朗大学计算机科学硕士学位。
© 2004 1542-7730/04/1200 $5.00
最初发表于 Queue vol. 2, no. 9—
在 数字图书馆 中评论本文
Phil Vachon - 王国之钥
一次不幸的手指误操作引发了当前的危机:客户意外删除了签署新固件更新所需的私钥。他们有一些令人兴奋的新功能要发布,以及通常的一系列可靠性改进。他们的客户越来越不耐烦,但当被问及发布日期时,我的客户不得不拖延。他们如何才能提出一个有意义的日期?他们已经失去了签署新固件版本的能力。
Peter Alvaro, Severine Tymon - 将天才从故障测试中解放出来
本文呼吁分布式系统研究界改进容错测试的最新水平。普通用户需要能够自动选择定制故障注入的工具。我们推测,超级用户选择实验的过程可以在软件中有效地建模。本文介绍了一个验证这一推测的原型,展示了来自实验室和现场的早期结果,并确定了可以使这一愿景成为现实的新的研究方向。
Pat Helland, Simon Weaver, Ed Harris - 大到不能倒
Web 规模的基础设施意味着大量服务器协同工作,通常是成千上万台服务器朝着相同的目标努力。如何管理这些环境的复杂性?如何引入通用性和简单性?
Steve Chessin - 注入错误以获得乐趣和利润
不幸的是,任何带有移动部件的东西最终都会磨损和发生故障,电子电路也不例外。当然,在这种情况下,移动部件是电子。除了电迁移(移动的电子逐渐将金属原子推出位置,导致导线变细,从而增加其电阻并最终产生开路)和树枝状生长(相邻导线之间的电压差导致位移的金属原子相互迁移,就像磁铁会相互吸引一样,最终导致短路)的磨损机制外,电子电路也容易受到背景辐射的影响。