第 24 章 高可用:业务不能挂的保障
副题:三道防线——挂了还有(冗余)、别冲垮(限流)、别拖垮全站(熔断降级);可用性是概率管理,不是"永不出错"
第 23 章把制品放上了服务器:多实例、容器、配置管理、发布回滚。业务在跑。但"在跑"不等于"不会挂"——服务器硬件会坏、进程会崩、流量会突然暴涨。本章回答:业务不能挂,靠什么保障?
读完本章,要建立三条认知——它们合起来回答一个问题:高可用到底是什么。
- 高可用是概率管理:"不能挂"的真实含义不是"永不挂",而是把挂的概率降到可接受的范围——工程上用"几个 9"把这个模糊的愿望变成可度量、可考核的目标;
- 三道防线:冗余——挂了还有备用的;限流——别让流量冲垮;熔断降级——别让一个组件拖垮全站。三道防线各有分工,每一道都是被一类真实事故逼出来的;
- 可用性是设计出来的,不是运气:定目标、建机制、做演练、排值班——每个环节都要有人负责、有机制兜底,而不是靠祈祷。
24.1 宕机成本与可用性目标:雪崩事故
阶段 4 的一个晚上,一篇内容突然爆了,被论坛转载,流量瞬间翻了几倍。应用层扛住了——多实例没白建——但所有请求都在查数据库,数据库连接池被打满。后面的请求开始排队,排队超时,超时的请求被重试,重试又占用新的连接。连锁反应就此启动:数据库越来越慢,应用等待越来越多,最终全站 502。故事线的事故档案里,这起登记为"热点内容、流量暴涨、数据库连接池打满、全站 502"。
现场细节补两笔,它们解释了"雪崩"为什么叫雪崩。第一,告警是半小时后才响的——连接池打满是渐进的,监控阈值又设得太高,监控体系的问题下一章展开,这里先立事故的印象。第二,值班的人被叫醒时,全站已经 502 了——值班制度刚引入,但"有值班"和"会处理"是两件事。第三,恢复用了 40 分钟:重启数据库、清连接、重启应用,一步步手工来。40 分钟乘以每分钟的订单损失,就是这次事故的直接账单。
雪崩不是玄学,它有清晰的机制,三个阶段。触发器:热点流量超过了正常量级,某个单点瞬间成为瓶颈——这次是数据库。放大器:请求排队,排队超时,超时重试,重试发出新请求——排队和重试互相喂养,连接池被打满,雪崩正式开始。传导:数据库慢,应用等待,应用线程耗尽,应用也挂——故障从数据层传到了应用层。记住这三步,本章的每一道防线都针对其中一步:限流砍触发器,超时砍放大器,熔断砍传导。
宕机是要花钱的,这笔账算清楚,才知道高可用为什么是硬指标。交易中断:宕机一小时,这一小时的下单全部失败,直接损失等于每小时订单量乘以客单价。用户流失:打不开的用户会走——社区产品的口碑是"随时都在",一次打不开,一部分用户就去了别处。信任损伤:交易类产品最怕"不靠谱",用户敢不敢把钱放进你的系统,取决于你挂不挂。
这笔账和用户行为还有一个关系,它解释了为什么可用性目标宁可紧不可松。用户对"挂"的容忍度是一条曲线:第一次打不开,下次还来;连续两次,开始怀疑;交易的时候挂,直接走人——交易的信任成本最高。社区加交易的产品形态里,"交易时挂"是最贵的挂法,钱和信任一起丢。所以后面排防线优先级时有一条铁律:交易相关的数据防线,优先级高于一切;降级清单里,交易永远最后降。
"不能挂"这三个字,到了工程师手里要翻译成硬指标,翻译的工具就是可用性目标,俗称"几个 9"。可用性等于正常运行时间除以总时间:99.9% 意味着一年最多宕机 8.76 小时,99.99% 意味着一年最多 52.6 分钟。注意,几个 9 首先是给业务方的承诺——业务说"不能挂",工程师要翻译成"99.9%,一年最多 8.76 小时",这就是把业务语言翻译成技术语言。
几个 9 谁说了算?不是工程师,也不是业务拍脑袋,是商业目标换算过来的。业务方要回答一个问题:宕机一小时,损失多少能接受?回答"一小时损失一万块",那么 99.9% 够用——一年 8.76 小时,约等于 8.76 万的损失预算;回答"一分钟都不能断",那就要 99.99% 以上,双机房的预算也随之而来。公式一句话:业务定损失,工程师算概率。
可用性目标定几个 9?这是决策模型的又一次应用。① 目标:给"不能挂"一个可度量、可验收的承诺。② 约束:阶段 4 的现状——8 人团队、无专职运维、值班刚引入、机器两三台、预算有限。③ 问题:定几个 9?④ 候选:A,99.9%,年宕机 8.76 小时;B,99.99%,年宕机 52.6 分钟。⑤ 维度:业务需求——宕机一小时损失可接受吗;达成成本——99.99% 需要什么;团队能力——8 人扛得起吗。⑥ 打分:99.9% 的业务含义是月宕机约 43 分钟,社区产品可接受;99.99% 的达成成本是双机房、多可用区、专职 SRE、复杂的故障转移——单机房物理上做不出 99.99%,一个机房断电就超了,而 8 人团队没有这份人力。⑦ 选择:A,99.9%——目标配得上阶段 4 的业务与团队。⑧ 收益:目标明确,所有机制有了验收线——预算思想的又一次应用。⑨ 演进条件:交易占比涨到"宕机一小时等于不可承受"时,再评估 99.99%,那时双机房和专职 SRE 的预算才花得值。
这个决策还收束了一条"承诺链"。流水线那章说过:敢部署的前提是能回滚。本章接上下半句:敢承诺 99.9% 的前提是机制在。链条完整了:业务说"不能挂",工程师翻译成"99.9%",机制把 99.9% 兜住,演练把机制证明。每一环都是下一环的前提——承诺要有机制兑现,机制要有演练证明,这条原则从测试那一章一路走来,到这里成了主干。
目标定了怎么验收?不是年末算账,是月度复盘:本月宕机多久,监控数据说了算;超了没有;超了是哪道防线漏的;下月补什么。目标、测量、复盘,三个词构成可用性的闭环——测量、对比、修正,这个循环在性能预算上跑过,在这里原样复用。
可用性目标和最初的约束清单也要对上关系。验收清单里的"首页要快""数据不能丢",是功能级承诺——平时看得见,验收时一条条对。可用性目标是运行级承诺:平时看不见,挂了才兑现。承诺因此分两层:功能承诺,靠测试验证;运行承诺,靠目标加机制验证。每层承诺都要有验收,有数字才算数。
照例,决策记录又添一行。回看这张表,已经密密麻麻:分层、认证方案、数据库选型、缓存引入、测试投入、安全投入、工作流、自动化程度、容器与编排,再到今天的可用性目标。这一行特殊:别的行定"选什么",这一行定"承诺多高"。而记录里每一行,都是某个阶段"业务逼出技术"的坐标——将来演进时,翻的就是这些记录。
目标定了,还要知道 99.9% 靠什么凑出来。这里有一个乘法:系统可用性约等于各组件可用性的乘积。请求要穿过所有组件,任何一个挂了,请求就失败:应用 99.9% 乘数据库 99.9% 乘网络 99.9%,整体只剩约 99.7%。两个结论。第一,整体永远低于最弱的一环——数据库那块短板还在,它直接决定预算的底线。第二,可用性预算的意义在于拆分:把 99.9% 拆到每个组件头上,谁拖后腿一目了然。当年把延迟预算拆到每一层,同一个思想。
"概率管理"还有一层含义,值得单独说透:接受"挂"是常态。正因为挂是常态,系统设计要面向故障:默认依赖会挂,超时和熔断兜着;默认机器会坏,冗余兜着;默认流量会超,限流兜着。这和安全那一章的思想是同一个:默认接受最坏情况,机制处理最坏情况。面向故障设计,是本章所有机制的设计哲学。
最后把本章的第一条认知钉死。高可用是概率管理,不是永不出错。选 99.9%,就意味着接受一年 8.76 小时的宕机——验收标准是"挂多久以内算达标",不是"尽力不挂"的愿望。这个认知重要,因为它决定钱花在哪:追求"永不挂"的团队,把钱砸在最后一公里的 99.99%;接受"会挂、但挂了有机制"的团队,把钱花在恢复上。本章后面写的,全是恢复的机制。
24.2 冗余:把单点变多点
第一道防线,一句话:挂了还有。核心思想是冗余——任何单点,都备一份。上一章的多实例已经是冗余的一种,本章把它讲完整。
冗余的第一个组件是负载均衡,上一章它露过脸,这里是完整形态:一个入口,多台机器,把请求分发下去。
冗余的极致是机房级,这里只浅提一句。一个机房断电断网,另一个机房顶上——云厂商说的多可用区就是这个意思。阶段 4 用不起,也不该用:99.9% 的目标用不到机房级冗余。但要知道它存在——当初选 99.9% 而不是 99.99%,省掉的正是这一层。演进条件前面记过了:交易占比涨到宕机不可承受时,再评估。
"冗余"这个词,也到了说完整的时候。冗余的完整定义是:任何一台挂了,系统都不受影响——多买一台机器只是手段。按这个定义,冗余的程度分三层,而目标决定建到哪一层。实例级:多实例加负载均衡,应用挂了换一台,本节的主体。数据级:数据多存一份,主从加备份,下一节展开。站点级:机房挂了换一个,就是刚浅提的多机房。三层不是都要上:99.9% 的目标,实例级加数据级就够,站点级是 99.99% 的预算。一句话:目标定多高,冗余建多深——可用性预算的拆分思想,在这里变成了冗余的深度表。
冗余的价值可以用数学说硬。一台机器的年故障率假设 1%,两台独立机器同时挂的概率约等于万分之一——多备一份不是浪费,是把故障概率降两个数量级。防线是叠起来的,这个原则在硬件层就是这个形态:每一层冗余都是一道防线。
这里还要立一条和上一章的分界,免得"部署"和"高可用"混为一谈。上一章管的是"怎么把制品放上去"——部署执行,关心上线那一刻;本章管的是"放上去之后怎么不挂"——运行保障,关心上线之后的每一天。两章合起来才是一句完整的话:跑起来,挂不了。再加下一章的"看得见",运行期的拼图才齐。
冗余方案里最容易被忽略的,是救火的人也需要救火:负载均衡自己也是单点。LB 挂了,多实例全瞎。所以 LB 也要冗余——两个 LB 加故障切换;DNS 层也要冗余——一个域名解析到多个 IP,浏览器会自动换。冗余是一层层叠上去的,多级缓存、纵深防御,同一个"叠"字。
最后一条防线的前置条件,是收尾也是伏笔:无状态。冗余的前提是"哪台机器都行"——为什么应用层可以随便冗余?因为会话早已挪进 Redis,应用变成了无状态:任何一台机器都能服务任何请求,挂了换一台,用户无感。也正因为无状态,会话亲和那个"省事"的方案可以正式退居备用——粘性的收益没了,可替换的收益是实打实的。记住这个因果关系:无状态是高可用的地基。应用层能随便冗余,全靠地基打得早;而有状态的组件——数据库——最难冗余,下一节的主角就是它。
24.3 数据层的冗余:主从与备份
第一道防线还有另一半:数据层。数据不能丢,数据库不能单点。应用层冗余了,数据库还是单点——它挂了,前面所有的冗余都白搭。
这一节和全书最早那张总架构图也有个对应关系,值得点破。总架构图画了系统的组成:负载均衡、应用、数据库、缓存。本章可以看作那张图的故障演练版:每一块都可能挂,每一块挂了都有对应的机制。LB 挂了有冗余 LB,应用挂了有多实例,数据库挂了有主从,缓存挂了能重建加降级,流量超了有限流。所以"看懂架构图"的完整含义,不是记住有哪几块,而是知道每一块挂了会怎样——架构图不是画着好看的,是故障时的作战地图。
数据冗余的主角是主从复制:一台主库负责写,一台或多台从库负责读,主库的每次写入异步复制到从库,读请求打到从库,写请求打到主库——这就是读写分离。它解决两个问题。一是读压力分摊:社区产品的流量大头是读,缓存放过热点之后,剩下的读打到从库。二是主库挂了有备:主库故障,从库提升为主库,也就是故障转移——恢复时间从"重新部署一台数据库"变成"切换一下",分钟级。
从库和之前两个读优化工具是什么关系?索引让查询变快,缓存挡住热点,从库分摊总量——三个工具,同一个目标:读扛得住。但各管一段,不可互相替代:缓存管热点,索引管查询,从库管总量,三层读防线缺一层,都会在某一类流量下暴露。
主从解决了"读挂",但它不是"数据丢了"的保险——备份才是。手滑删库——DELETE 忘加 WHERE,勒索软件,机房火灾——这些场景里主从会一起没:从库跟着主库删,火烧掉的是整个机房。所以备份是独立的防线:每天全量备份,加定期恢复演练。而"备份能不能用,只有恢复过才知道"——演练思想的数据版。
主从和备份之外,还有一个思想值得单独表彰:切换。发布方式那章说过,发布从"替换"变成"切换"——流量切到新版本,快且可逆。故障转移也是同一个动词:流量切到新主库。发布切换的是版本,本章切换的是机器,用户视角都是"没感觉"。可以说,切换是部署与高可用共同的核心动词——无感知发布,无感知故障转移。
主从有一个必须面对的代价:复制是异步的。写完主库就返回,从库慢慢复制——延迟窗口内,从库的数据是旧的。这个延迟引出两条规矩。第一条,"数据从哪读"由一致性要求决定:浏览内容,晚几秒无妨;下单后立刻查订单,不能走从库——交易的强一致要求在这里收账。"读自己的写"要路由回主库,这是读写分离最常见的坑:写完读不到。第二条,读写分离的边界由此清楚:它解决了"读挂",没解决"写挂"。写还是压在主库这一个点上,写挂了照样靠切换。所以主从的完整意义是"写的备用",外加读的分摊。
切换也不是免费的,它的代价要认账。一是切换有延迟:主库宕机那一刻,最后几秒的写入可能还没复制完——切换会丢一点数据,至少要有"丢多少"的预案。二是切换要演练,而且没练过的切换,事故时没人敢按。演练要练到什么程度?练到闭眼能切:值班的人被叫醒,又困又慌,30 秒内完成切换——事故时的动作,必须等于演练时的动作。敢部署的前提是能回滚,敢切换的前提就是练过切换;演练,是"切换承诺"的测试。
备份同理,它也要演练,而且有两个著名的坑。第一个坑,只备不验:备份天天做,但从没恢复过,事故那天才发现备份是坏的——所以要把备份恢复到一台新机器上,看能不能起来、数据全不全。第二个坑,备了不回:备份存在同一台机器、同一个机房,机器没了,备份也没了——备份要异地异机,阶段 4 至少做到异机:另一台机器存一份。约束是机制在才算数,备份这条约束,靠恢复演练证明。
最后把"数据不能丢"这四个字说完整。它有三层答案,各防一种不同的丢。事务防进程崩溃——提交了的写,进程死了也不丢。备份防磁盘坏和手滑——机器和人都靠不住时,还有一份在。冗余和主从防数据库挂——库没了,从库顶上,服务不断。三层防的是三种"丢",缺一层都不完整。数据安全不是某个机制,是叠出来的。
24.4 限流:把流量闸住
第二道防线,一句话:别冲垮。它的直接教训来自雪崩事故:数据库不是被正常流量压垮的,是被超出来的流量加重试压垮的——所以要在入口把流量的闸住。限流做的事,就是这个闸:单位时间内最多放进来多少请求,超出的拒绝或排队。事故里如果数据库前面立着这道闸,连接池就不会被打满。
限流不是本章凭空发明——缓存雪崩的对策里就埋过它:"数据库前面加一道闸"。两种雪崩,同一个对策方向:在数据库前面把流量闸住。缓存那一章的雪崩,是同一时刻大量 key 过期;本章的雪崩,是流量超量加重试放大。对策也互补:TTL 随机化让请求错开,限流让请求总量受控——两道闸一起,数据库才安全。
闸要立在两层。入口限流在负载均衡或网关,所有流量先过闸,闸住总量——防雪崩靠的就是它。应用内限流在中间件管道里,按 IP、按接口细分:单个 IP 每秒最多 N 个请求,防爬虫防单点刷;整个服务的每秒上限,不管谁来的,总量闸死。粒度分两档,用途各不相同,两层都要。
把闸放进完整的防线序列看,位置就更清楚了:CDN 挡掉静态层的请求,这是第一道墙;入口限流闸住总量,这是服务端的第一道闸;应用内限流细分;然后才轮到鉴权中间件、业务逻辑。请求每过一道闸,系统就多一分保护——纵深防御的思想,在流量侧就是这个形态:层层设卡。
闸怎么判断超没超?三个算法,名词级别。固定窗口:每秒一个计数器,到点清零——简单,但窗口边界可能放进两倍的量。滑动窗口:把窗口切成小段滚动统计——更准。令牌桶:桶里定时加令牌,请求消耗令牌——能容忍突发,同时守住总量上限。阶段 4 选令牌桶,理由很现实:社区产品的常态就是热点突发,令牌桶允许短时冲一下,又拦住持续洪峰。
阈值定多少?不是拍脑袋:正常峰值乘以余量系数。阶段 4 的数字:正常峰值 800 QPS,限流 2000 QPS,2.5 倍余量。而阈值真正的依据来自压测——压测会告诉你系统真实的拐点,拐点才是限流阈值该站的位置。这个余量还要给误伤留账:系数留不够,正常用户的高峰期也会被拦在门外。
既然阈值是活的,它就要被管理——这正是配置管理的领地。限流阈值属于行为类配置:不发布也能改,流量变了调阈值,不用发版。但"能改"要配"能看":阈值的每次改动要记录、要走评审——限流阈值是保护机制的强度,乱改等于保护失效。配置管理和高可用在这里相遇:保护机制的参数,也要按代码的轨道被管理。
闸还有一个安全侧的用途。攻击者打系统的主要手段就是大量请求——爬虫、撞库、刷接口——限流是对这类攻击的第一道防线,不管谁来的,总量闸住。而且闸门可以按身份分级:未登录的限得更狠,匿名流量更可疑;登录的放宽,那是正常用户。最小权限的原则,在流量侧就是这个形态:按身份分级设卡。
闸的运行情况也要看得见:限流拦了多少请求,要能从日志和指标里数出来。拦不到——阈值太高,形同虚设;拦太多——阈值太低,误伤正常用户。两种失灵都要在数据里现形。保护机制的运行数据,和机制本身合起来才是闭环——观测是下一章的主角。
超出的请求怎么办?两种处置,取舍分明。拒绝:返回 429,告诉客户端"太忙了",请退避重试——这是保护系统。排队:在闸口等一会儿,等不到再放弃——这是尽力而为。阶段 4 选拒绝:简单,保护彻底;排队还要管理队列本身,那是消息队列的领地。而 429 这个响应也要讲规矩:错误信息要友好——"服务繁忙,请稍后再试",附上 Retry-After 告诉客户端等多久;客户端也要尊重 429,退避重试,不是死磕。限流说到底是系统和用户之间的一个约定:你让我喘口气,我让你别断线——大家都错开,大家都好。
代价最后认账:限流会误伤。它的本质是牺牲一部分请求,保住整个系统——宁可拒绝,不可拖死。余量系数就是给误伤上的保险,压测就是给系数找的依据。
24.5 熔断与降级:止损
第三道防线,一句话:别拖垮全站。雪崩事故里最致命的一环是它的传播:数据库慢了,应用在等;应用等了,请求堆积;请求堆积,应用也挂了——故障像多米诺骨牌一样传播。熔断与降级,就是切断传播的机制。
切断之前,先要有"等不到就放弃"的能力,这能力叫超时。网络那章认识过超时——握手失败的一种场景;本章把它升级为服务端的第一条原则:每一个依赖调用都要有超时,数据库、缓存、第三方接口,无一例外。为什么是第一原则?因为没有超时的调用,等于线程被无限期占用——一个依赖不响应,应用的线程池就被它慢慢吃光,应用跟着挂。超时是熔断的前置条件:请求永远在等,就谈不上"快速失败"。
超时设多少?和限流阈值一个算法:正常耗时乘以余量。正常 100 毫秒的数据库查询,超时设 1 秒,留十倍余量。太短,正常请求被误杀;太长,故障时照样拖垮。所以超时也要压测校准:压测给出真实的耗时分布,P95 再乘个系数,就是超时值——先测量,再定值,预算思想的老规矩。
但超时只救了这一次调用,救不了"这个依赖已经挂了"这个判断——依赖挂了,每个请求还要等到超时才放弃,系统照样被拖慢。熔断补上这一环:检测到依赖连续失败——比如错误率过半——直接断开,后续请求不再调用它,立即返回失败。它的工作方式是一个三态状态机:关闭,正常,请求照走;打开,故障,请求直接失败,不调依赖——让依赖有时间恢复;半开,试探,放少量请求过去试试,恢复了就回到关闭,没恢复继续打开。每个状态有明确的转移条件,这个状态机从订单领域走到系统健康领域,是同一个思想的又一次迁移。
还要澄清一句:熔断保护的是调用方,不是被调方。数据库挂了,熔断让应用不再往里冲,少添乱;同时数据库也有自己的自保——连接池打满后拒绝新连接。两侧都有保护,数据库才不至于被打死。雪崩事故里缺的正是这组双向保护。
熔断管"不调了",降级管"不调了之后给用户什么"。兜底响应按场景分:热点内容挂了,给缓存里的旧版本——缓存存着上一份成功的数据,依赖挂了缓存还在;下单挂了,给友好的错误页——"支付通道拥挤,请稍后再试",比 500 好一万倍;非核心功能挂了,直接隐藏——评论暂时不显示。一份缓存在这里露出第二个用途:平时管快,现在管兜底。所以缓存的 TTL 设计要想到降级场景:降级时希望缓存还在,TTL 就不能太短——六十秒的旧数据,总比没有好。
降级清单里藏着熔断和重试的关系。重试是交易的防线,幂等键让它安全;但在故障期,重试是放大器——依赖已经挂了,客户端和网关还在自动重试,等于更多请求涌向已挂的依赖。熔断的快速失败就是重试的刹车:熔断打开期间,重试立即失败,不再制造新流量。所以重试要立三条规矩:有上限,最多 N 次;有退避,等一会儿再试;认熔断,熔断打开就停手。一句话:重试救单个请求,熔断救整个系统。
限流和降级都是闸,方向不同。限流挡进来的流量——"你别进来太多",是入口;降级处理依赖的失败——"你拿不到就算了,我给你别的",是出口。一个保护自己,一个服务用户;两个方向,一个目标:让系统在异常下仍能部分工作。先保什么,由业务定,而且要提前定:交易最不能降——钱的信任;内容浏览其次——降级给旧数据;评论、搜索可以降——功能暂时隐藏。"事故时先降哪个"不该是现场拍脑袋,是早就排好的清单。按重要性排序、出事按序执行——安全投入按风险排序、配置变更按风险分级,同一个逻辑的第三次应用。
到这里,雪崩的完整机制可以复盘了:流量超限,限流没拦住;依赖变慢;超时开始堆积;重试放大——这是最狠的一环,失败后的自动重试全是新流量;全站拖垮。限流、超时、熔断、降级,每一道都是链条上的一刀,砍在传播的不同位置。
三条防线立完,回看本书的"防线"家族,它们同源。交易那章的防线——状态机、事务、幂等——防出错;安全那章的防线——纵深防御——防攻击;本章的防线——冗余、限流、熔断——防挂掉。三个方向,同一个思想:把不可控的坏情况,变成可控的机制。错误是环境的必然,挂也是环境的必然,机制是应对必然的唯一方式。
最后收个尾,一句哲学。第三道防线的总结是四个字:坏影响不扩散。一台机器挂了,冗余兜住;一个依赖挂了,熔断降级兜住;一波流量超了,限流兜住——高可用的全部机制,都是把故障的爆炸半径控制住。交易正确性管钱不能错,本章管挂不能扩散,各司其职。
24.6 容量与压测:挂之前先知道
三道防线讲完,还有一个问题要回答:**看起来够用,为什么还是挂了?**雪崩事故里,数据库平时负载不高——但"平时"不等于"峰值"。容量管理,就是高可用的预算。
先看清它和前面"预算"家族的关系。性能预算把 P95 延迟拆到每一层,可用性预算把几个 9 拆到每个组件,本章的容量规划管"够不够"——三个预算,一套思想:目标拆到部件,谁超标一目了然。快不快、挂不挂、够不够,各管一域,算法相同。
容量规划做什么?一句话:容量等于峰值流量乘以余量系数。关键是峰值从哪来——两个来源。从历史拿趋势:监控曲线按趋势外推。从业务拿预期:运营预告下周末大促,市场说要投广告,工程师就要提前算——业务方知道的峰值比监控曲线早,因为监控看到的是过去,业务看到的是未来。所以容量不是工程师一个人的事,它有一条协作流程:业务预告,工程师评估,峰值乘余量,不够就提前扩,扩完压测验证。业务预期逼出容量决策——"业务逼出技术"在运行期的形态,就是这个流程。
余量系数给多少?压测定。压测做的事:模拟流量逐渐加压,找到系统的拐点。阶段 4 压出来的数字:单机 1500 QPS 时,数据库连接先到顶——限流阈值 2000 QPS 的依据就是这么来的,压测数据说话,不拍脑袋。压测在测试体系里的位置也要摆准:功能测试回答"对不对",压测回答"压力来时还对不对"——功能测试验证逻辑,压测验证承载,两类测试,一个都不能少。而压测的本质是承诺的测试:99.9% 是定下的承诺,压测证明系统离这个承诺有多远。
压测怎么测?三种类型,各问一个问题。容量测试压到拐点——极限在哪。稳定性测试持续加压——长时间跑会不会漏,内存泄漏、连接泄漏这类慢病,只有它查得出。突发测试模拟峰值突增——雪崩事故的形态。三种里阶段 4 最该测突发:社区产品的常态就是平时闲、突然爆,不测突发等于没测主战场。
压测的环境也有讲究。不能在生产压——把生产压挂了,事故乘二。压测环境要和生产等规模:同镜像、同配置,压出来的结果才可信。容器在这里又一次兑现价值:环境一致,压测结果才可信——环境不一致,结果不可信,这是复现思想的老规矩。压测的结果还要接到监控上:拐点数据成为监控的基准,生产指标逼近拐点就告警——"离拐点还有多远",就是容量告警的标尺。压测是一次性的测量,监控是持续的测量;先度量再优化,压测把度量做一次,监控把度量做一辈子。
容量不够了怎么办?两条路。垂直扩容:换更大的机器——简单,但有上限,单机总有天花板。水平扩容:加机器——前提早就建好了:应用无状态,负载均衡在位,新实例上线,流量自动分过去。扩容不是新动作,是前面所有章节的顺水推舟。阶段 4 的分工:应用层水平扩,数据库先垂直——主从已经把读分摊了,写的垂直扩容是过渡方案;写的终极解法是拆服务、分库,那是架构演进的领地。扩容的尽头是架构问题,预告到此。
代价最后认账。压测要时间,容量要机器——备着不用的机器也是钱。这是"概率管理"的另一半:容量是花钱买概率。多备一台机器,就是把"扛不住"的概率降一点;买多少,回到那个已定下的目标——99.9%,一年 8.76 小时,就是这笔钱的花法。
24.7 故障演练与 on-call:出事时有人会处理
最后一道保障不是技术,是人和流程。前面定了 99.9% 的目标——一年最多 8.76 小时宕机。这 8.76 小时怎么花?不是花完为止,而是取决于一件事:出事后多快能恢复。恢复速度,决定了目标能不能达成。本节讲让恢复变快的三样东西:演练、值班、复盘。
第一样是演练。发布方式那章演练过回滚——本章演练的是故障:主动制造故障,验证系统扛得住。练什么?每道防线都要练一遍。冗余:拔掉一台应用,负载均衡摘除了吗?数据:拔掉主库,切换了吗?限流:压到阈值以上,429 了吗?熔断:弄挂一个依赖,熔断打开了吗?练过的防线才是防线;没练过的防线只是理论上存在——约束是机制在才算数,高可用版本就是练过才算数。演练还要练到人:值班的人被叫醒,又困又慌,事故时的每一步都必须是练过的动作,否则不敢按、按不对。
演练还有进阶形态,知道存在即可。一种是红队演练:专门有人扮演搞破坏的,模拟攻击、模拟故障——自己弄挂验证"机制在不在",红队弄挂验证"机制扛不扛得住意外"。另一种是混沌工程:在生产环境随机注入故障,Netflix 的 Chaos Monkey 是鼻祖——演练测试环境会手下留情,生产环境才真实。这两种阶段 4 都不上:红队是安全团队的编制,8 人团队没有;混沌工程是演练的极致形态,玩得起的门槛是专职 SRE。演进条件里记着:规模到了再说。但思想要先记住——高可用做到极致,就是随时准备着它挂。
第二样是值班。阶段 4 的关键事件里,这是首次引入 on-call。之前的规矩是"谁在电脑前谁处理"——听着随意,实际是没有规矩。引入之后:每周一人值班,出事了值班的人被叫醒。处理的第一步永远是先恢复、再查因——出事了先按回滚按钮,让用户先能用,真相慢慢找。值班的人手里还要有一张处理清单,出事了照着走,不靠临场发挥。清单四步:先恢复——回滚、重启、切流量,先让用户能用;再判断——影响面多大,全站还是单接口,对应哪道防线;升级——30 分钟内处理不了,叫醒负责人,值班不是一个人扛,是当第一响应人;记录——时间线记下来,复盘要用。清单的意义:事故时人的判断会失灵——困、慌、信息乱——清单是兜底的判断。开发期的协作轨道管代码怎么改,运行期的轨道管故障怎么处理;值班清单,就是那条轨道的故障版。
值班还要配上两样东西。一样是升级路径和交接文档:处理不了找谁、系统最近改了什么——值班的人不能赤手空拳。一样是代价认账:值班很累,半夜被叫醒是真金白银的消耗。但为什么这笔投入要花?因为值班是可用性目标的最后一环:机制再好,没人处理,99.9% 就是纸上的数字。目标、机制、人——到这里闭环。
还要认一个短板,顺便给下一章递个话。雪崩事故时最难受的不是系统挂,是不知道挂在哪:应用还是数据库?流量问题还是代码问题?没有监控,排障靠猜——"谁在电脑前谁处理"的老规矩,没有监控就变成了"谁在电脑前谁猜"。事故里"告警半小时才响"的教训也在这。本章的机制解决"挂了怎么办","挂了怎么知道"是下一章的事——没有告警,值班等于等用户投诉;告警太多,值班等于狼来了。两章合起来,才是完整的事故应对。
第三样是复盘。每次事故之后写复盘:发生了什么,为什么,哪道防线漏了,补什么机制。复盘的思想基础还是那句:事故是漏掉的承诺。回看本章,每一节其实都是上一场事故的复盘结论——限流来自连接池被打满,熔断来自故障传播,压测来自"看起来够用",值班来自"有值班和会处理是两件事"。事故是演进之母:本章的三道防线,加上超时与人和流程,全是雪崩事故这一个母亲生的。
24.8 本章对应表
| 业务诉求 | 技术选择 | 为什么 | 代价/取舍 |
|---|---|---|---|
| "不能挂"要变成硬指标 | 可用性目标(几个 9) | 把承诺翻译成可度量的数字(99.9%=年宕机 8.76 小时) | 目标≠现实,靠后面所有机制兑现 |
| 一台挂了全站挂 | 冗余:多实例 + LB(图 24-1) | 故障概率的乘法(1%×1%=万分之一) | 机器成本翻倍;LB 自己也是单点 |
| 数据库是单点 | 主从 + 备份 + 故障转移 | 有状态最难冗余;数据三层防护 | 主从延迟、切换要演练、备份要验证 |
| 流量突增打垮系统 | 限流(图 24-2:闸门) | 宁可拒绝不可拖死(429) | 误伤正常用户;阈值要压测校准 |
| 依赖故障拖垮全站 | 熔断 + 降级(图 24-2:开关/兜底) | 超时救单次,熔断救全局;坏影响不扩散 | 降级=体验下降;功能取舍 |
| "看起来够用"还是挂 | 容量规划 + 压测 | 拐点要压测才知道(单机 1500 QPS) | 压测投入;备机是钱 |
| 出事了没人处理 | 故障演练 + on-call | 恢复速度决定 99.9% 能否达成;先恢复再查因 | 值班成本;演练时间 |
本章小结
- 高可用是概率管理:99.9% 不是"永不挂",是"一年挂 8.76 小时以内"——目标、机制、人,三环闭环;
- 三道防线:冗余(挂了还有)、限流(别冲垮)、熔断降级(别拖垮全站)——每一道都是一类事故的答案;
- 数据是最难冗余的一环:主从+备份+故障转移,三层防护(事务/备份/冗余)叠出"数据不能丢";
- 事故是演进之母:本章每一节,都是上一场事故的复盘结论。
下一章
三道防线建好了,on-call 也值班了。但第一次值班,你就遇到最难的问题:用户说"打不开",可系统监控一切正常——问题到底出在哪?——你只能看见"系统活着",看不见"一次请求内部发生了什么"。
第 25 章,可观测性:日志、监控、链路追踪——挂了或慢了,怎么知道是哪里。