稳定性¶
稳定性治理的核心目标是:在部分组件故障时,系统整体仍能提供可接受的服务。本质上是通过一系列防御性设计,将故障的影响范围控制在最小,并尽快恢复。
服务降级¶
当系统负载过高或下游依赖不可用时,主动放弃部分非核心功能,保障核心链路的可用性。
降级策略¶
| 策略 | 说明 | 示例 |
|---|---|---|
| 返回兜底数据 | 使用缓存或默认值代替实时计算结果 | 推荐列表降级为热门榜单 |
| 关闭非核心功能 | 通过开关关闭边缘特性 | 关闭商品评论、个性化推荐 |
| 简化业务流程 | 跳过非关键步骤 | 下单时跳过积分计算,异步补偿 |
| 拒绝低优先级请求 | 按业务优先级分级处理 | VIP 用户正常服务,普通用户排队 |
降级级别¶
L0(正常)→ L1(轻度降级:关闭非核心功能)→ L2(中度降级:返回兜底数据)→ L3(重度降级:仅保留核心写入链路)
降级设计原则
- 降级开关应支持动态下发(通过配置中心),不需要重启服务
- 降级策略需要提前演练,确保兜底逻辑可用
- 降级后必须有恢复机制:手动恢复或自动探测恢复
熔断¶
熔断是对下游服务的保护机制。当下游持续异常时,调用方主动切断请求,防止故障扩散和资源耗尽(如线程池被大量超时请求占满)。
熔断状态机¶
┌──── 失败率 < 阈值 ────┐
│ │
▼ │
Closed ──失败率≥阈值──► Open ──冷却时间到──► Half-Open
▲ │
│ │
└───── 探测请求成功 ─────────────────────────┘
探测请求失败 → 回到 Open
- Closed(关闭) — 正常状态,所有请求放行,持续统计失败率
- Open(打开) — 熔断触发,所有请求直接返回 fallback,不再调用下游
- Half-Open(半开) — 冷却期结束后,放行少量探测请求。成功则恢复为 Closed,失败则回到 Open
熔断参数¶
| 参数 | 说明 | 推荐值 |
|---|---|---|
| 失败率阈值 | 触发熔断的失败比例 | 50%~60% |
| 统计窗口 | 计算失败率的时间窗口 | 10~30 秒 |
| 最小请求数 | 窗口内低于此数量不触发熔断 | 10~20 次 |
| 冷却时间 | Open 状态持续时间 | 5~30 秒 |
| 探测数量 | Half-Open 时放行的请求数 | 1~5 次 |
熔断 vs 降级
- 熔断是对调用方的保护,自动触发,粒度是单个下游接口
- 降级是对整体系统的保护,通常由人工或策略触发,粒度是功能模块
- 两者常配合使用:熔断触发后执行降级逻辑(返回兜底数据)
限流¶
限流是对自身服务的保护机制。通过控制单位时间内的请求数量,防止系统被突发流量压垮。
限流算法对比¶
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 固定窗口 | 在固定时间窗口内计数,超过阈值则拒绝 | 实现最简单 | 存在临界突刺问题(窗口边界处可能瞬间放过 2 倍流量) |
| 滑动窗口 | 将窗口细分为多个小窗口,滑动统计 | 解决临界突刺 | 实现稍复杂,内存占用更高 |
| 漏桶 | 请求以任意速率进入桶,以固定速率流出 | 输出速率恒定,保护下游 | 无法应对合理的突发流量 |
| 令牌桶 | 以固定速率生成令牌,请求需获取令牌才能通过 | 允许一定程度的突发(桶中有积攒的令牌) | 实现相对复杂 |
令牌桶算法¶
固定速率放入令牌
│
▼
请求到达 ──► [获取令牌] ──有令牌──► 放行处理
│
└──无令牌──► 拒绝 / 排队
令牌桶是最常用的限流算法,兼顾了平滑限流和突发容忍:
- 桶容量(burst)决定了允许的最大突发量
- 生成速率(rate)决定了稳态下的最大 QPS
限流维度¶
限流不仅仅是全局 QPS 限制,应按多维度精细化控制:
- 接口级限流 — 按 API 路径限流,核心接口单独配置
- 用户级限流 — 按用户 ID 限流,防止单个用户刷接口
- IP 级限流 — 防爬虫、防 DDoS
- 服务级限流 — 按调用方服务限流,防止某个下游拖垮自己
分布式限流¶
单机限流只能保护单个实例。在微服务场景下,需要分布式限流来控制整个服务集群的总流量:
请求 → API Gateway / Sidecar → Redis(集中式计数 / 令牌桶) → 后端服务
常见方案:
- Redis + Lua — 使用 Lua 脚本保证计数的原子性,适合大多数场景
- Sentinel — 阿里开源的流量治理组件,支持集群限流、热点参数限流
- API Gateway 限流 — 在网关层统一限流(如 Kong、APISIX)
限流后的处理
限流不等于直接拒绝。应根据场景选择:
- 快速失败:返回 429 Too Many Requests,适合客户端可重试的场景
- 排队等待:将请求放入队列,匀速处理,适合削峰场景(如秒杀)
- 降级响应:返回兜底数据,保证用户体验
超时重试¶
超时设置¶
每一次跨服务调用都必须设置超时时间,否则调用方线程 / 协程会被阻塞,导致资源耗尽。
超时设置原则:
- 超时时间应略大于下游的 P99 延迟(如下游 P99 = 200ms,超时设为 500ms)
- 不同操作设置不同超时:读操作超时 < 写操作超时
- 链路超时递减:上游超时 > 下游超时(如 Gateway 3s → Service A 2s → Service B 1s),避免下游超时后上游仍在等待
重试策略¶
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 固定间隔重试 | 每次间隔固定时间重试 | 简单场景 |
| 指数退避 | 每次重试间隔翻倍(如 100ms → 200ms → 400ms) | 避免重试风暴 |
| 指数退避 + 抖动 | 在指数退避基础上加随机偏移 | 避免多个客户端同时重试(推荐) |
重试间隔 = min(base * 2^attempt + random(0, jitter), max_interval)
重试的注意事项¶
重试必须满足的前提
- 接口幂等:重试意味着可能重复调用,下游接口必须支持幂等(通过唯一请求 ID 去重)
- 限制重试次数:一般不超过 3 次,避免重试风暴压垮下游
- 仅重试可恢复错误:网络超时、503 可以重试;400、403、404 不应重试
- 重试预算:整个链路的重试总量应有上限(如 Google SRE 推荐的 retry budget:重试请求不超过总请求的 10%)
负载均衡¶
负载均衡将请求分散到多个服务实例上,避免单点过载。
常见算法¶
| 算法 | 原理 | 特点 |
|---|---|---|
| 轮询(Round Robin) | 按顺序依次分配请求 | 简单均匀,不考虑实例差异 |
| 加权轮询 | 按权重比例分配,高性能实例分配更多请求 | 适合异构集群 |
| 最少连接数 | 将请求分配给当前连接数最少的实例 | 适合长连接、处理时间差异大的场景 |
| 一致性哈希 | 根据请求特征(如用户 ID)哈希到固定实例 | 适合有状态服务、缓存亲和 |
| P2C(两次随机选择) | 随机选 2 个实例,取负载较低的那个 | 兼顾性能与均衡,gRPC 默认策略 |
健康检查¶
负载均衡需要配合健康检查,及时摘除异常实例:
- 主动健康检查 — 负载均衡器定期向实例发送探测请求(HTTP / TCP)
- 被动健康检查 — 根据实际请求的响应状态判断(连续 N 次失败则摘除)
- 优雅下线 — 服务主动通知注册中心下线,等待存量请求处理完毕后再停止
故障隔离¶
故障隔离的原则是:一个组件的故障不应影响其他组件。通过隔离手段将故障限制在最小范围内。
隔离维度¶
| 维度 | 说明 | 示例 |
|---|---|---|
| 线程池隔离 | 为不同的下游调用分配独立线程池 | 支付服务和推荐服务使用不同线程池,推荐服务超时不会耗尽支付服务的线程 |
| 信号量隔离 | 限制并发调用数量,不分配独立线程 | 轻量级隔离,适合本地快速调用 |
| 进程隔离 | 关键服务独立部署,不与其他服务混部 | 核心交易服务独占容器 |
| 机房隔离 | 不同机房独立运行,故障不跨机房扩散 | 单元化架构 |
| 数据隔离 | 不同租户 / 业务使用独立的数据库或表 | 多租户 SaaS 系统 |
舱壁模式(Bulkhead)¶
借鉴船舶的水密舱设计:船体被隔板分为多个独立舱室,即使一个舱室进水,也不会影响其他舱室。
┌─────── 线程池 A(支付) ───────┐
│ 最大 20 线程 | 队列 50 │
请求 ──► 路由分发 ──►├─────── 线程池 B(推荐) ───────┤
│ 最大 10 线程 | 队列 20 │
├─────── 线程池 C(通知) ───────┤
│ 最大 5 线程 | 队列 10 │
└──────────────────────────────┘
隔离粒度选择
- 线程池隔离适合 I/O 密集型调用(RPC、HTTP),代价是额外的线程开销
- 信号量隔离适合计算密集型或本地缓存调用,开销更小
- 核心原则:越重要的依赖,隔离级别越高
灰度发布¶
灰度发布(又称渐进式发布)是指将新版本逐步推送给一小部分用户,验证无误后再全量上线,从而降低发布风险。
发布策略对比¶
| 策略 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 金丝雀发布 | 先将少量流量(如 5%)导向新版本,逐步增加 | 风险可控,可快速回滚 | 需要流量调度能力 |
| 蓝绿发布 | 维护两套完整环境,切流量一次性切换 | 回滚极快(切回旧环境) | 资源成本翻倍 |
| A/B 测试 | 按用户特征分组,对比新旧版本的业务指标 | 数据驱动决策 | 实现复杂,需要数据分析平台 |
| 滚动发布 | 逐批替换旧版本实例 | 不需要额外资源 | 回滚速度慢,新旧版本共存时间长 |
灰度流量路由¶
灰度发布的关键是精确的流量路由,常见的路由维度:
- 按比例 — 5% → 20% → 50% → 100% 逐步放量
- 按用户 ID — 指定测试用户或按 ID 尾号分流
- 按地域 — 先在某个城市灰度
- 按请求头 / Cookie — 内部员工携带特定标识,优先使用新版本
灰度验证指标¶
灰度期间必须持续监控以下指标,确保新版本表现正常:
- 错误率:新版本的错误率不应高于旧版本
- 延迟:P50 / P99 延迟不应有明显劣化
- 业务指标:转化率、下单量等核心业务数据
- 资源消耗:CPU、内存是否异常上涨
灰度发布注意事项
- 灰度期间必须有自动回滚机制:关键指标超过阈值时自动切回旧版本
- 数据库 Schema 变更需要前向兼容:新旧版本代码必须都能正常工作
- 灰度流量的日志和链路追踪应打上版本标签,便于对比分析
异地多活/容灾¶
异地多活是稳定性的终极保障,确保单个数据中心故障时业务不中断。
容灾架构模式¶
| 模式 | 说明 | RPO/RTO | 成本 |
|---|---|---|---|
| 冷备 | 备用机房平时不提供服务,灾难时手动切换 | RPO 高(数据丢失多),RTO 高(恢复慢) | 低 |
| 温备 | 备用机房有预备资源,但需时间启动 | RPO 中,RTO 中 | 中 |
| 热备(主从) | 一个机房承担读写,另一个机房实时同步作为备份 | RPO 低,RTO 中 | 较高 |
| 双活 / 多活 | 多个机房同时提供读写服务 | RPO ≈ 0,RTO ≈ 0 | 高 |
RPO(Recovery Point Objective):可容忍的最大数据丢失量
RTO(Recovery Time Objective):从故障到恢复服务的最大时间
异地多活的核心挑战¶
数据一致性是多活架构最难解决的问题。跨数据中心的数据同步不可避免地面临延迟,需要在一致性和可用性之间权衡:
- 单元化(Set-based) — 按用户维度(如 UID 尾号)将数据和流量划分到不同单元,每个单元内部自治。跨单元调用走专线,单元故障时将其流量切换到其他单元
- 数据同步方案 — 基于 Binlog 的异步复制(如 Canal + Kafka),或使用数据库原生的多主复制
- 冲突处理 — 多个机房同时写入同一条数据时的冲突解决策略(Last Write Wins、业务合并等)
容灾切换¶
正常状态 故障切换
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ 机房 A │◄──►│ 机房 B │ ──► │ 机房 A │ │ 机房 B │
│ (活跃) │ │ (活跃) │ │ (故障) │ │ (接管) │
└────────┘ └────────┘ └────────┘ └────────┘
DNS / GSLB DNS 切换 / 流量调度
切换机制:
- DNS 切换 — 修改 DNS 解析指向,生效时间取决于 TTL(通常 30s~5min)
- GSLB(全局负载均衡) — 基于健康检查自动切换流量,秒级生效
- API Gateway 路由 — 在网关层直接切换后端集群
容灾演练
异地多活架构必须定期演练(如每季度一次),验证:
- 切换流程是否顺畅、切换耗时是否达标
- 数据同步延迟是否在可接受范围内
- 切换后业务指标是否正常
- 回切流程是否可靠