高可用架构
开篇:什么是"高可用"?
你有没有遇到过这样的场景——打开一个 App 发现页面白屏,刷新几次也没用,心里默念"又挂了"?
作为用户,你只是刷新了几次然后换了个 App。但作为开发者,这几分钟的不可用可能意味着大量的订单损失、用户投诉,甚至公关危机。
高可用 (High Availability) 的目标就是:让系统在各种异常情况下,依然能够正常对外提供服务。 不是不出问题——世上没有永远不出问题的系统——而是出了问题能扛住、能自愈、能快速恢复。
就像人不可能永远不生病,但一个免疫力强的人,生了小病能自己扛过去,生了大病能快速就医恢复。高可用架构做的就是提升系统的"免疫力"。
SLA:用"几个9"衡量可用性
SLA(Service Level Agreement,服务等级协议)是衡量可用性的行业标准。它是供应商和客户之间达成的正式协议,规定了服务应该达到的水平。
最核心的指标是服务可用性百分比——用"几个9"来表示:
| 级别 | 可用性 | 年停机时间 | 典型场景 |
|---|---|---|---|
| 2个9 | 99% | 3天15小时 | 内部工具 |
| 3个9 | 99.9% | 8小时45分 | 一般互联网应用 |
| 4个9 | 99.99% | 52分钟 | 核心业务系统 |
| 5个9 | 99.999% | 5分钟 | 金融支付、电信核心 |
计算公式很简单:年停机时间 = 365 × 24 × 60 × (1 - 可用性)。
从 3 个 9 到 4 个 9 看起来只多了一个 9,但年停机时间从 8 小时缩减到 52 分钟——架构复杂度和成本都上了一个数量级。从 4 个 9 到 5 个 9,又是一个质的飞跃——全年只允许停机 5 分钟,几乎不能有任何人工干预的时间,一切都要靠自动化。
除了可用性百分比,一份完整的 SLA 通常还包含以下指标:
- 响应时间:服务响应请求的时间,如 P99 < 100ms。
- 吞吐量:系统每秒能处理的请求量(QPS/TPS)。
- 故障恢复时间 (MTTR):故障发生后多长时间能恢复正常。4 个 9 意味着每次故障的恢复时间必须非常短。
- 数据可靠性:数据不丢失、不损坏。通过主从同步、多副本、定期备份等技术保障。
- 服务支持:技术支持的响应速度和覆盖时间(如 7x24 小时)。
SLA 不是越高越好。3 个 9 和 5 个 9 的成本差距可能是 10 倍甚至 100 倍。要根据业务实际需要来定——一个内部管理系统,3 个 9 绰绰有余;核心支付系统,4 个 9 是起步。
一、高可用设计原则
1.1 冗余——消除单点故障
"鸡蛋不要放在一个篮子里"——这是高可用最朴素的原则。
系统中的任何一个组件都可能出问题:服务器硬盘坏了、网络交换机挂了、进程 OOM 了、机房停电了。如果整个系统只有一台机器、一个数据库实例、一个缓存节点,其中任何一个挂了整个系统就瘫痪了——这就是单点故障 (Single Point of Failure, SPOF)。
解决方案就是冗余——关键组件都部署多份:
- 应用服务器:部署多台,前面挂负载均衡器。一台挂了,流量自动切到其他机器。
- 数据库:做主从复制。主库挂了,从库自动提升为主库(借助 MHA、Orchestrator 等工具)。
- 缓存:Redis 部署集群模式(Cluster)。每个主实例挂一个从实例,主挂了从自动接管。
- 消息队列:多 Broker、多副本。一个 Broker 挂了,其他 Broker 继续服务,消息不丢失。
一个实际的 Redis Cluster 案例:5 主 5 从,10 台机器。任何一个主实例宕机,对应的从实例会自动故障迁移变成主实例,继续提供读写服务。整个过程对业务透明,用户无感知。
冗余的本质是用成本换可靠性——多一份备份就多一份保障,但也多一份开销。
1.2 故障隔离——控制爆炸半径
冗余解决了"一个组件挂了"的问题。但还有另一种更隐蔽的情况:一个组件没挂,只是变慢了。
想象这个场景:你的订单服务调用支付服务,支付服务因为数据库慢查询,响应时间从 50ms 飙到 5 秒。订单服务的线程在等待支付服务返回,一个线程等 5 秒。请求源源不断地涌入,越来越多的线程被阻塞等待。最终订单服务的线程池耗尽了——连和支付无关的查询订单功能也不能用了。
更可怕的是,订单服务变慢后,调用订单服务的网关也开始变慢,然后所有依赖网关的服务都受影响——雪崩效应就这样发生了。一个支付服务的慢查询,最终导致整个系统不可用。
故障隔离的核心思想是:一个地方出问题,不能把整个系统带崩。 就像轮船的水密舱设计——一个舱进水了,关上水密门,其他舱不受影响,船不会沉。
隔离的维度有很多:
- 线程池隔离:调用支付服务的线程池和调用库存服务的线程池分开。支付服务的线程池满了,不影响库存服务的调用。
- 信号量隔离:用计数器限制同时对某个服务的并发调用数。
- 集群隔离:核心业务和非核心业务部署在不同的集群。
- 机房隔离:不同的机房承载不同的用户群体。
- 数据隔离:核心数据和非核心数据放在不同的数据库实例。
1.3 超时与重试——快速失败
每个外部调用(RPC、HTTP、数据库查询)都必须设置超时时间。没有超时的调用就是一颗定时炸弹——对方如果不响应,你的线程就永远等下去,资源永远被占用。
超时后可以选择重试。但重试不是无脑重试,要注意两个关键点:
- 重试次数要有上限:一般 2-3 次就够了。无限重试 = 自己对自己发动 DDoS 攻击——对方已经压力很大了,你还在疯狂重试。
- 重试的接口必须是幂等的:否则重试可能导致重复操作(比如重复扣款、重复下单)。
一个实用的重试策略是指数退避 (Exponential Backoff):第一次失败等 1 秒重试,第二次等 2 秒,第三次等 4 秒。避免在对方已经过载的时候还密集重试加重负担。
二、常见高可用手段
2.1 集群与负载均衡
集群是高可用的基础——把同一个服务部署多份,前面用负载均衡器分发请求。任何一台机器挂了,负载均衡器自动把流量切到其他健康的机器。
关键机制是健康检查 (Health Check):负载均衡器定期对后端机器发起探测(发心跳、检查 HTTP 端口),发现不健康的机器立即从分发列表中摘除。等机器恢复后再重新加入。
2.2 限流、降级、熔断
这是高可用的"三板斧",也是面试最高频的考点。
限流 (Rate Limiting): 系统只处理它能承受的请求量,超出部分直接拒绝或排队。就像地铁早高峰限流——不是不让你坐,是这趟满了你等下一趟。
限流可以从两个层面实施:
- 请求层面:固定窗口、滑动窗口、漏桶算法、令牌桶算法。
- 资源层面:控制数据库连接池大小、控制线程池大小、控制请求队列长度。
降级 (Degradation): 当系统压力过大或部分功能不可用时,主动关闭非核心功能,把资源集中到核心业务上。丢车保帅,保住核心链路。
生活中的例子:暴雪天气,快递公司暂停非紧急件的派送,集中力量保障生鲜和医药类快递——这就是降级。
降级的常见手段:
- 停止读数据库,直接走缓存(数据有延迟但服务不挂)。
- 关闭"猜你喜欢"等推荐功能,返回静态兜底内容。
- 精确计算结果转为近似结果("约xx人关注"代替精确数字)。
- 同步调用改异步(下单后告诉用户"处理中",后台慢慢算)。
- 加验证码、滑块题——用少量的用户体验牺牲换取系统喘息时间。
熔断 (Circuit Breaker): 当调用某个下游服务的失败率或响应时间超过阈值时,直接断开对它的调用,快速返回失败。就像电路中的保险丝——电流过大时自动断开,保护整个电路不被烧毁。
熔断器通常有三个状态:
2.3 灾备与多活
备份的三种温度:
| 类型 | 特点 | 恢复速度 | 成本 | 举例 |
|---|---|---|---|---|
| 冷备 | 关闭系统后备份到离线介质 | 慢(小时级) | 低 | 深夜停机备份整个数据库 |
| 暖备 | 定期同步,备份数据有一定延迟 | 中等(分钟级) | 中 | 特定功能关闭时备份 |
| 热备 | 系统运行中实时同步 | 快(秒级) | 高 | MySQL 主从同步 |
MySQL 的主从复制就是热备的典型实现——主库实时将 binlog 推送到从库,从库实时重放。主库挂了,从库可以在秒级别内接管。
同城容灾: 在同一城市的不同机房部署完整的系统副本。机房之间网络延迟低(< 1ms),数据同步容易实现。一个机房挂了,流量切到另一个机房。
异地多活: 高可用的终极形态。在多个地理位置部署独立的数据中心,每个中心都能独立处理请求。
异地多活的核心挑战是数据一致性——跨地域的网络延迟(几十到几百ms)使得强一致性几乎不可能实现。通常采用最终一致性方案,接受短暂的数据不一致。
异地多活的好处:低延迟(就近处理)、天然容灾(一个中心挂了流量自动切到其他中心)、高扩展性(加一个数据中心就能扩容)。代价是:改造成本极高、数据一致性复杂、运维难度大。
三、压测与故障演练
你的系统设计得再好,不经过验证都是纸上谈兵。压测和故障演练就是"考试前的模拟考"。
3.1 压测的基本流程
压测就是模拟用户请求,给系统施加压力,观察系统在不同负载下的表现。
步骤一:明确目标。 压哪个接口?什么样的数据?要验证 300 QPS 能不能扛住,还是要探底系统极限?
步骤二:准备环境和脚本。 环境配置要和线上一致。压测数据要尽量真实——不同用户 ID、不同商品 ID,不能全是同一条数据(那样缓存命中率 100%,测出来的结果没有参考价值)。
步骤三:逐步施压。 从低并发开始,逐步加量,观察各项指标的变化曲线。看看系统在什么 QPS 下开始出现 RT 飙升、错误率上升、CPU 打满。
步骤四:监控关键指标。 施压只是手段,观察系统表现才是目的。核心关注:QPS、RT(P50/P99)、错误率、CPU 利用率、Load、内存、GC 次数和时长、网络 IO、线程数。
步骤五:分析瓶颈并优化。 根据监控数据定位瓶颈——是 CPU 打满了?数据库慢查询了?线程池满了?GC 过于频繁?针对性优化后再压测验证。
常用工具: JMeter(功能最全、社区最大)、Apache Bench(轻量简单、适合快速验证)。
3.2 常见压测误区
"单机 300 QPS,10 台就能扛 3000 QPS"——大错特错。
单纯用机器数量线性推算 QPS 是不成立的。应用层可以线性扩展,但系统还有很多共享组件——数据库、Redis、消息队列、外部服务。这些组件并不会因为你加了应用服务器就自动变强。
更何况,10 台机器引入了新的问题:负载均衡是否均匀?网络延迟是否增加了?分布式锁是否成为新瓶颈?
必须通过实际压测来验证,不能靠计算器。
3.3 避免影响线上用户
压测绝对不能影响线上真实用户。关键措施:
- 独立环境:尽量在和线上隔离的环境压测。
- 影子表/影子缓存:在预发布环境压测时,压测数据写入影子表(如
order__shadow),不污染真实数据。 - 低峰期执行:选择深夜或业务低谷期。
- 逐步放量:先小流量验证链路通畅,再逐步加量。
3.4 全链路压测
单链路压测只关注一个服务,容易忽略系统间的资源竞争和外部干扰,得出过于乐观的结论。
全链路压测从网络到 Nginx、到应用服务器、到服务间依赖、到数据库、到缓存、到磁盘——全方位找出系统瓶颈。
它需要两个核心能力:
- 流量染色:在请求中添加特殊标记,让全链路的每个服务都能识别这是压测流量。可以和分布式链路追踪结合实现。
- 数据隔离:识别出压测流量后,数据库写影子表、缓存写影子 Key、日志打到独立文件。
3.5 故障演练
压测验证的是性能水位。故障演练验证的是容灾能力——当系统真的出故障时,你的冗余、熔断、降级方案能不能正常工作?
故障演练就是在可控条件下主动制造故障:随机杀掉一个服务实例、断开数据库连接、注入网络延迟、模拟磁盘写满。观察系统是否能自动恢复,监控告警是否及时触发,降级策略是否正确生效。
Netflix 的 Chaos Monkey 就是这个思路——在生产环境随机杀进程,倒逼团队把系统做得足够健壮。"如果你怕它挂,那就经常让它挂,直到你不怕为止。"
四、常见面试题精选
面试题 1:什么是 SLA?
SLA 是供应商和客户之间约定的服务等级协议,定义了可用性、响应时间、故障恢复时间等量化指标。最常见的是用"几个 9"表示可用性——4 个 9 意味着 99.99%,全年停机不超过 52 分钟。
SLA 不只是一个数字,它是甲乙双方的正式承诺。不达标通常有违约赔偿条款。
面试题 2:如何设计一个高可用架构?
可以从以下维度展开回答:
- 冗余消除单点:集群部署、主从复制、多副本。
- 保护机制:限流、降级、熔断,防止雪崩效应。
- 多活容灾:同城容灾或异地多活,跨机房故障切换。
- 监控告警:全方位实时监控,异常自动触发告警和预案。
- 弹性伸缩:根据负载自动扩容缩容(K8s HPA),应对流量波动。
- 容量规划:定期压测评估水位,提前做好扩容准备。
- 安全保护:加密传输、防火墙、入侵检测,保护数据安全。
这些手段不是全都要上——根据业务需求、技术能力和预算综合考虑。高可用是一个持续优化的过程,不是一锤子买卖。
面试题 3:单机 300 QPS,10 台能扛 3000 QPS 吗?
不能线性推算。原因有三:
- 共享瓶颈:数据库、缓存、MQ 等共享组件不会因为加应用服务器就自动变强。单机压测时数据库只扛 300 QPS,10 台机器时数据库要扛 3000——它能扛住吗?
- 分布式开销:负载均衡不均匀、网络延迟增加、分布式锁竞争等都会降低效率。
- 外部依赖:第三方支付、短信服务、网关等外部服务也有各自的容量上限。
必须通过全链路压测来验证实际系统容量。
五、高可用架构全景图
让我们用一张全景图把所有高可用手段串联起来,看看一个完整的高可用架构长什么样:
每一层都有冗余,每一层都有保护机制,每一层都有监控。 这就是高可用架构的全貌。
在实际落地时,不需要一步到位——先做集群和主从(消除单点),再加限流降级(保护机制),然后完善监控告警(可观测性),最后考虑异地多活(终极容灾)。根据业务发展阶段逐步演进。
高可用检查清单
在做系统高可用设计时,可以用以下清单自查:
小结
高可用不是一个功能特性,而是一种系统性的工程能力。它贯穿架构设计、编码实现、测试验证、运维监控的全过程。
记住这几个核心原则:
- 消除单点:任何关键组件都要有冗余备份。没有冗余就没有高可用。
- 控制爆炸半径:通过隔离、限流、熔断,把故障影响控制在最小范围内。一个服务慢了不能拖垮整个系统。
- 快速恢复:故障不可避免,关键是能多快恢复。自动化程度越高,恢复越快,人工干预越少越好。
- 验证,验证,再验证:压测验证性能水位,故障演练验证容灾方案。没经过实际验证的高可用方案都只是"看起来高可用"。
- 成本意识:高可用是有成本的。3 个 9 和 5 个 9 的成本差距是数量级的。根据业务需要选择合适的级别——过度投入是浪费,投入不足是隐患。找到那个平衡点。