1 高可用设计要回答什么
顾客在商店页面点下“提交订单”,页面转了几秒,最后显示失败。此时应用进程可能已经退出,也可能仍在运行,只是无法访问数据库;订单甚至可能已经写入,只是响应没有回到手机。高可用设计从这样的业务动作开始:发生什么故障时,哪些动作仍能完成,哪些动作必须暂停,暂停多久,以及恢复后怎样确认结果。画出很多服务器只是起点,让用户在故障期间得到可信的结果才是目标。
本课用订单 API 作为例子。请求经过域名解析和负载均衡到达应用实例;应用验证调用身份,通过私有网络访问订单数据库;日志、指标和探测帮助值班人员判断服务是否正常。应用部署在不同可用区,可用区代表具有一定独立供电、网络等条件的基础设施范围。这里的“独立”有边界:不同可用区仍可能共享区域级服务、账号配置和人员操作。不能把部署位置不同直接理解为所有风险都互不相关。
可用性口径、超时预算、有限重试、持久幂等,以及可迁移的冗余/健康检查/故障隔离模式,已在 P11「高可用基础模式」建立。本课不再重讲统计分母或等待预算,而是把同一组业务成功条件落到云资源机制:可用区故障域边界、负载均衡如何摘除与回挂目标、各层主动-主动与主动-备用如何配置,以及网络分区下如何隔离旧写入者。对查询订单,稍旧但明确标注的状态也许可以接受;对创建订单,仍须可查询、金额正确、同幂等键不重复下单——这些成功条件沿用 P11,本课要回答的是云上哪一层在故障时真正接手。

设计时沿这条链追问:入口不可达会怎样,应用少了 1 个可用区会怎样,数据库无法确认提交会怎样,身份服务暂时不可达会怎样。已有凭证能否在有效期内本地校验,要根据实际认证方式判断,不能为保住可用性跳过权限检查。观测系统也要有独立的探测来源,否则应用和它的监控同时失联时,仪表盘可能只是停止更新,而非显示故障。
高可用方案因此需要同时写清业务边界、故障假设、恢复路径和验证证据。例如“可用区 A 的应用全部退出后,创建订单在约定时间内恢复,已确认订单不丢失,重复请求不产生重复订单”。这种陈述可以演练,也允许被证据推翻。至于整个区域长期不可用、误删数据和历史数据恢复,仍需备份与容灾措施,本课末尾再把它们接起来。
2 冗余、故障域与恢复目标
冗余是为同一种能力准备额外资源,例如用 2 个应用实例提供相同接口。但冗余是否有用,取决于它们会不会被同一次故障同时影响。2 个进程在同一台主机上,能应对部分进程故障,却无法应对该主机断电。2 台主机在同一可用区,扩大了可应对的故障范围,却仍可能一起受到该可用区网络中断的影响。故障域就是可能被同一原因同时影响的资源集合。
从实例、主机、可用区到区域,故障域通常逐层扩大,但不是只有地理位置才形成故障域。相同的错误镜像、统一下发的安全组规则、同一个被撤销的访问身份,也会同时击穿不同位置的副本。部署冗余还要配合分批发布、配置审查和可回退的变更。对刚开始设计系统的读者,先列出要覆盖的故障,再为每个故障确认剩余路径,比直接追求更多部署地点有效。
冗余还必须包含容量。平时 2 个可用区各承担 50% 流量,如果每个区的资源已接近上限,失去其中 1 个区后,剩余实例可能立即超载。后续即使会自动扩容,启动实例、拉取镜像、准备连接和完成健康检查都需要时间。方案要说明现有剩余容量能支撑多久,扩容期间是否限流,哪些请求优先保留。可以把查询频率限制下来,但不能悄悄把写入失败算成成功。
恢复目标帮助我们决定为这些云资源投入多少。RTO/RPO 的业务口径与“服务恢复 vs 数据恢复”区分沿用 P11;本课要把它们写成可演练的云上接管路径。比如订单创建 RTO 为 120 秒、RPO 为 0,表达的是希望在单个可用区应用失效后 120 秒内恢复,且已确认提交的订单不能丢失——它要求负载均衡收敛、剩余可用区容量与数据层写入资格同时成立,而不是只增加实例名单。

RTO 与 RPO 不能互相替代。快速启用 5 分钟前的数据副本,可能符合较短的 RTO,却违反订单的 RPO;等待数据完整确认后再恢复,则可能保住数据而超过 RTO。这里还要区分目标和实测:目标写在方案中,实测来自演练或真实故障,两者有差距时应记录原因。不能把某次切换用了 40 秒写成“以后任何故障都保证 40 秒恢复”。
为避免目标悬空,可以给每个故障写 1 行记录:故障对象、受影响动作、探测方法、接管资源、恢复条件、RTO、RPO、验证方法。对于只读商品介绍,可以允许短时间旧数据;对于已确认订单,通常更在意完整性。这些不同要求不一定需要不同系统,但必须在同一方案中分别说明,避免用“整站高可用”掩盖业务差异。
3 主动-主动与主动-备用边界
主动-主动指多个部署单元在正常时期都承担业务;主动-备用指正常时期主要由活动单元承载,备用单元在满足条件后接管。这些词必须带上对象。应用层可以主动-主动,数据库仍采用单个写主节点;2 个区域都能查询,也不代表它们能同时修改同一笔订单。只写“双活架构”,读者无法知道究竟哪一层在同时工作。
对于无状态应用,主动-主动比较容易理解。2 个可用区都运行订单 API,负载均衡把新请求送给健康实例;会话、订单状态等持久信息放在有明确定义的数据服务中。某个区的应用退出后,入口减少对应目标,剩余实例继续服务。这里仍需处理连接中的请求:请求可能已经执行,客户端却没收到结果,因此重试要携带相同的幂等键,让同一次业务动作能够返回原结果,而不再次创建订单。
主动-备用则需要回答备用是否真的随时可用。热备通常已经运行并同步必要状态,冷备可能还需要创建资源、恢复数据和配置入口。术语没有统一的秒数保证,方案应直接列出准备程度及接管步骤。备用实例若长期不接流量,可能存在权限过期、依赖配置落后、证书失效或容量不足等问题。定期让受控请求走过备用路径,比只确认实例状态为“运行中”更有说服力。
数据库的接管还多了写入权问题。旧主节点无法联系,不等于它已经停止写入。接管前要通过数据库支持的选主和隔离机制,确认新主具备写入资格,并阻止旧主继续修改同一份业务数据。这种阻断通常称为隔离或 fencing,可以体现为可靠的写入租约、撤销存储访问权等机制。具体实现留给数据库课程,本课只要求识别“谁能写”和“怎样阻止另一方写”这 2 个问题。

上面的交互按正常、故障、判断、隔离、接管、验证展示同一条恢复路径。重点观察负载均衡摘除应用目标与数据库授予写入权的差别:前者影响请求去向,后者决定数据能否安全修改。入口改到了新应用,不表示数据库已完成接管;数据库接管完成,也不表示应用连接池已经重新连对地址。恢复需要整条请求链成立。
选择模式时可以先看 3 件事:数据是否允许多个写入者、剩余资源能否接住流量、团队能否持续验证接管过程。入门方案采用跨可用区应用主动-主动、数据库单写主加可接管副本,往往更容易解释和演练。它仍需要明确复制方式及写入隔离,也不能承诺区域级故障可恢复。跨区域同时写同一份数据会引入更复杂的冲突处理,应在业务确实需要时另行设计。
4 动手:演练一次单故障域失效
这次演练只在隔离的测试环境进行,目标是让可用区 A 的应用全部退出,验证可用区 B 能继续创建和查询订单。数据库、入口和身份依赖保持原有配置,以便观察应用故障的直接影响。先写清楚范围:这模拟了某个故障域内应用承载能力消失,没有模拟整区断网,也没有证明数据库能够故障转移。结论应与实际注入的故障保持一致。
准备时,在 A、B 各部署可区分的应用实例,响应日志记录请求标识、实例所在区、幂等键和订单标识。测试账号仅拥有测试数据权限,测试流量使用独立标记。创建订单后立刻查询,确认状态和金额;再次提交同一个幂等键,确认返回同一订单。先让这些检查在正常状态下稳定通过,否则无法判断后面的错误究竟来自故障还是原本就存在的配置问题。
再确认 B 的容量与依赖路径。让少量测试请求明确经过 B,检查其能否获取所需身份、访问数据库、写日志并返回结果。准备好恢复 A 的部署方式及停止演练的条件,例如 B 持续超载、异常写入或波及非测试资源。故障注入要使用能精确限定目标的环境管理操作,避免用宽泛的网络规则误伤共享依赖。本课不提供对任意环境直接执行的停机命令。
开始连续发送测试流量,记录基线成功率和延迟。在时间点 t0,把 A 的应用实例缩减至 0,保持 B 不变。观察入口从什么时候开始停止向 A 发送新请求,B 的请求数何时增加,错误是否集中在连接中断或健康检查尚未收敛的阶段。不要只看最终页面恢复;短暂的失败、超时和重试数量也属于演练结果,用户可能已经感受到了这些变化。

把 t0 到恢复标准满足的过程拆开记录:故障多久被探测到,流量多久完成转移,剩余实例多久稳定,以及数据核对何时完成。这些阶段可能重叠,不能机械地把所有监控面板上的耗时相加。可以预先约定:连续 60 秒创建与查询都达到测试成功率和延迟门槛,才记为恢复。这个窗口用于本次演练判断,不代替长期可用性统计,也不保证随后不会再次失败。
演练结束后恢复 A,逐步放回流量,确认连接数、错误率和订单核对结果都正常,再清理测试数据。报告写出实际注入方式、流量规模、已确认订单清单、失败请求处理结果和时间线。如果没有丢失已确认订单,只能说明这次应用故障实验没有观察到丢失,不能据此证明数据库故障时 RPO 为 0。下一次可单独演练数据库接管,前提是已有受支持的接管与隔离方式。
5 一致性与可用性取舍
假设数据库主节点在 A,副本在 B。网络暂时中断后,A 还能接到部分请求,但无法把新订单同步到 B。此时继续在 A 确认订单,可能保持这部分用户的写入体验;如果随后 A 彻底失效,B 可能没有这些已确认订单。反过来,要求至少获得 B 的确认才向用户报告成功,就会在这段网络故障期间暂停写入。选择发生在明确的故障条件下,不能笼统地说某个系统永远只重视一致性或只重视可用性。
同步复制与异步复制提供了直观入口。同步复制通常要求规定的副本完成相应确认后,才向调用方确认提交;异步复制允许先确认,再把变化传给其他副本。确认是否代表持久化、需要哪些副本确认、故障后允许提升哪个副本,均取决于具体产品配置。看到“支持同步”还不够,必须核实当前模式,以及切换时是否会自动降低确认要求。
可以用 1 个主节点和 2 个副本做简化比较。写入只需主节点确认时,通常等待较少,但其他副本可能落后;要求主节点和至少 1 个副本确认,会增加跨网络等待,却让这次提交在多个位置留有副本;要求全部 3 个节点确认,可能进一步增加等待,并让任意副本不可达都影响提交。这种确认门槛只是在说明复制等待,不能单独证明系统具有正确的选主或一致性算法。
交互固定主节点、快速副本、较慢副本的位置和示例延迟,只改变确认节点数。网络分区后,主节点独自可达,门槛为 1 时仍可能确认写入;门槛为 2 或 3 时暂停确认。图中的延迟来自教学假设,不是性能预测。副本数量增加不自动带来更高可用性,复制策略、仲裁机制、故障位置和业务承诺共同决定结果。尤其不能把“多份数据”直接等同于“绝不丢数据”。
业务上也有减轻取舍的方法。创建订单暂时受限时,仍可提供带有更新时间的商品查询;用户可以保存购物车,但页面必须说明订单尚未提交。若改为排队受理,就需要可靠队列、明确的受理状态和后续查询机制,并且重新定义成功条件。把请求放进某个进程的内存后马上报告“下单成功”,并没有解决可用性,只是把可能丢失的工作藏了起来。
还应处理超时后的不确定结果。调用方超时只表明没有按时收到响应,不表示写入失败。客户端可以用同一幂等键重试,或通过请求标识查询处理结果;服务端要保存幂等结果,并约定保存期限及并发请求的处理方式。幂等能够减少重复动作,却不能补回没有复制的数据,也不能替代主节点隔离。入门阶段先把成功、失败、结果未知分开,已经能避免许多“恢复后又重复下单”的问题。
6 常见坑:双活却共享单点存储
有个方案把订单应用部署到 2 个可用区,入口也能自动摘除故障实例,于是宣称“任意节点故障都不影响服务”。沿依赖继续追踪,却发现 2 组应用都连接到同一台自建数据库主机,没有副本,也没有已验证的恢复路径。应用层确实具备冗余,但数据库主机一旦失效,2 组应用会一起失去订单读写能力。架构图中有 2 条应用支路,并不等于业务有 2 条完整可工作的路径。
同样的问题会出现在共享文件目录、缓存、凭证服务、出口和域名配置上。比如应用本身无状态,却把生成订单需要的唯一编号文件放在单个共享目录里;或者启动新实例必须访问某个内部镜像仓库,而故障时这个仓库也不可达。前者会立刻阻断业务,后者可能只在扩容或重建时暴露。依赖清单因此既要看正在处理请求的路径,也要看恢复过程中必须调用的能力。

排查可以从失败请求反向追踪。先看入口是否收到请求,再看应用是否执行到数据库调用,最后看数据库是否接受连接、确认写入。若不同区应用都在同一依赖处失败,优先检查共同依赖及共同配置,而不是反复重启所有应用。若应用日志完全没有新请求,再调查入口、网络和名字解析。每次假设都配上可观察证据,避免“看起来像数据库”成为没有依据的结论。
修复也要对应故障范围。增加数据库副本只有在复制、接管、写入隔离和访问地址切换都成立时,才形成恢复能力;启用一个可跨可用区工作的托管能力,也仍需确认配置、配额、连接方式和责任边界。对于账号权限或错误数据修改,多副本可能一起受到影响。设计不能因为底层服务由其他团队维护,就省略自己需要验证的业务结果。
另一个陷阱是把重试当成冗余。数据库过载时,所有实例同时快速重试,会把更多请求压向同一个瓶颈。客户端和服务端都应限制重试次数,设置合理超时,并用退避及随机抖动分散重试时机;对于无法安全重复的动作,先查询结果或使用幂等键。健康检查也应区分实例自身故障和全体共享依赖故障,否则全体实例同时被摘除,可能让本来还能提供的查询能力一起消失。
最后给每个“高可用”标签加上范围说明。可以写“覆盖单个应用实例及单个可用区的应用承载故障”,并列出数据库、区域、身份配置等剩余风险。范围清楚不代表方案弱,而是让评审能够找到下一笔投入应该用在哪里。用故障清单检查每条恢复路径,比用服务器数量或部署地点数量证明可用性可靠得多。
7 案例:脑裂导致双主写坏数据
某订单服务在 A 使用数据库主节点,在 B 保留副本。一次网络故障使两侧无法互相探测,但各自仍能接到部分客户端流量。B 的接管脚本只检查“连续几次联系不上 A”,随后把 B 提升为主节点并修改部分入口。A 实际仍在运行,旧连接也没有全部断开。于是 2 个节点同时认为自己有权接收写入,这就是本例中的脑裂。
起初,双方的监控都显得合理:A 看见 B 不可达,B 看见 A 不可达,2 边都有健康应用和成功请求。但它们正在形成不同的订单历史。某笔订单在 A 被标记为已取消,在 B 被标记为已发货;或者库存初值为 1,2 边分别接受了购买请求。每边的局部检查都可能通过,合并起来却违反了业务规则。普通的“最后更新时间较新者覆盖”无法自动决定是否应该退款或撤回发货。

根因不在于探测不够频繁,而在于把“无法联系”当成“已经失去写入权”。分布式环境中,网络丢包、链路隔离和节点退出会产生相似的远端观察。多等几次可以减少误判,却不能证明旧主停止写入。设计必须依靠受支持的仲裁、租约或隔离机制控制写入资格;把自己的健康判断写进普通脚本,不能代替数据库的故障转移协议。
在事故处置中,先限制有冲突风险的写入入口,按照预案确定权威写入侧,并可靠阻止另一个节点继续写。保留两侧数据、日志、请求标识和接管时间线,再核对哪些已确认请求只存在于某一侧。不要立即把较旧的一侧整体覆盖掉,否则可能丢掉用于修复的证据。对于支付、库存和履约结果,需要业务负责人参与对账,无法仅靠数据库行级合并恢复可信状态。
预防措施要落实到恢复条件中:谁决定接管,依赖哪些证据,旧主怎样被隔离,无法隔离时是否停止自动提升。节点恢复后,也不能因它重新通过健康检查就直接开放写入;应先确认角色并重新同步,避免旧状态再次加入服务。幂等键能够阻止同一次请求重复创建,却不能阻止 2 个不同购买请求争夺同一份库存,因此它只是防线中的一部分。
这个案例的验收重点是“不发生双主写入”,而不只是“故障后还能写”。测试网络分区与测试进程退出是不同实验,后者的成功不能证明前者安全。初学者不需要自行实现仲裁算法,但要能在方案评审中指出:如果接管逻辑只有超时和改地址,还缺少控制写入资格的证据。为了保住业务规则,在证据不足时短暂停写可能比持续接受冲突订单更合理。
8 把 RTO/RPO 写进可用性方案
现在把前面的设计变成可执行的约定。先按业务动作列目标:创建订单、查询订单、后台导出可以分别设置。每项都写明故障范围、计时起点、恢复终点和数据边界。例如“测试环境中单个可用区的应用全部退出,从故障注入开始计时,到连续 60 秒创建与查询达到约定门槛为止,目标 RTO 为 120 秒;此次故障下,已确认订单的目标 RPO 为 0”。缺少这些限定,2 个团队可能对同一份数字作出不同解释。
RTO 要有实现路径和时间预算。可以为发现与告警、入口调整、备用资源准备、业务检查分别预留预算,同时说明哪些步骤自动执行、哪些需要值班人员介入。对于需要人工确认的接管,应把通知与响应时间纳入目标,而不是只计算点击接管按钮后的时间。若目标短到人员不可能稳定完成,就要调整流程、自动化程度或目标,不能靠文字抹去等待。
RPO 则沿数据确认链核实。订单成功响应在何时发出,数据库当时已经把数据保存到哪里,哪些副本满足接管条件,复制中断后是否仍继续确认写入。若使用异步复制,观测到的复制延迟可以帮助评估风险,但一次采样的低延迟不能成为最坏情况下的丢失上限。对 RPO 为 0 的主张,应明确其覆盖的故障模型,并核对持久化、复制和接管策略是否共同支撑该主张。

证据应能被别人复核。演练记录保留故障注入时间、告警时间、入口变化、业务探测结果、订单核对和回切记录。创建请求使用唯一标识,已确认订单形成清单,恢复后逐个核对;超时请求单独列出并查询结果,不能简单把它们全部当成失败或成功。还要检查重复订单与错误状态,因为“订单数量相等”无法证明每笔业务都正确。
方案需要指定责任人和触发条件。谁接告警,谁批准数据层切换,谁核对订单,谁决定恢复流量,都要有明确安排。权限要预先验证,并留有审计记录;紧急情况下才发现备用身份无法访问数据库,会直接拉长恢复时间。回切也属于变更,应确认复制追平、写入角色和容量,再逐步调整入口,不应为了恢复原来的布局立刻进行第 2 次冒险切换。
最后记录未满足的目标与改进动作。比如实测恢复耗时 170 秒而目标是 120 秒,就写明主要耗时来自健康检查等待或容量准备,并安排对应调整。若只在低负载下完成演练,应保留这一限制。目标、证据、差距、责任和复测条件放在一起,可用性方案才会随着系统变化持续更新,而不是在上线评审后变成无人再看的架构附件。
9 从高可用设计走向备份与容灾(C44)
高可用通过冗余和接管,尽量让服务在约定故障期间继续工作或尽快恢复。它通常依赖当前系统附近的有效副本,但这些副本可能忠实复制错误。管理员误删订单表,错误删除也可能很快传播到副本;应用缺陷把订单金额批量改错,同步复制不会判断这是不是业务想要的变化。节点都在线、请求都能返回时,数据仍然可能已经不可信。
这就需要备份提供可以回到过去的恢复点。副本关注保持当前状态,备份关注在独立保留的历史版本中找到可信状态。备份频率、保留期限、访问权限和恢复耗时,决定它能覆盖哪些风险。不能因为有副本就取消备份,也不能因为每天有备份就认为故障时可以立即继续下单。2 者解决的问题相互补充,需要分别验证。
容灾进一步讨论更大范围的中断,例如区域长期不可用,或者关键依赖在主部署范围内整体失效。此时除了订单数据,还需要应用镜像、部署配置、网络规则、身份权限、密钥访问和对外入口。只有数据库能恢复,而新环境无法获得必要凭证,业务仍然不能运行。恢复路径可能还依赖管理接口,因此要区分已有资源继续服务的能力和新建、修改资源的能力。
高可用的 RTO/RPO 不能直接复制到灾难恢复场景。单个应用区退出时,也许剩余实例几10秒内就能接流;从历史备份恢复到另一个区域,却可能涉及更长的数据恢复、环境准备和核对过程。应为每种故障分别定义目标,说明成本和限制。跨区域保存数据也不自动说明备份可信,还要核实错误操作是否会传播、保留策略是否独立、恢复权限是否可用。
对订单服务而言,可以先整理 3 类证据:单个应用故障域失效后的接流记录、数据库接管及旧主隔离记录、从历史恢复点还原订单的记录。前 2 类主要支撑本课的高可用主张,第 3 类将在 C44 展开。每份记录都需要业务校验,因为资源状态正常只能证明恢复过程中的部分环节成功,不能证明用户的订单已经正确回来。
进入下一课前,先带走当前系统的依赖图、业务成功条件、按故障划分的恢复目标,以及尚未覆盖的风险清单。接下来要问的是:如果当前副本都不可信,最近哪个恢复点可用;恢复需要哪些环境和权限;恢复期间的新订单怎样处理。把这些问题与本课的接管路径放在一起,才能从“资源坏了还能运行”继续走向“数据出错后也有办法恢复”。
放进方案里
本方案面向订单 API,成功条件、统计口径与幂等保护沿用 P11 的基础高可用约定:请求在约定时间内返回可查询的订单,金额和状态正确,同一幂等键不产生重复订单;创建与查询分别统计。云上落点是:应用跨 2 个可用区主动接流,持久状态保存在具备明确复制和接管机制的数据服务中;剩余可用区需通过容量验证,并为扩容等待准备限流策略。身份校验保持有效,观测覆盖入口、应用、数据及外部业务探测。
对单个可用区的应用全部退出,目标 RTO 为 120 秒,目标 RPO 为 0;范围限于此类应用故障,已确认订单须恢复后逐笔核对。计时从故障注入开始,到连续 60 秒满足预先约定的成功率和延迟门槛结束。测试报告记录流量规模、健康检查收敛、失败与超时请求、幂等重试及订单核对结果。生产目标还需结合实际容量和流量验证。
数据层按单写主设计,接管前须确认候选副本资格并可靠隔离旧主;无法确认写入权时暂停有冲突风险的写入。复制确认策略、可能的数据损失范围及适用故障模型单独登记。应用故障演练不作为数据库 RPO 的证明。值班人员负责告警响应,数据负责人执行受支持的接管,业务负责人确认订单核对,相关权限提前验证并保留审计记录。
恢复流量前检查业务请求、容量和写入角色;原节点恢复后先同步与验证,再安排受控回切。区域中断、误删和错误数据传播列为剩余风险,由 C44 的备份与容灾方案补充。每次演练保留时间线、观测证据、目标差距和改进责任人,配置或依赖发生重要变化后重新评估相应恢复路径。
10 小结:下一步备份与容灾(C44)
高可用设计要把业务成功条件、故障域、剩余容量、写入资格和恢复证据连接起来。主动-主动或主动-备用描述的是特定层的工作方式;RTO/RPO 描述的是特定故障下的恢复目标。它们都需要实际路径支撑。
自测时,试着回答:2 个应用可用区共享单台数据库,覆盖了什么故障?无法联系旧主,为什么不能直接让副本写入?客户端超时后,怎样确认订单结果?如果误删已经传播到全部副本,应该寻找哪类恢复能力?最后这个问题,就是 C44 备份与容灾的起点。