第十章 节点故障模式
此模式着重描述应用程序在其运行的计算节点关闭或发生故障时应如何响应。
此模式反映了运行在因软件或硬件问题而关闭或突然发生故障的节点(虚拟机)上的应用程序代码的视角。应用程序承担三项职责:在节点发生故障时做好准备以最大程度地减少问题;优雅地处理节点关闭;以及在节点发生故障后进行恢复。
一些常见的关闭原因包括:应用程序因自身故障而无响应、云供应商管理的例行维护活动以及应用程序启动的自动扩展活动。故障可能由硬件故障或应用程序代码中未处理的异常引起。
尽管节点关闭或故障的原因有很多,但我们仍然可以统一处理它们。处理各种形式的故障就足够了;所有关闭场景也都会得到处理。此模式的名称源于更广泛的节点故障。
背景
节点中断模式能够有效应对以下挑战:
- 您的应用程序采用以队列为中心的工作流程模式,并且需要对跨层发送的消息进行至少一次处理
- 您的应用程序采用自动缩放模式,并且需要优雅地关闭正在释放的计算节点
- 您的应用程序需要优雅地处理突发的硬件故障
- 您的应用程序需要优雅地处理意外的应用程序软件故障
- 您的应用程序需要优雅地处理由云平台发起的重启
- 您的应用程序需要足够的弹性来应对计划外计算节点丢失的情况,且不会造成停机
所有这些挑战都会导致需要解决的中断,其中许多挑战的解决方案存在重叠。
云的重要性
云应用程序会遇到导致节点关闭的故障。云原生应用程序会预料到这些故障,并在收到云平台的关闭通知时做出相应的响应。
影响
可用性、用户体验
机制
此模式的目标是允许单个节点发生故障,同时保持应用程序整体的可用性。需要考虑多种故障场景,但所有场景都具有一些共同的特征,例如故障准备、节点关闭处理(如果可能)以及恢复。
故障场景
当您的应用程序代码在虚拟机中运行时(尤其是在通用硬件和公共云平台上),可能会出现多种导致关闭或故障的场景。虽然还有其他潜在场景,但从应用程序的角度来看,这些场景通常类似于表 10-1 中列出的场景之一。
表 10-1。 节点故障场景
| 场景 | 发起方 | 高级警告 | 影响 |
|---|---|---|---|
| 突发故障 + 重启 | 突发硬件故障 | 无 | 本地数据丢失 |
| 节点关闭 + 重启 | 云平台(硬件故障、软件更新) | 是 | 本地数据可能可用 |
| 节点关闭 + 重启 | 应用程序(应用程序更新、应用程序错误、受控重启) | 是 | 本地数据可用 |
| 节点关闭 + 终止 | 应用程序(节点释放,例如通过自动扩缩容) | 是 | 本地数据丢失 |
这些场景列出了许多触发条件;有些会提供预警,有些则不会。预警的形式多种多样,但本质上都是云平台主动发送的信号,允许节点优雅地关闭。在某些情况下,写入本地虚拟机磁盘的本地数据在中断后仍然可用,但有时数据会丢失。我们该如何处理这种情况?
场景可能由硬件或软件故障、云平台故障或您的应用程序触发。
将所有中断视为节点故障
从单个节点的角度来看,所有场景都无法预测。此外,应用程序代码也无法预知自身所处的具体场景,但如果将所有中断都视为故障,则可以优雅地处理所有场景。
鉴于故障可能随时发生,您的应用程序不应使用计算节点的本地磁盘驱动器作为任何业务数据的记录系统。如表 10-1 所述,数据可能会丢失。请记住,使用无状态节点的应用程序与无状态应用程序并不相同。应用程序状态持久存储在可靠的存储中,而不是单个节点的本地磁盘上。
通过对所有关机和故障情况采取相同的处理方式,可以明确处理这些情况的路径:维持容量、处理节点关机、尽可能保护用户,并在事后恢复正在进行的工作。
使用 N+1 规则保持足够的故障容量
应用程序首先要假设节点故障将会发生,然后采取积极措施,确保故障不会导致应用程序停机。首要的积极措施是确保足够的容量。
高可用性需要多少个节点实例?应用 N+1 规则:如果需要 N 个节点来支持用户需求,则部署 N+1 个节点。一个节点发生故障或中断不会影响应用程序。尤其重要的是,要避免直接支持用户的节点出现单点故障。如果只需要一个节点,则部署两个。
N+1 规则 应针对每种类型的节点单独考虑和应用。例如,某些类型的节点可能会发生停机,而不会被任何人注意到或重视。这些节点不适合应用 N+1 规则。
虽然很少见,但由于异常故障,可能需要比单个节点更大的缓冲区。例如,与恶劣天气相关的事件可能会导致整个数据中心无法访问。示例中讨论了机架顶部交换机故障,而跨数据中心故障转移则在多站点部署模式(第 15 章)中有所提及。
故障发生到云平台监控系统识别到该故障之间存在时间延迟。在新的节点完全部署之前,您的服务将以较低的容量运行。对于配置为通过云平台负载均衡器接收流量的节点,流量将继续路由到该节点;一旦识别到故障节点,它将立即从负载均衡器中移除,但还需要一些时间才能启动替换节点并将其添加到负载均衡器。有关在容量下降期间从不太重要的功能中借用已部署资源来支持更重要的功能的讨论,请参阅自动缩放模式(第 4 章)。
处理节点关闭
我们可以负责任地处理节点关闭,但对于突发的节点故障却没有相应的处理方法,因为节点会瞬间停止运行。并非所有硬件故障都会导致节点故障:云平台能够敏锐地检测到即将发生的硬件故障信号,并可以提前启动受控关闭,将租户迁移到其他硬件。
有些节点故障是可以预防的:您的应用程序代码是否处理异常?至少,为了记录日志,应该这样做。
无论节点关闭的原因是什么,目标都是允许正在进行的工作完成,在关闭完成之前从该节点收集运行数据,并且不影响用户体验。
云平台会提供信号,让正在运行的应用程序知道节点即将关闭或正在关闭。例如,Amazon Web Services 提供的信号之一是高级警报,而 Windows Azure 提供的信号之一是在节点内触发一个事件,表明关闭已经开始。
节点关闭对用户体验影响最小
同时管理负载均衡和服务关闭的云平台会停止向正在关闭的节点发送 Web 流量。这将避免许多类型的用户体验问题,因为新的 Web 请求将被路由到其他实例。只有在服务关闭时尚未完成的待处理 Web 请求才会出现问题。某些应用程序不会受到任何影响,因为所有请求几乎都能立即响应。
节点可能会在用户可见的某些工作进行期间开始关闭。处理 Web 请求所需的时间越长,这个问题就越严重。目标是避免用户仅仅因为 Web 节点在处理其请求时关闭而看到服务器错误。应用程序应该等待这些请求处理完毕后再关闭。最佳处理方法取决于应用程序、操作系统和云平台;示例中提供了一个在 Windows Azure 上运行的 Windows 云服务的示例。
如果节点正在处理用户请求,则导致突然终止的故障可能会造成用户体验问题。一种最大限度减少这种情况的策略是设计用户界面代码,使其在发生故障时进行重试,如忙音模式(第 9 章)中所述。请注意,这里的忙信号并非来自云平台服务,而是来自应用程序自身的内部服务。
节点关闭时不丢失部分完成的工作
节点一旦开始关闭,就应立即停止从队列中拉取新任务,并尽可能让当前任务完成。
以队列为中心的工作流程模式(第 3 章)描述了一些处理时间可能较长的服务。如果处理时间过长,无法在节点终止前完成,则该节点可以选择将任何正在进行的工作保存到本地节点之外(例如云存储),以便之后可以恢复。
节点关闭时不丢失运行数据
Web 服务器日志和自定义应用程序日志通常写入各个节点的本地磁盘。这是一种合理的方法,因为它能够有效地利用可用的本地资源来捕获这些日志数据。
如水平扩展计算模式(第 2 章)中所述,管理多个节点的一大挑战在于整合运行数据。通常,这些数据会定期收集。如果数据采集过程未能检测到某个节点正在关机,则可能无法及时采集到相关数据,从而导致数据丢失。此时,您可以触发该运行数据的采集。
节点故障恢复
节点故障恢复包含以下几个方面:在故障期间保持良好的用户体验,以及恢复任何中断的正在进行的工作。
保护交互式用户免受故障影响
某些节点实例直接支持交互式用户。这些节点可能直接提供网页服务,或者提供为移动应用或 Web 应用提供支持的 Web 服务。良好的用户体验可以保护用户免受偶尔发生的故障影响。这可以通过以下几种方式实现:
如果服务器上的某个节点发生故障,云平台会检测到此情况并停止向该节点发送流量。一旦检测到故障,服务调用和网页请求将被路由到运行正常的节点实例。您无需执行任何操作即可实现这一点(只需遵循 N+1 规则),有时这已经足够了。
但是,为了确保完全的健壮性,您应该考虑在处理网页请求或执行服务调用过程中可能发生的短暂故障窗口。例如,节点上的磁盘驱动器可能突然发生故障。针对这种特殊情况的最佳应对方法是在客户端添加重试逻辑,本质上就是应用忙音模式(第 9 章)。对于原生移动应用或通过 Web 服务调用管理更新的单页 Web 应用来说,这很容易实现(例如 Google Mail 就很好地处理了这种情况)。但对于每次都重新显示整个页面的传统 Web 应用,用户可能会通过浏览器看到错误提示。这不在您的控制范围内,用户需要自行重试操作。
恢复后端系统中的未完成工作
除了影响用户体验外,节点突发故障还会中断服务层的处理。可以通过构建幂等进程来稳健地处理这种情况,使进程能够安全地对相同的输入多次执行。成功恢复取决于节点是否为无状态,以及重要数据存储在可靠的存储介质中,而不是节点的本地磁盘驱动器上(假设该磁盘没有可靠的存储介质支持)。以队列为中心的工作流程模式(第 3 章)详细介绍了一种云原生应用程序的常用技术,该技术涵盖了重启中断进程以及保存未完成工作以加快恢复速度。
示例:在 Windows Azure 上构建 PoP
Page of Photos (PoP) 应用程序(前言中已描述)的架构旨在提供始终如一的可靠用户体验,并确保数据不丢失。为了在偶尔发生故障和中断的情况下维持这一目标,PoP 会做好故障准备,优雅地处理角色实例(节点)的关闭,并根据需要从故障中恢复。
为故障做好准备
PoP 会不断调整容量以节省成本;我们只希望为正常运行所需的容量付费,不多也不少。PoP 也不希望在偶尔发生故障或中断时,因容量减少而导致停机或牺牲用户体验。
N+1 规则
对于直接服务于用户的角色,我们遵循 N+1 规则,因为用户体验至关重要。然而,由于偶尔的中断不会直接影响用户,PoP 管理层决定不对构成服务层的工作角色应用 N+1 规则。
这些决策都已纳入PoP自动扩缩容规则中。
Windows Azure 故障域
Windows Azure(通过名为 Windows Azure Fabric Controller 的服务)会在特定约束条件下,确定应用程序的每个角色实例在数据中心的部署位置。对于故障场景而言,最重要的约束是故障域。故障域是数据中心内潜在的单点故障 (SPoF),任何至少有两个实例的角色都会保证分布在至少两个故障域中。
细心的读者会意识到,这意味着最多可能有一半的应用程序 Web 和辅助角色实例同时宕机(假设有两个故障域)。虽然这种情况有可能发生,但概率很低。好消息是,即使真的发生了,Fabric Controller 也会立即启动修复程序,尽管在恢复期间应用程序的运行能力会有所下降。
大多数情况下,硬件故障会导致单个角色实例中断。N+1 规则 考虑到了这种情况。
如前所述,PoP 会做出 N+1 足以满足业务需求的决策。
请注意,故障域仅适用于意外硬件故障。对于操作系统更新、虚拟机管理程序更新或应用程序发起的升级,停机时间将分布在各个升级域中。
升级域
Windows Azure 也具有与故障域类似的升级域概念,它将角色实例逻辑划分为多个组(默认为五个),每个组都可以独立更新。这与就地升级功能相辅相成,该功能以增量方式升级应用程序,每次升级一个升级域。
如果您的应用程序有 N 个升级域,Fabric Controller 将管理更新,每次影响 1/N 个角色实例。这些更新完成后,即可开始更新接下来的 1/N 个角色实例。此过程持续进行,直到应用程序完全更新。从一个升级域到下一个升级域的过渡可以是自动的,也可以是手动的。
升级域对 Fabric Controller 非常有用,因为它会在操作系统升级(如果客户选择了自动更新)、虚拟机管理程序更新、将角色实例从一台机器或机架紧急迁移到另一台机器或机架等情况下使用它们。如果您的应用程序使用就地升级流程进行自我更新(即一次升级一个升级域),则需要使用升级域。您可以选择手动批准从一个升级域升级到下一个升级域,也可以让 Fabric Controller 自动执行。
我的应用程序应该使用多少个合适的升级域?这其中存在权衡:升级域越多,部署所需时间越长;而升级域越少,容量缩减的程度就越大。对于 PoP 来说,默认的五个升级域效果很好。
处理 PoP 角色实例关闭
优雅地关闭角色可以避免操作数据丢失和用户体验下降。
当我们指示 Windows Azure 释放一个角色实例时,会有一个既定的关闭流程;它不会立即关闭。第一步,Windows Azure 会选择要终止的角色实例。
首先,我们来考虑如何终止一个 Web 角色实例。
Web 角色实例关闭
一旦选定要终止的特定 Web 角色实例,Windows Azure(具体来说是 Fabric Controller)会将该实例从负载均衡器中移除,以防止新的请求路由到该实例。然后,Fabric Controller 会向正在运行的角色发送一系列即将关闭的信号。最后一个信号是调用角色的 OnStop 方法,该方法允许五分钟的时间进行优雅关闭。如果实例在五分钟内完成清理,则可以从 OnStop 方法返回;如果实例在五分钟内没有从 OnStop 方法返回,则会被强制终止。只有在 OnStop 方法完成后,Azure 才会将您的实例视为已释放并用于计费,因此您应尽可能高效地使 OnStop 方法退出。不过,您可以采取一些步骤来使关闭过程更加优雅。
尽管关闭流程的第一步是从负载均衡器中移除 Web 角色,但它可能仍在处理现有的 Web 请求。根据满足各个 Web 请求所需的时间,您可能需要采取一些主动措施,确保这些现有的 Web 请求在关闭完成之前得到满足。例如,用户上传文件就是一个比平时慢的操作,但即使是例行操作,如果完成速度不够快,也需要考虑。确保 Web 角色优雅关闭的一个简单方法是,在 OnStop 方法内部循环监控 IIS 性能计数器,例如“Web 服务\当前连接数”,只有当该计数器降至零时才继续执行直至终止。
角色实例的应用程序诊断信息会记录到当前虚拟机,并由诊断管理器按照您选择的计划(例如每十分钟一次)收集。无论您的计划如何,在 OnStop 方法中触发一次计划外收集,都能让您在准备就绪后安全地结束并退出。可以使用服务管理功能发起按需收款。
工作角色实例关闭
优雅地终止工作角色实例与终止 Web 角色实例类似,但也存在一些差异。由于 Azure 不对 PoP 工作角色实例进行负载均衡,因此没有需要释放的活动 Web 请求。但是,触发按需收集应用程序诊断信息是有意义的,通常情况下您也需要这样做。
工作角色实例在关闭过程中需要尽快停止从队列中拉取新项目进行处理。与 Web 角色类似,实例关闭信号(例如 OnStop)非常有用;工作角色可以设置一个全局标志来指示角色实例正在关闭。从队列中拉取新项目的代码可以检查该标志,并在实例关闭时停止拉取新项目。工作角色可以使用 OnStop 作为信号,或者挂钩更早的信号,例如 OnStopping 事件。
如果工作角色实例正在处理从 Windows Azure 存储队列中拉取的消息,并且在实例终止之前未完成处理,则该消息将超时并返回到队列,以便再次拉取。 (详情请参阅以队列为中心的工作流程模式(第 3 章)。)如果处理过程包含多个步骤,则可以在队列中更新底层消息以指示进度。例如,PoP 分三个步骤处理新上传的照片:从照片中提取地理位置信息、创建两种尺寸的缩略图以及更新所有 SQL 数据库引用。如果其中两个步骤已完成,但第三个步骤尚未完成,则从队列中提取的消息对象中的LastCompletedStep字段会指示这一点,并且该消息对象可用于更新队列中的副本。当此消息再次从队列中提取(由后续角色实例提取)时,处理逻辑将知道跳过前两个步骤,因为 LastCompletedStep 字段已设置为 2。首次提取消息时,该值设置为 0。
使用受控重启
有时您可能需要重启角色实例。如果使用 Windows Azure 服务管理服务中的“重启角色实例”操作触发重启,则支持允许释放活动工作的受控关机。
如果使用其他方式启动重启,例如直接在节点上使用 Windows 命令,则 Windows Azure 无法管理该过程以实现优雅关机。
从故障中恢复 PoP
以队列为中心的工作流程模式(第 3 章)详细介绍了如何从长时间运行的进程中恢复。
否则,对于无状态角色实例(无论是 Web 角色还是工作角色),通常无需进行太多操作即可恢复。
PoP 解决了一些用户体验问题,因为用于获取数据的客户端 JavaScript 代码(通过 AJAX 调用 REST 服务)实现了忙音模式(第 9 章)。如果服务没有响应,此代码将进行重试,最终重试请求会通过负载均衡分配到能够满足请求的其他角色实例。
总结
尽管云原生应用很少出现停机,但云环境中的故障却屡见不鲜。使用节点故障模式可以帮助您的应用做好准备,优雅地处理并从运行所在计算节点的偶尔中断和故障中恢复。许多场景都可以使用相同的实现方式来应对。
未考虑节点故障场景的云应用将不可靠:用户体验会受到影响,数据也可能丢失。