跳到主要内容

第 24 章 高可用:业务不能挂的保障

副题:三道防线——挂了还有(冗余)、别冲垮(限流)、别拖垮全站(熔断降级);可用性是概率管理,不是"永不出错"

第 23 章把制品放上了服务器:多实例、容器、配置管理、发布回滚。业务在跑。但"在跑"不等于"不会挂"——服务器硬件会坏、进程会崩、流量会突然暴涨。本章回答:业务不能挂,靠什么保障?

本章要建立的认知

  1. 高可用是概率管理:"不能挂"不是"永不挂",是把挂的概率降到可接受——用"几个 9"把它变成可度量的目标;
  2. 三道防线:冗余(挂了还有)、限流(别冲垮)、熔断降级(别拖垮全站)——每一道都是被一类事故逼出来的;
  3. 可用性是设计出来的,不是运气:目标、机制、演练、值班——每个环节都要有人负责。

24.1 宕机成本与可用性目标:雪崩事故

事故:一个热点帖子,压垮了全站。阶段 4 的一个晚上,一篇内容突然爆了(被论坛转载),流量瞬间翻了几倍。应用是扛住了(23 章的多实例),但所有请求都在查数据库——数据库连接池被打满,后面的请求开始排队,排队超时,超时的请求被重试,重试又占用新的连接——连锁反应:数据库越来越慢,应用等待越来越多,最终全站 502(五阶段故事线登记的阶段 4 事故:"热点内容 + 流量暴涨 → 数据库连接池打满 → 全站 502")。

事故的现场细节也补两笔(它是"雪崩"为什么叫雪崩的实景):告警是半小时后才响的——连接池打满是渐进的,监控阈值设得太高(监控这事下一章展开,这里先立事故印象);值班的人被叫醒时,全站已经 502 了——on-call 刚开始有(24.7 的关键事件),但"有值班"和"会处理"是两件事;恢复用了 40 分钟——重启数据库、清连接、重启应用,一步步手工来——40 分钟 × 每分钟的订单损失 = 这次事故的直接账单

雪崩的三个阶段(它是"为什么会从一点小问题变成全站挂"的机制):① 触发器(热点流量超了正常量级——单点瞬间成为瓶颈);② 放大器(请求排队 → 超时 → 重试 → 新请求——排队和重试互相喂养,连接池被打满后雪崩就开始了);③ 传导(数据库慢 → 应用等待 → 应用线程耗尽 → 应用也挂——故障从数据层传到应用层)——记住这三步,本章的每一道防线都是针对其中一步的(限流砍触发器、超时砍放大器、熔断砍传导)。

这次宕机的成本账单(它是"为什么高可用是硬指标"的答案):

  • 交易中断:宕机 1 小时,这一小时里的下单全部失败——直接损失 = 每小时订单量 × 客单价;
  • 用户流失:打不开的用户会走——社区产品的口碑是"随时都在",一次打不开,一部分用户就去了别处;
  • 信任损伤:交易产品(17 章)最怕"不靠谱"——用户敢不敢把钱放进你的系统,取决于你挂不挂。

宕机成本与用户的关系(它是"为什么 8.76 小时不是 9 小时"的业务侧):可用性不是工程师的 KPI——用户对"挂"的容忍度曲线:首次打不开(下次还来)→ 连续两次(开始怀疑)→ 交易时挂(直接走人——17 章说过交易的信任成本)——社区+交易的产品,"交易时挂"是最贵的挂(钱和信任一起丢)——所以 24.3 的数据防线(交易的数据)优先级高于一切(24.5 降级清单里交易永远最后降)。

"不能挂"变成硬指标:可用性目标(几个 9)。可用性 = 正常运行时间 / 总时间——99.9% 意味着一年最多宕机 8.76 小时(365 天 × 24 小时 × 0.1%);99.99% 意味着一年最多 52.6 分钟。"几个 9"不是技术名词,是给业务方的承诺——业务方说"不能挂",工程师要翻译成"99.9%,一年最多 8.76 小时",这就是第 5 章说的"把业务语言翻译成技术语言"。

可用性目标与商业目标的对齐(它是"几个 9 谁说了算"):几个 9 不是工程师定的,也不是业务拍脑袋——是商业目标翻译过来的:社区+交易的业务,宕机 1 小时损失 = 订单损失 + 用户流失——业务方要回答:1 小时损失多少能接受?——回答"一小时损失一万块" → 99.9%(一年 8.76 小时 ≈ 8.76 万的损失预算);回答"一分钟都不能断" → 99.99%+(那就要双机房的预算)——目标 = 损失 × 概率,业务定损失,工程师算概率(第 5 章"翻译":双向翻译)。

★ 可用性目标——九步决策第 16 次应用(决策记录又添一行): ① 目标:给"不能挂"一个可度量、可验收的承诺;② 约束:阶段 4——8 人团队、无专职运维(on-call 刚引入)、机器 2-3 台(23 章)、预算有限;③ 问题:可用性目标定几个 9?④ 候选:A 99.9%(年宕机 8.76 小时);B 99.99%(年宕机 52.6 分钟);⑤ 维度:业务需求(社区+交易,宕机 1 小时损失可接受吗)、达成成本(99.99% 需要什么)、团队能力(8 人扛得起吗);⑥ 打分:99.9% 的业务含义——月宕机 43 分钟,社区产品可接受;99.99% 的达成成本——双机房/多可用区、专职 SRE、复杂的故障转移(单机房做不出 99.99%——一个机房断电就超了),8 人团队没有这个人力;⑦ 选择A(99.9%)——目标配得上阶段 4 的业务与团队;⑧ 收益:目标明确,机制有验收线(第 4 章复杂度预算、第 7 章 P95 预算的"预算"思想);⑨ 演进条件交易占比涨到"宕机 1 小时 = 不可承受"时,再评估 99.99%(双机房/专职 SRE 的预算才花得值)。

可用性目标与 22 章"敢部署的前提"的关系(它是"承诺"链的收束):22 章说"敢部署的前提是能回滚"——本章说"敢承诺 99.9% 的前提是机制在"(冗余/限流/熔断/演练,缺一个都是空头支票)——承诺的链条:业务说"不能挂"(24.1)→ 工程师说"99.9%"(24.1)→ 机制兜住(24.2-24.5)→ 演练证明(24.7)——每一环都是下一环的前提(18 章"承诺"思想:承诺要有机制兑现,机制要有测试证明)。

"三个九"的验收(它是"目标怎么检查"):99.9% 不是年末算账——月度可用性复盘:本月宕机多久(监控数据)→ 超了没有 → 超了是哪道防线漏了(24.7 复盘)→ 下月补什么——目标 + 测量 + 复盘 = 可用性闭环(16 章预算的"测量-对比-修"循环:同一个闭环)。

"几个 9"与第 4 章约束的关系(它是"承诺"的完整形态):第 4 章验收清单里的"首页要快""数据不能丢"是功能级承诺——可用性目标是运行级承诺(平时看不见,挂了才兑现)——承诺的层级:功能承诺(18 章测试验证)→ 运行承诺(本章目标+机制验证)——每层承诺都要有验收(12 章"预算"思想:有数字才算数)。

决策记录"又添一行"的呼应(它是第 16 次应用的登记):第 5 章的决策记录,到本章已经添了十六行——分层(12)、Session vs JWT(13)、SQL vs NoSQL(14)、缓存引入(16)、测试投入(18)、安全投入(19)、工作流(20)、自动化程度(22)、容器与 K8s(23)——可用性目标是第十六行:它不是技术选型,是"承诺定多高"——记录里的每一行,都是某个阶段"业务逼出技术"的坐标(第 26 章演进时会翻这些记录)。

**可用性预算:链上最弱的一环。**目标定了,还要知道"99.9% 靠什么凑出来"——系统可用性 ≈ 各组件可用性的乘积(请求要经过所有组件,任何一个挂了请求就失败):应用 99.9% × 数据库 99.9% × 网络 99.9% ≈ 99.7%——整体永远低于最弱的一环(23 章说"数据层的单点是最后一块短板",这块短板直接决定了预算的底线)。可用性预算的意义:把 99.9% 拆到每个组件,谁拖后腿一目了然(16 章把 P95 预算拆到层内,同一个思想)。

"概率管理"的另一个含义(它是"接受会挂"的落地):概率管理意味着接受"挂"是常态——所以系统设计要面向故障设计(design for failure):默认依赖会挂(超时/熔断兜着)、默认机器会坏(冗余兜着)、默认流量会超(限流兜着)——与"默认安全"(19 章)同一个思想:默认接受最坏情况,机制处理最坏情况——"面向故障设计"是本章所有机制的设计哲学。

高可用是概率管理,不是"永不出错"。选 99.9% 意味着接受一年 8.76 小时的宕机——不是"尽力不挂",是"挂多久以内算达标"。这个认知很重要:追求"永不挂"的团队,把钱花在错误的地方(最后一公里的 99.99%);接受"会挂、但挂了有机制"的团队,把钱花在恢复上(本章后面全是恢复的机制)。

24.2 冗余:把单点变多点

第一道防线:挂了还有——核心思想是冗余(redundancy):任何单点,都备一份。23 章的多实例已经是冗余(应用层),本章把它讲完整。

图24-1 图稿占位
负载均衡拓扑
LB 分摊流量 + 健康检查

负载均衡(LB)(23.2 已登场,这里是它的完整形态):一个入口,多台机器,把请求分发下去——多实例的流量入口。三个机制:

  • 四层 vs 七层:四层 LB(按 IP/端口转发——快,不管内容);七层 LB(按 URL/请求头路由——能识别"哪个接口",能做更细的分发)——阶段 4 用七层(能按接口限流/路由,12 章中间件的"网关版");
  • 健康检查(23.6 已讲,这里是它的常态角色):不健康的实例不分配流量——检测到故障 → 摘除;恢复 → 重新加入——LB 是"自动剔除坏机器"的机制(23 章说"健康了才放上去",LB 让"不健康就摘下来"也自动化)。
  • 分发策略:轮询(大家轮流——默认)、最少连接(谁闲给谁——更均匀)、按会话(23.2 的会话亲和——粘住用户)——策略不是玄学,是"怎么分更合理"的取舍(轮询最公平,最少连接最均匀,粘性最省事——阶段 4:轮询为主 + 会话亲和备用)。

多机房(浅提)(它是"冗余的尽头"预告):冗余的极致是机房级——一个机房断电/断网,另一个机房顶上(云厂商的"多可用区"就是这个意思)——阶段 4 用不起(99.9% 目标用不到机房级冗余)——但要知道它存在:24.1 选 99.9% 而不是 99.99%,省掉的正是这层冗余(演进条件:交易占比涨了再评估)。

"冗余"这个词的完整含义(它是第一道防线的核心认知):冗余不是"多买一台机器"——冗余是"任何一台挂了,系统都不受影响"——多实例(应用挂了换一台)、多副本(数据多存一份)、多机房(机房挂了换一个)——冗余的程度,由 24.1 的目标决定(99.9% 要应用+数据两层冗余就够,99.99% 才要机房级)——"目标定多高,冗余建多深"(第 4 章复杂度预算的可用性版)。

"挂了还有"的三个层次(它是第一道防线的全景):实例级(多实例+LB——24.2)、数据级(主从+备份——24.3)、站点级(多机房——24.2 浅提)——三个层次不是都要上:99.9% 的目标 → 实例+数据两级就够(站点级是 99.99% 的预算)——"挂"的每一层,都有对应的"还有"(24.1 可用性预算的"拆"思想:目标拆到组件,冗余建到对应层)。

故障数学(23 章的 1% × 1% = 万分之一,这里完整展开):冗余的价值 = 故障概率的乘法——一台机器年故障率 1%,两台独立机器同时挂 ≈ 万分之一——"多备一份"不是浪费,是把概率降两个数量级(17 章"防线是叠起来的"在硬件层的形态:每一层冗余都是一道防线)。

本章与第 23 章的分界(它是"部署 vs 运行"的边界):23 章管"怎么把制品放上去"(部署执行),本章管"放上去之后怎么不挂"(运行保障)——部署关心"上线那一刻",高可用关心"上线之后每一天"——23 章说"本章让业务跑起来,第 24 章让业务挂不了"(23.8 的边界原话,本章兑现)——两章合起来:"跑起来 + 挂不了",才是完整的运行期(第 25 章再加"看得见")。

LB 自己也是单点(高可用最容易被忽略的:救火的人也需要救火):LB 挂了,多实例全瞎——所以 LB 也要冗余(两个 LB + 故障切换);DNS 层也要冗余(第 11 章:一个域名解析到多个 IP,浏览器自动换)——冗余是一层层叠上去的(16 章多级缓存、19 章纵深防御:同一个"叠"字)。

会话亲和 vs 无状态的取舍(它是"粘性与可替换"的权衡):23 章说过会话亲和(粘住用户),本章说无状态=可替换——两者有张力:粘性让用户固定在一台(省事),但机器挂了用户会话就丢;无状态让任何机器都能接(挂了换一台用户无感)——阶段 4 的答案:无状态优先(Session 已在 Redis,粘性的收益没了,可替换的收益实打实)——"能替换"是冗余的前提:无状态 = 高可用的地基(13 章 Session 决策的价值,到这里收成高可用的前提)。

无状态 = 可替换(13/16/23 章伏笔的完整兑现):冗余的前提是"哪台机器都行"——为什么应用层可以随便冗余?因为 Session 入了 Redis(13 章决策 + 16 章缓存 + 23 章多实例兑现)——应用变成无状态:任何一台机器都能服务任何请求,挂了换一台,用户无感——"无状态"是高可用的前提:有状态的组件(数据库)最难冗余(下一节)

24.3 数据层的冗余:主从与备份

第二道防线的一半:数据不能丢、数据库不能单点(23 章伏笔兑现)。应用层冗余了,数据库还是单点——它挂了,前面所有的冗余都白搭(23.1 说过"数据层的单点是最后一块短板")。

本章与第 3 章总架构的关系(它是"架构图"的运行视角):第 3 章的总架构画了系统的组成(LB/应用/数据库/缓存)——本章是那张图的"故障演练版":每一块都可能挂,每一块挂了都有对应机制——LB 挂(24.2 冗余 LB)、应用挂(24.2 多实例)、数据库挂(24.3 主从)、缓存挂(16 章重建+24.5 降级)、流量超(24.4 限流)——"看懂架构图"的完整含义:知道每一块挂了会怎样(第 1 章地图:架构不是画着好看的,是故障时的作战地图)。

主从复制(23.2 预告的"主从/读写分离"):一台主库(写)+ 一台或多台从库(读)——主库的每次写入,异步复制到从库——读请求打到从库,写请求打到主库(读写分离)。解决两个问题:

  • 读压力分摊:社区产品的流量大头是读(内容/评论——16 章缓存放过了热点,剩下的读打从库);
  • 主库挂了有备:主库故障 → 从库提升为主库(故障转移/failover)——恢复时间从"重新部署一台数据库"变成"切换"(分钟级,23 章发布切换的思想,数据版)。

主从与第 14 章索引的关系(它是"读优化"的延续):14 章用索引让查询变快(读优化),本章用从库让读压力分摊(读冗余)——"读"是社区产品的主战场(16 章缓存、14 章索引、本章从库——三个工具,同一目标:读扛得住)——但缓存/索引/从库各管一段:缓存管热点(16 章)、索引管查询(14 章)、从库管总量(本章)——三层读防线,缺一层都会在某一类流量下暴露

备份(14 章伏笔兑现——"第 24 章高可用会展开"):主从 ≠ 备份——从库是"主库坏了"的冗余,不是"数据丢了"的保险:手滑删库(DELETE 忘加 WHERE)、勒索软件、机房火灾——主从一起没(14 章说过:备份防的是"磁盘坏了/手滑删库")。所以:每天全量备份 + 定期演练恢复(备份能不能用,只有恢复过才知道——23 章"演练"思想,数据版)。

故障转移的"切换"与发布切换的类比(它是"切换"思想的跨章呼应):23 章说发布从"替换"变成"切换"(蓝绿:流量切到新版本)——故障转移也是切换(流量切到新主库)——同一个思想:"切换"比"重建"快、比"重建"可逆——23 章切换的是版本(回滚),本章切换的是机器(故障转移)——"切换"是部署与高可用共同的核心动词(23 章"无感知发布",本章"无感知故障转移"——用户视角都是"没感觉")。

主从的"复制延迟"与业务的关系(它是"异步的代价"在数据层的形态):复制是异步的(写完主库就返回,从库慢慢复制)——延迟窗口内,从库的数据是旧的——读写分离的读(浏览内容)能容忍几秒延迟;但"写后立刻读"的场景(下单后查订单)不能走从库(17 章交易的强一致要求——17 章说过异步要有取舍)——"数据从哪读"由一致性要求决定(14 章事务隔离级别的同一个思想:一致性要求决定技术选择)。

主从复制与读写分离的"读多写少"前提(它是"为什么从库能扛读"的机制):社区产品读多写少(浏览 > 发帖下单)——从库水平扩展(多台从库),读压力越摊越薄——但写压力还在主库(单点)——读写分离的边界:它解决了"读挂",没解决"写挂"(写挂了还是要切换——主从的完整意义是"写的备用");从库延迟(复制是异步的——写完主库立刻查从库可能查不到——"读自己的写"要路由到主库,这是读写分离最常见的坑:写完读不到)。

故障转移的"演练"细说(它是"切换"的训练):主从切换要练到什么程度?——练到"闭眼能切":值班的人被叫醒(困、慌),30 秒内要能完成切换——没练过的切换,事故时没人敢按(23 章"敢部署的前提是能回滚"——本章"敢切换的前提是练过切换")——演练的标准:事故时的动作 = 演练时的动作(18 章"测试是承诺"——演练是"切换承诺"的测试)。

故障转移的代价(它是"数据冗余"的边界):切换不是免费的——主从切换有延迟(主库宕机时,最后几秒的写入可能没复制完——切换会丢一点数据,或至少要有"丢多少"的预案);切换本身要演练(没练过的切换,事故时不敢切)——数据层的冗余是三道防线里最重的(机器贵、机制复杂、代价是数据)。

备份的恢复演练(它是"备份能不能用"的验证):备份要定期恢复演练(把备份恢复到一台新机器上,看能不能起来、数据全不全——没恢复过=不知道备份坏没坏,15 章"约束是机制在才算数"的数据版)——备份的两个坑只备不验(备了但从没恢复过——事故时发现备份是坏的);备了不回(备份存在同一台机器/同一个机房——机器没了备份也没了——备份要异地/异机,阶段 4 至少异机:另一台机器存一份)。

数据安全的三层(它是"数据不能丢"(第 4 章)的完整答案):事务(14 章:进程崩溃不丢)→ 备份(14 章+本章:磁盘坏/手滑不丢)→ 冗余/主从(本章:数据库挂能用)——三层防的是三种不同的"丢",缺一层都不完整(17 章"防线是叠起来的"——数据安全是叠出来的)。

24.4 限流:把流量闸住

第二道防线:别冲垮——雪崩事故的直接教训:数据库不是被"正常流量"压垮的,是被"超出来的流量 + 重试"压垮的——所以要在入口把流量闸住。

限流(rate limiting)单位时间内最多放进来多少请求,超出的拒绝或排队——雪崩事故里,如果数据库前面有一道闸(16 章说的"数据库前面加一道闸"),连接池就不会被打满。

限流与第 16 章缓存雪崩的关系(它是"限流不是新东西"的呼应):16 章缓存雪崩的对策里就有限流("数据库前面加一道闸")——本章是那道闸的正式展开:缓存雪崩是"同一时刻大量 key 过期",本章的雪崩是"流量超量 + 重试放大"——两种雪崩,同一个对策方向:在数据库前面把流量闸住(16 章用 TTL 随机化让请求错开,本章用限流让请求总量受控——两道闸一起,数据库才安全)。

限流中间件(12 章伏笔兑现——"这个 IP 每秒最多 10 个请求"(第 24 章展开)):12 章把限流放进中间件管道(鉴权之后、路由之前)——每个请求进业务逻辑前先过闸。两个粒度:

  • 按 IP:单个 IP 每秒最多 N 个请求(防爬虫/防单点攻击——19 章攻击者视角的防线);
  • 按接口/总量:整个服务的每秒上限(雪崩事故防的是这个:不管谁来的,总量闸住,数据库就有救)。
图24-2 图稿占位
限流熔断降级
保护与止损的组合拳

为什么说"限流是服务端的第一道闸"(它是 12 章伏笔的完整兑现):12 章说"CDN 是第一道墙,限流中间件是服务端的第一道闸"——完整的防线序列:CDN(11 章:静态层,挡大部分流量)→ 入口限流(24.4:闸住总量)→ 应用内限流(按 IP/接口细分)→ 中间件(12 章:鉴权)→ 业务逻辑——请求每过一道闸,系统就多一分保护(19 章纵深防御的流量版:同样的"层层设卡"思想)。

限流算法("怎么判断超没超",三个名词级):固定窗口(每秒一个计数器,到点清零——简单,但窗口边界可能放两倍流量);滑动窗口(把窗口切成小段,滚动统计——更准);令牌桶(桶里定时加令牌,请求消耗令牌——能容忍突发,同时有总量上限——阶段 4 用令牌桶:社区产品有"热点突发"的常态,令牌桶允许短时突发又拦住持续洪峰)。

限流与 23 章配置管理的关系(它是"限流阈值"的运维):限流阈值是行为类配置(23.5)——不发布也能改(流量变了调阈值,不用发版)——但"能改"要配"能看":阈值改动了要记录(23.5 配置变更走评审——限流阈值是保护机制的强度,乱改 = 保护失效)——配置管理(23)与高可用(24)在这里相遇:保护机制的参数,也要被管理(20 章轨道思想)。

阈值怎么定(它是"限流不是拍脑袋"的答案):正常峰值 × 余量系数——阶段 4:正常峰值 800 QPS,限流 2000 QPS(2.5 倍余量)——限流阈值是配置(23.5 的"行为类配置":限流阈值/功能开关,不发布也能改)——压测(24.6)会告诉你真实的拐点,拐点才是阈值的依据

限流与攻击的关系(它是"闸门"的安全侧):19 章的攻击者会用大量请求打系统(爬虫/撞库/刷接口)——限流是对这类攻击的第一道防线(不管谁来的,总量闸住)——限流和认证(13 章)配合:未登录的限得更狠(匿名流量更可疑)、登录的放宽(正常用户)——"闸门"按身份分级,是 19 章最小权限的流量版

限流在哪一层(它是"闸门位置"的选择):入口限流(LB/网关——所有流量先过闸)应用内限流(中间件——按接口/按用户细分)——两层都要:入口闸住总量(防雪崩),应用内细分(防单个用户/接口打爆——19 章的攻击者视角:限流是服务端防爬虫/防爆破的第一道闸)。

限流的"数据"(它是"限流也要可观测"的预告):限流拦了多少请求?——拦掉的请求要数得出来(日志/指标):限流没拦到(阈值太高,形同虚设)和限流拦太多(阈值太低,误伤正常用户)都要看得见——"保护机制的运行情况"也是运行数据(本章机制 + 观测合起来才是闭环——下一章的主角)。

限流的选择:拒绝 vs 排队(它是"用户体验"的取舍):超出的请求——拒绝(返回 429"太忙了",让客户端退避重试——17 章幂等键+重试的配合)或排队(在闸口等一会儿,请求过时再放弃)——拒绝是"保护系统",排队是"尽力而为"——阶段 4 选拒绝(简单、保护彻底;排队还要管理队列本身,是 26 章消息队列的活)。

限流的误伤与补偿(它是"代价"的落地):限流误伤了正常用户怎么办?——429 的响应要友好("服务繁忙,请稍后再试"+Retry-After 告诉客户端等多久再试——第 7 章错误码规范:限流有自己的错误码);客户端要尊重 429(退避重试,不是死磕——17 章幂等+重试的配合)——限流是系统和用户之间的约定:你让我喘口气,我让你别断线(16 章缓存随机的哲学:大家都错开,大家都好)。

限流的代价(它是"第二道防线"的边界):限流会误伤——正常用户的高峰期也可能被 429(余量系数没留够时)——限流的本质是"牺牲一部分请求,保住整个系统"(17 章说过"幂等假设请求会重复"——限流反过来:宁可拒绝不可拖死)。

24.5 熔断与降级:止损

第三道防线:别拖垮全站——雪崩事故里最致命的一环:数据库慢了,应用在等;应用等了,请求堆积;请求堆积,应用也挂了——故障像多米诺骨牌一样传播。熔断与降级是切断传播的机制。

超时与第 10 章的关系(它是"超时"思想的跨章延续):10 章认识过"超时"——握手失败的一种场景(服务器没响应:防火墙拦了/服务器挂了)——本章把超时升级为服务端原则:每一个依赖调用都要有超时(数据库、缓存、第三方接口——19 章审查过的依赖面)——"没有超时的调用 = 线程被永久占用"——超时是熔断的前置条件:没有超时,熔断的"快速失败"无从谈起(请求永远在等,谈不上快速)。

依赖链与超时(它是"雪崩怎么传播"的机制):应用依赖数据库/缓存/第三方接口(19 章说过的依赖面)——每个依赖的调用都要有超时(连接超时 + 读超时):没有超时的调用 = 无限期等待 = 线程被占满 = 应用跟着挂——超时是第一道保险:等不到就放弃(10 章认识过"超时"——HTTP 层的思想,服务端同样适用)。

超时的取值(它是"超时设多少"的实践):超时不是拍脑袋——超时 = 正常耗时 × 余量(正常 100ms 的数据库查询,超时设 1s——留 10 倍余量)——超时太短(正常请求被误杀)、超时太长(故障时照样拖垮)——超时和限流阈值一样,要压测校准(24.6:压测时看真实耗时分布,P95×系数就是超时——16 章预算思想:先测量再定值)。

熔断的"保护对象"(它是"熔断保护谁"的澄清):熔断保护的是调用方(应用),不是被调方(数据库)——数据库挂了,熔断让应用不继续往里冲(少添乱);同时数据库也有自己的保护(连接池打满后拒绝新连接——数据库自保)——双向保护:应用侧熔断(不冲了)+ 数据侧自保(别接了)——雪崩事故里如果两侧都有,数据库不会被打死("加一道闸"的完整版)。

熔断(circuit breaker)(它是"超时不够"的答案):超时只救了"这一次调用",救不了"这个依赖已经挂了"——依赖挂了,每个请求都等超时才放弃,系统还是被拖慢——熔断:检测到依赖连续失败(比如错误率 50%),直接断开——后续请求不再调用它,立即返回失败(快速失败)。三态:

  • 关闭(正常:请求照走);
  • 打开(故障:请求直接失败,不调依赖——让依赖有时间恢复);
  • 半开(试探:放少量请求过去试试——恢复了吗?恢复就关闭,没恢复继续打开)。

熔断的三态是"状态机"(17 章状态机的又一个实例:关→开→半开→关,每个状态有明确的转移条件——状态机思想从订单扩展到系统健康)。

降级与缓存的配合(它是"兜底数据从哪来"):降级给"旧版本"——旧版本哪来的?——缓存(16 章):缓存里存着上一份成功的数据,依赖挂了,缓存还在——"缓存是高可用的最后一道靠山"(16 章缓存管快,本章缓存管"挂了还有旧数据"——同一份缓存,两个用途:快 + 兜底)——缓存的 TTL 设计要想到这个用途:降级时希望缓存还在,TTL 就不能太短(16 章 TTL 60s——降级时 60s 的旧数据比没有好)。

熔断与重试的配合(它是"别让重试放大故障"的机制):17 章说过重试是交易系统的防线(幂等键让重试安全)——但重试在故障期是放大器:依赖挂了,客户端/网关自动重试 = 更多请求涌向已挂的依赖——熔断的"快速失败"就是重试的刹车:熔断打开期间,重试立即失败(不再制造新的流量)——重试要有上限(最多 N 次)+ 要有退避(等一会儿再试)+ 要认熔断(熔断打开就停)——"重试救单个请求,熔断救整个系统"

降级(fallback)(它是"失败之后给什么"的答案):熔断是"不调了",降级是"不调了之后给用户什么"——兜底响应:热点内容挂了给缓存中的旧版本(16 章缓存的价值:挂了还有旧数据可给);下单挂了给友好的错误页("支付通道拥挤,请稍后再试"——比 500 好一万倍);非核心功能挂了直接隐藏(评论挂了先不显示——功能降级:保核心,舍非核心)。

降级 vs 限流的区别(它是"两道闸"的澄清):限流挡进来的流量(入口),降级处理依赖的失败(出口)——限流:"你别进来太多";降级:"你拿不到就算了,我给你别的"——两个方向,一个目标:让系统在异常下仍能部分工作——24.4 说"宁可拒绝不可拖死",24.5 说"保核心舍非核心"——拒绝是保护自己,降级是服务用户(两道闸的哲学各一半)。

降级的优先级(它是"保核心舍非核心"的排序):降级不是"全都给个兜底"——先保什么,由业务定:交易(17 章)最不能降(钱的信任)、内容浏览其次(降级给旧数据)、评论/搜索可以降(功能暂时隐藏)——降级清单要提前列好(事故时不用想:"哪个功能先降"是早就排好的)——这和第 19 章"安全投入按风险排序"、23 章"配置变更按风险分级"是同一个逻辑:按重要性排序,出事按序执行

雪崩的完整机制(它是本章事故的"为什么"):流量超限(限流没拦住)→ 依赖变慢 → 超时堆积 → 重试放大(雪崩最狠的一环:请求失败后客户端/网关自动重试,重试是新的流量——17 章说过重试要有幂等,这里说重试会放大故障)→ 全站拖垮——限流、超时、熔断、降级,每一道都是链条上的一刀(砍在雪崩传播的不同位置)。

本章的"防线"与 17/19 章防线的同源(它是引导词的跨章放大):17 章防线(状态机/事务/幂等)防"交易出错",19 章防线(纵深防御)防"被攻击",本章防线(冗余/限流/熔断)防"系统挂掉"——三个方向的"防线",同一个思想:把不可控的坏情况,变成可控的机制(17 章说"错误是环境的必然"——挂也是环境的必然——机制是应对必然的唯一方式)。

止损的哲学(它是第三道防线的总结):"坏影响不扩散"——一台机器挂了(24.2 冗余兜住)、一个依赖挂了(24.5 熔断降级兜住)、一波流量超了(24.4 限流兜住)——高可用的全部机制,都是把"故障的爆炸半径"控制住(17 章交易正确性管"钱不能错",本章管"挂不能扩散")。

24.6 容量与压测:挂之前先知道

三道防线讲完,还有一个问题:"看起来够用"为什么还是挂?——雪崩事故里,数据库平时负载不高,但**"平时"不等于"峰值"**——容量管理(capability planning)是"高可用的预算"(16 章预算思想的运维版)。

容量与 16 章性能预算的关系(它是"预算"思想的延续):16 章把 P95 预算拆到每一层(中间件 10ms/控制器 5ms…),本章把"可用性预算"拆到每一个组件(应用 99.9%×数据库 99.9%×…)——同一个思想:目标拆到部件,谁超标一目了然——性能预算管"快不快",可用性预算管"挂不挂",容量规划管"够不够"——三个预算,一套思想(第 5 章决策记录"预算"系列)。

容量与业务方的协作(它是"容量不是工程师一个人的事"):容量规划要业务方参与——运营的活动预告(下周末大促?)、市场的推广计划(要投广告?)——业务方知道的峰值,比监控曲线早(监控看到的是过去,业务看到的是未来)——容量规划的流程:业务预告 → 工程师评估(峰值×余量)→ 不够就提前扩/压测验证——这是"业务逼出技术"(第 1 章)在运行期的形态:业务预期逼出容量决策

容量规划(它是"够不够用"的量化):容量 = 峰值流量 × 余量系数——预估方法:从业务拿预期(活动/大促/热点预告——运营说"下周末有活动",工程师就要提前算)、从历史拿趋势(16 章监控的历史曲线,按趋势外推)——容量不是"现在够用",是"可预期的将来够用"

压测与 18 章测试体系的衔接(它是"测试"的完整版图):18 章说测试金字塔(单元/集成/E2E)验证"对不对"——压测验证"扛不扛",是金字塔旁边的第二根柱子——两类测试的回答:功能测试回答"逻辑对吗",压测回答"压力来时系统还对吗"——18 章说"测试是承诺":压测是"可用性承诺"的测试(24.1 定了 99.9%——压测证明系统离这个目标有多远)。

压测(load testing)(它是"拐点怎么知道"的答案):模拟流量逐渐加压,找到系统的"拐点"——单机 1500 QPS 时数据库连接先到顶(这是 24.4 限流阈值 2000 的依据——压测数据说话,不拍脑袋)——压测与 18 章测试的关系:功能测试验证"对不对",压测验证"扛不扛"——两类测试,一个都不能少

压测与监控的衔接(它是"压测结果怎么用"):压测的拐点数据(单机 1500 QPS)要成为监控的基准(生产指标接近拐点 → 告警——"离拐点还有多远"是容量告警的标尺)——压测是一次性的测量,监控是持续的测量(16 章说"先度量,再优化"——压测把度量做一次,监控把度量做一辈子)。

压测的环境(它是"在哪压"的实践):压测不能在生产压(把生产压挂了——事故×2)——压测环境要和生产等规模(23 章容器:同镜像同配置——压测环境 = 生产的复制品)——阶段 4 的压测:测试环境 + 生产的镜像/配置(23.3 容器的收益:环境一致,压测结果才可信)——"压测结果可信"的前提是"环境一致"(21 章复现思想:环境不一致,结果不可信)。

压测的三种类型("怎么测"的三个层次):容量测试(压到拐点:极限在哪)、稳定性测试(持续加压:长时间跑会不会漏——内存泄漏/连接泄漏是慢病)、突发测试(模拟峰值突增:雪崩事故就是突发——阶段 4 最该测的是突发:社区产品的常态是"平时闲、突然爆")。

水平扩容与 23 章多实例的衔接(它是"加机器"的完整流程):23 章建好了多实例+LB——水平扩容 = 再多加一台实例(同样的制品(21 章)、同样的镜像、LB 自动把流量分过去)——"扩容"不是新动作,是前面章节的顺水推舟——垂直扩容的边界(换大机器:数据库的短期出路——主从把读摊了,写的垂直扩容是过渡,26 章架构演进会看到写的终极解法:拆服务/分库)——扩容的尽头是架构问题(26 章预告)。

扩容的两种方式(它是"不够了怎么办"的答案):垂直扩容(换更大的机器——简单,有上限);水平扩容(加机器——多实例的形态,无上限,但要有 LB/无状态的前提)——阶段 4 的答案:水平(应用层已经无状态、已经有 LB——加机器就行;数据库先垂直(主从已是水平读)——扩容方式不是偏好,是前面章节铺垫的兑现(无状态/LB 建好了,水平扩容是水到渠成)。

容量管理的代价(它是高可用的账单之一):压测要时间、容量要机器(备着不用的机器也是钱)——"高可用是概率管理"的另一半:容量是花钱买概率(多备一台机器,就是把"扛不住"的概率降一点——买多少,回到 24.1 的 99.9% 目标)。

24.7 故障演练与 on-call:出事时有人会处理

最后一道保障不是技术,是人和流程——24.1 选了 99.9%(一年 8.76 小时),这 8.76 小时怎么花?取决于出事后多快能恢复——恢复速度(MTTR)决定了目标能不能达成。

故障演练(23.6 兑现——"第 24 章会看到'演练'成为高可用的日常"):主动制造故障,验证系统扛得住——23 章演练的是"回滚",本章演练的是"故障":定期(比如每月)选一个组件故意弄挂——数据库主库挂(验证切换)一台应用挂(验证 LB 摘除)流量暴涨(验证限流)——演练的意义:事故时的每一步,都必须是练过的(15 章"约束是机制在才算数"——高可用机制要证明过才算数)。

演练的"红队"视角(它是"演练"的进阶):演练除了"自己弄挂",还有红队演练(专门有人扮演"搞破坏的":模拟攻击/模拟故障——19 章安全测试的兄弟)——自己弄挂验证"机制在不在",红队弄挂验证"机制扛不扛得住意外"——阶段 4 先做"自己弄挂"(红队是安全团队的活,8 人团队不折腾)——知道红队存在,将来规模到了再说(24.1 演进条件思想)。

故障演练的范围(它是"练什么"的清单):演练不是"把系统弄挂一次"就完——每道防线都要练:冗余(拔掉一台应用——LB 摘除了吗)、数据(拔掉主库——切换了吗)、限流(压到阈值以上——429 了吗)、熔断(弄挂一个依赖——熔断打开了吗)——练过的防线才是防线(没练过的防线是"理论上存在"——15 章"约束是机制在才算数":高可用版本的"练过才算数")。

混沌工程(它是演练的进阶,一句):在生产环境随机注入故障(Netflix 的 Chaos Monkey 是鼻祖)——演练测试环境会"手下留情",生产环境才真实——阶段 4 不用(8 人团队玩不起),但要知道它的存在:高可用做到极致,就是"随时准备着它挂"

on-call 与 20 章协作轨道的关系(它是"流程"的延续):20 章说"流程是把一个人的经验变成团队的能力"——on-call 是运行期的协作轨道:值班表(谁负责)、升级路径(处理不了找谁)、交接文档(值班人要知道系统最近改了什么——23 章发布记录)——开发期的轨道管"代码怎么改",运行期的轨道管"故障怎么处理"(20 章说"流程是团队的函数"——on-call 是运行期的那个函数)。

事故中的"信息真空"(它是 24.7 → 25 章的第二处预告):雪崩事故时最难受的不是系统挂——是不知道挂在哪:应用还是数据库?流量问题还是代码问题?没有监控(25 章),排障靠猜——"谁在电脑前谁处理"的老规矩,变成了"谁在电脑前谁猜"——本章的机制解决"挂了怎么办",25 章解决"挂了怎么知道"——两章合起来,才是完整的事故应对。

on-call 值班(它是阶段 4 的关键事件——五阶段故事线:"首次引入 on-call"):故障要有人第一时间处理——阶段 4 之前:线上故障"谁在电脑前谁处理"(故事线原话:靠"谁在电脑前谁处理");引入 on-call 后:每周一个人值班,出事了值班的人被叫醒处理——"处理"的第一步永远是:先恢复,再查因(23 章的回滚按钮:出事了先按回滚,再慢慢看——恢复优先:用户先能用,真相慢慢找)。

on-call 与可观测性的预告(它是 24.7 → 25 章的衔接):值班的人被叫醒后要"看得见"——监控告警是 on-call 的眼睛:没有告警,值班等于"等用户投诉";告警太多,值班等于"狼来了"——on-call 引入了,下一章就把"看得见"补齐——本章事故里"告警半小时后才响"的教训,下一章会正面回答。

on-call 的代价(它是"人"这道防线的边界):值班很累(半夜被叫醒)、值班要有支撑(处理不了要能升级——20 章的协作轨道,故障版);on-call 的价值(为什么这笔投入要花):它是可用性目标的最后一环——机制再好,没人处理,99.9% 也是纸上的(24.1 的"目标 → 机制 → 人",闭环了)。

值班清单(它是"会处理"的具体所指):值班的人手里要有一张处理清单(出事了照着走,不靠临场发挥):① 先恢复(回滚/重启/切流量——23 章的回滚按钮,先让用户能用);② 再判断(影响面多大:全站还是单接口——对应 24.2-24.5 哪道防线);③ 升级(处理不了 30 分钟内叫醒负责人——on-call 不是一个人扛,是"第一响应人");④ 记录(时间线记下来——复盘要用)——清单的意义:事故时人的判断会失灵(困、慌、信息乱),清单是兜底的判断(20 章说"流程是团队的函数"——值班清单是它的故障版)。

事故复盘(它是 on-call 的配套):每次事故后写复盘(18 章"事故=漏掉的承诺"):发生了什么 → 为什么 → 哪道防线漏了 → 补什么机制——雪崩事故的复盘产出:限流(24.4)→ 熔断(24.5)→ 压测(24.6)→ on-call(24.7)——本章的每一节,都是上一场事故的复盘结论(第 2 章演进循环:事故是演进之母)。

24.8 本章对应表

业务诉求技术选择为什么代价/取舍
"不能挂"要变成硬指标可用性目标(几个 9,★决策第 16 次)把承诺翻译成可度量的数字(99.9%=年宕机 8.76 小时)目标≠现实,靠后面所有机制兑现
一台挂了全站挂冗余:多实例 + LB(图 24-1)故障概率的乘法(1%×1%=万分之一)机器成本翻倍;LB 自己也是单点
数据库是单点主从 + 备份 + 故障转移有状态最难冗余;数据三层防护主从延迟、切换要演练、备份要验证
流量突增打垮系统限流(图 24-2:闸门)宁可拒绝不可拖死(429)误伤正常用户;阈值要压测校准
依赖故障拖垮全站熔断 + 降级(图 24-2:开关/兜底)超时救单次,熔断救全局;坏影响不扩散降级=体验下降;功能取舍
"看起来够用"还是挂容量规划 + 压测拐点要压测才知道(单机 1500 QPS)压测投入;备机是钱
出事了没人处理故障演练 + on-call恢复速度决定 99.9% 能否达成;先恢复再查因值班成本;演练时间

每一行都在本章正文里有完整论证:目标在 24.1,冗余在 24.2,数据在 24.3,限流在 24.4,熔断降级在 24.5,容量在 24.6,人在 24.7。先看"业务诉求"列——每一行都是一类事故的答案(本章的全部机制,都是被事故逼出来的)。

本章小结

高可用速查(第六部分回指本章时翻回这里):

  1. 可用性目标:99.9%(★决策第 16 次)——"不能挂"= 年宕机 8.76 小时;可用性预算=串联乘积,最弱一环拖底线;
  2. 第一道防线·冗余:多实例+LB(四层/七层/健康检查摘除)——故障概率乘法;LB 也要冗余;无状态=可替换(Session 入 Redis 的完整意义);
  3. 数据防线:主从+读写分离+故障转移;主从≠备份(每天备份+演练恢复);数据三层防护(事务/备份/冗余);
  4. 第二道防线·限流:中间件(12 章兑现)、令牌桶、阈值=峰值×余量(压测校准)、429 拒绝;
  5. 第三道防线·熔断降级:超时→熔断(三态状态机:关/开/半开)→降级(兜底:旧缓存/友好页/隐藏非核心);重试会放大故障;
  6. 容量与压测:容量=峰值×余量;压测找拐点;水平扩容(无状态+LB 的兑现);
  7. 演练 + on-call:先恢复再查因;事故复盘=下一道防线的来源(本章每一节都是上一场事故的结论)。 (七条速查对应本章结构:1 是"目标",2-3 是"冗余",4 是"限流",5 是"熔断降级",6 是"容量",7 是"人与流程"——第 25 章可观测性回指时重点翻 6(容量)和 7(恢复)。)

  • 高可用是概率管理:99.9% 不是"永不挂",是"一年挂 8.76 小时以内"——目标、机制、人,三环闭环;
  • 三道防线:冗余(挂了还有)、限流(别冲垮)、熔断降级(别拖垮全站)——每一道都是一类事故的答案;
  • 数据是最难冗余的一环:主从+备份+故障转移,三层防护(事务/备份/冗余)叠出"数据不能丢";
  • 事故是演进之母:本章每一节,都是上一场事故的复盘结论(第 2 章演进循环的运维版)。

下一章

三道防线建好了,on-call 也值班了。但第一次值班,你就遇到最难的问题:用户说"打不开",可系统监控一切正常——问题到底出在哪?——你只能看见"系统活着",看不见"一次请求内部发生了什么"。

第 25 章,可观测性:日志、监控、链路追踪——挂了或慢了,怎么知道是哪里——这是本章 on-call 的武器库,也是全书"排障"主题的收官(第 15 章联调排查法的运行期升级版)。