流量控制基础:核心概念与三大策略
做分布式系统,最怕的是什么?我个人最怕「雪崩效应」。一个小服务挂了,拖垮一大片。我记得刚接手一个电商订单中心,一次大促,下游库存服务响应变慢,结果订单线程全部堵塞,最后整个交易平台瘫了。那次之后我才真正理解——流量控制不是锦上添花,是保命手段。
这一章,咱们聊聊流量控制的三个核心武器:限流、熔断、降级。说白了就是:挡住多余的,保护脆弱的,准备好退路。
一、核心概念对比
先看一张表,把这三个概念摆在一起比较,心里就有谱了。
| 策略 | 目标 | 触发时机 | 典型手段 |
|---|---|---|---|
| 限流 | 控制请求速率,防止系统过载 | 请求到达时,速率超过阈值 | 令牌桶、漏桶、计数器 |
| 熔断 | 快速失败,避免错误蔓延 | 错误率达到阈值 | 断路器状态机 |
| 降级 | 牺牲非核心功能,保障核心可用 | 资源紧张或故障时 | 返回兜底数据、关闭非关键功能 |
二、限流策略:把风险挡在门外
限流,顾名思义,限制流量。你在项目中遇到过突发峰值吗?我曾经用接口计数器,结果因为统计窗口边界问题,实际限流效果差得离谱。后来改用令牌桶,稳定多了。
1. 计数器算法
最简单。比如每秒最多100次请求。缺点:窗口边界容易出现流量突刺。
2. 滑动窗口
把时间窗口切小段,平滑统计。比计数器好,但内存开销大一点。
3. 令牌桶(我推荐)
以固定速率往桶里放令牌,请求拿到令牌才能通过。瞬间突发也能平滑。实际使用时,桶容量和速率要根据压测结果调整。
# 伪代码示意:令牌桶
class TokenBucket:
def __init__(self, rate, capacity):
self.rate = rate # 每秒生成令牌数
self.capacity = capacity # 桶最大容量
self.tokens = capacity
self.last_refill = time.time()
def allow(self):
now = time.time()
elapsed = now - self.last_refill
self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
4. 漏桶算法
强制固定速率流出,超出就丢弃。适合用来**削峰填谷**,但不适合处理突发。
三、熔断策略:及时止损
熔断机制,我习惯把它想象成一个电路断路器。正常时闭合,异常多了就断开,让请求快速失败,给小服务喘息时间。
熔断器有三种状态:
- Closed(闭合):正常调用,计数错误
- Open(断开):错误达到阈值,直接拒绝请求
- Half-Open(半开):过一段时间,放一个请求探测恢复情况
举个例子:一个服务调用的错误率超过50%(最近10秒内),熔断器自动打开。所有调用直接返回错误。30秒后进入半开状态,放一个试验请求,成功就闭合,失败就再次打开。
我当时踩过一个坑:熔断器和限流器的超时设置不匹配。限流器那边请求排队,熔断器统计超时错误,结果排队越多错误越多,熔断频繁抖动。解决方案是把超时时间统一管理,并且在熔断器统计时剔除限流排队的时间。
四、降级策略:留好退路
降级,说白了就是「打不过就先认怂」。业务上,降级分两类:
- 主动降级:预知大流量(比如双11),提前关闭非核心功能(如推荐弹窗、日志实时分析)
- 被动降级:检测到故障或资源不足,自动切换到兜底方案
我在一个O2O项目中,商品详情页依赖了很多推荐、评论、广告服务。一旦某个依赖挂了,我们降级为显示缓存中的静态文案+统一兜底图片。效果还不错,至少页面能打开,用户能下单。
五、三者的关系与协作
限流、熔断、降级不是孤立的。它们组成一个层层递进的防御体系。我画了一张结构图,你可以直观看到它们怎么配合:
六、实践小结
光懂概念不够,落地才是关键。我习惯在设计初期就把限流、熔断、降级方案放进架构图,而不是等出问题了再补。你想想看,如果每层都做好保护,一个小故障根本扩散不起来。
最后说一个原则:降级宁可保守,不要激进。我见过一个团队把整站降级成静态页,结果用户反馈全崩了。其实只要保留核心交易链路就好。记住,降级的目标是让系统「死得体面」,不是「直接死透」。