为什么需要三道防线
没有防护的系统:
正常流量 1,000 QPS ──▶ 系统处理正常
突发流量 10,000 QPS ──▶ DB 连接池耗尽 ──▶ 请求积压
──▶ 内存溢出 ──▶ 全服务崩溃
──▶ 恢复缓慢 ──▶ 雪崩效应
三道防线(由外到内):
① 限流(Rate Limiting):在入口拦截超量请求
「每秒最多接受1000个请求,超出直接拒绝」
② 熔断(Circuit Breaking):检测下游故障,快速失败
「下游失败率 > 50%,立即断路,不再转发请求」
③ 降级(Degradation):核心功能正常,非核心功能关闭
「支付正常 → 推荐系统可以关闭,评论可以只读」
限流算法详解
算法一:固定窗口(Fixed Window)
固定窗口计数器:
时间窗口:每1秒重置一次计数器
限制:每秒最多 100 个请求
12:00:00.000 ~ 12:00:01.000 窗口:
├── 请求1 计数=1 ✓
├── 请求2 计数=2 ✓
├── ...
├── 请求100 计数=100 ✓
└── 请求101 计数=101 ✗ 拒绝!
问题:窗口边界攻击!
┌────────────────────┬───────────────────┐
│ 00.9s 内 100次 │ 01.0s 后 100次 │
└────────────────────┴───────────────────┘
↑ 这0.2s内实际通过了200次请求!
算法二:滑动窗口(Sliding Window)
滑动窗口:
当前时间:12:00:01.500
窗口大小:1秒
统计范围:12:00:00.500 ~ 12:00:01.500
──────────────────────────────────────────────
12:00:00.0 请求×20
12:00:00.4 请求×30 ← 这50个在窗口外
12:00:00.6 请求×30 ← 这30个在窗口内
12:00:01.0 请求×40 ← 在窗口内
12:00:01.4 请求×10 ← 在窗口内
──────────────────────────────────────────────
当前计数 = 30 + 40 + 10 = 80 (< 100,允许)
优点:平滑,无边界突刺问题
缺点:需要记录每条请求的时间戳(内存开销大)
实现:Redis ZSet,Key=IP,Score=时间戳,Value=请求ID
ZADD ip:1.2.3.4 1700000000.123 req-uuid
ZREMRANGEBYSCORE ip:1.2.3.4 0 (now-1s) // 移除窗口外的
count = ZCARD ip:1.2.3.4 // 当前窗口计数
算法三:漏桶(Leaky Bucket)
漏桶算法:
请求涌入(速率不固定)
│││││
▼▼▼▼▼
┌──────────┐ ← 桶满则溢出(丢弃请求)
│ │
│ 桶 │ 容量:100
│ │
└────┬─────┘
│ 匀速流出(固定速率)
▼
下游服务(10 req/s)
优点:完全平滑输出,下游永远以固定速率接收请求
缺点:突发流量被强制排队或丢弃,无法利用系统瞬时余量
适用:需要严格保护下游的场景(如第三方 API 调用)
算法四:令牌桶(Token Bucket)— 推荐
令牌桶算法:
令牌生成器以固定速率往桶里放令牌:
┌──────────────────────────────────────────────┐
│ Token Generator: 100 tokens/second │
│ │
│ ┌──────────────────────┐ │
│ ───▶ │ Token Bucket │ │
│ 放入 │ ● ● ● ● ● ● ● ● ● ● │ 容量 100 │
│ │ ● ● ● ● ● ● ● ● ● ● │ │
│ └──────────┬───────────┘ │
│ │ │
│ 请求到来:拿走一个令牌 → 处理请求 │
│ 无令牌:拒绝或等待 │
└──────────────────────────────────────────────┘
vs 漏桶的区别:
漏桶:输出速率固定,无法利用突发余量
令牌桶:平时积累令牌,突发时可以消耗积累的令牌
允许短暂的流量突刺(Burst),更实用!
例子:平时 10 req/s,桶满100个令牌
突然来了200个请求:
前100个:消耗存量令牌,通过!
后100个:新令牌还没生成,拒绝
实现(Redis + Lua 原子操作):
tokens = min(capacity, last_tokens + rate * elapsed)
if tokens >= 1:
tokens -= 1
allow = True
else:
allow = False
| 算法 | 允许突刺 | 平滑度 | 实现复杂度 | 推荐场景 |
|---|---|---|---|---|
| 固定窗口 | 边界处可 | 差 | 低 | 简单场景 |
| 滑动窗口 | 否 | 好 | 中 | 精确限流 |
| 漏桶 | 否 | 极好 | 中 | 保护下游 |
| 令牌桶 | 是 | 好 | 中 | 通用推荐 |
熔断策略
熔断器在第8章已详细介绍三态状态机。这里重点关注熔断触发条件的配置:
Resilience4j 熔断配置示例:
CircuitBreakerConfig {
slidingWindowSize: 10 // 统计最近10次请求
failureRateThreshold: 50 // 失败率 > 50% 触发熔断
slowCallRateThreshold: 80 // 慢调用率 > 80% 也触发
slowCallDurationThreshold: 2s // 2秒以上算慢调用
waitDurationInOpenState: 30s // OPEN 状态等待30秒后进入 HALF-OPEN
permittedCallsInHalfOpen: 3 // HALF-OPEN 时允许3个探测请求
minimumNumberOfCalls: 5 // 至少5次调用才开始统计
}
熔断 vs 限流 的区别:
限流:我限制自己能发多少请求(入口控制)
熔断:我检测到你(下游)出了问题,我不再调用你(出口保护)
降级策略
降级是在系统资源不足或部分组件故障时,主动关闭非核心功能,保证核心链路畅通。
降级优先级划分(电商系统为例):
P0 核心链路(绝不降级):
用户登录 → 商品展示 → 加入购物车 → 下单 → 支付
P1 重要功能(尽量保留):
订单查询、物流查询、退款申请
P2 辅助功能(可以降级):
商品评论(降级为:暂时无法查看评论)
推荐算法(降级为:固定热销商品列表)
实时库存显示(降级为:显示「有货」)
P3 增值功能(直接关闭):
猜你喜欢
最近浏览
用户行为统计上报
降级的四种形式:
① 返回默认值:推荐列表返回热销 Top10
② 返回缓存数据:即使缓存过期,也返回旧数据(stale cache)
③ 简化处理:评论列表只返回前5条
④ 完全关闭:直接返回「功能暂时不可用」
超时设置原则
超时设置的艺术:
场景:API Gateway → Service A → Service B → DB
如果不设超时:
DB 慢查询 60s → Service B 等待 60s
→ Service A 等待 60s(连接池耗尽)
→ API Gateway 等待 60s(线程耗尽)
→ 前端白屏 60s → 用户不断重试 → 雪崩!
正确的超时设置(从内到外递减):
DB 查询超时: 5s
Service B 超时: 6s(= DB超时 + 处理时间缓冲)
Service A 超时: 8s(= B超时 + 处理时间缓冲)
API Gateway 超时:10s(= A超时 + 处理时间缓冲)
前端超时: 12s
原则:
外层超时 > 内层超时(给内层时间先超时并返回)
超时 + 重试要配合熔断(否则重试会放大压力)
不同操作设置不同超时:读操作100ms,写操作500ms
SLA 定义与 SLO/SLI
SLA
服务级别协议
服务级别协议
Service Level Agreement。与用户/客户签订的合同承诺,违反了要赔钱。如:「API 可用性 >= 99.9%,否则赔偿服务费」。
SLO
服务级别目标
服务级别目标
Service Level Objective。团队内部设定的技术目标,比 SLA 更严格。如内部 SLO 是 99.95%,对外 SLA 承诺 99.9%(留缓冲区)。
SLI
服务级别指标
服务级别指标
Service Level Indicator。衡量 SLO 的具体度量指标。如:可用性 = 成功请求数 / 总请求数;延迟 = P99 响应时间。
Error Budget
错误预算
错误预算
SLO 允许的失败空间。99.9% SLO → 每月 43.8 分钟可用于故障/发布。预算耗尽 → 冻结发布,专注稳定性。
▶ 面试要点
- 令牌桶 vs 漏桶:令牌桶允许突刺,漏桶不允许。大多数互联网 API 用令牌桶,对下游有严格保护要求时用漏桶。
- 限流的粒度:全局限流(所有用户共享配额)vs 用户级限流(每个用户独立配额)。API 开放平台通常按 API Key 限流。
- Google SRE 的 Error Budget 理念:可用性目标不是越高越好,99.999% 意味着每月只有 26 秒容错空间,研发团队无法发布任何有风险的更新。合理的 SLO 比盲目追求高可用更重要。