Skip to main content

第11章 限流

第7章介绍过秒杀系统的架构方案,其中涉及了限流的相关内容。这一章就着重讲解限流的相关知识。

限流概念

11.1 业务场景:如何保障服务器承受亿级流量

在某次秒杀活动中:

  • 总计有 100 个特价商品
  • 每个商品的价格都非常低
  • 活动计划于 10 月 10 日晚上 10:10:00 开启

服务架构:

客户端 → Nginx → 网关层(Spring Cloud Zuul) → 后台服务

公司预测到秒杀开始那一瞬间会有海量用户涌入,致使系统无法处理所有请求。为保障服务器承受住大流量,只能通过限流的方式将部分流量放入后台服务。

熔断 vs 限流

机制发生位置说明
熔断服务调用方发现下游服务有问题时,在一段时间内不再调用
限流服务被调用方(主要在网关层)把超出处理能力的请求抛弃,保证能处理的请求正常

业务需求:在某个层级通过限流的方式将秒杀活动的交易 TPS 控制在 100 笔/秒

11.2 限流算法

限流算法对比

11.2.1 固定时间窗口计数算法

假设需求是后台服务每 5 秒钟处理 500 个请求:

时间段请求数是否超标
1~5秒200+300=500
6~10秒499+1=500

看起来没问题?计算一下 5~9秒 这个区间:

  • 300 + 499 = 799 个请求
  • 超标了 299 个,服务器支撑不住!

结论:固定时间窗口计数算法在现实中并不实用。

11.2.2 滑动时间窗口计数算法

假设每秒处理 100 个请求,每 100 毫秒设置一个时间区间:

  • 每 10 个时间区间合并计算请求总数
  • 超出最大数量时把多余请求抛弃
  • 进入下一个区间时,窗口向前滑动

优点:大大减少请求数超出阈值且检测不出来的概率

问题:库存 100 个商品,TPS 控制在 100 笔/秒,可能在第一个 100 毫秒请求就超过 100 个。

什么人能在 100 毫秒内完成点击购买、下单、提交订单的整个流程?只有机器人

11.2.3 漏桶算法

漏桶算法

实现步骤

  1. 任意请求进来后直接进入漏桶排队
  2. 以特定的速度处理漏桶队列里面的请求
  3. 超出漏桶负载范围的新请求直接抛弃

把输出速度设置为 100 个/秒(每 10 毫秒处理一次请求),桶的大小设置为 100。

问题:漏桶算法是先进先出原则,最终被处理的还是前面 100 个请求(机器人的请求)。

11.2.4 令牌桶算法

令牌桶算法

实现步骤

  1. 按照特定速度产生令牌(Token)并存放在令牌桶中
  2. 如果令牌桶满了,新的令牌将不再产生
  3. 新请求需要消耗桶中的一个令牌
  4. 如果桶中没有令牌,进入队列等待
  5. 如果等待队列满了,新请求直接被抛弃

配置方案

  • 令牌产生速度:100 个/秒
  • 等待队列:0(拿不到令牌直接抛弃)
  • 令牌桶数量:10(保证最多只有 10 个机器人抢到商品)

11.3 方案实现

11.3.1 使用令牌桶还是漏桶模式

算法特点适用场景
漏桶处理速度恒定,无法应对突发流量平稳流量
令牌桶可以把令牌桶装满,应对突发流量秒杀等突发场景(选择

11.3.2 在 Nginx 还是网关层实现限流

层级优点缺点
Nginx性能好基于漏桶算法,对 Lua 不熟悉
网关层令牌桶算法,团队熟悉 Java,可动态配置性能略低于 Nginx

选择:在 Java 网关层做限流。

11.3.3 分布式限流还是统一限流

方式实现优缺点
统一限流令牌桶数据存放在 RedisRedis 崩溃则限流失效
分布式限流每个节点有自己的令牌桶部分节点失效其他节点仍可工作(选择

分布式限流的影响

  • 部分节点失效时,后台处理 100 个请求的时间拉长
  • 对业务影响不大

11.3.4 使用哪个开源技术

使用开源库 Google-Guava 中 RateLimiter 的相关类来实现限流:

@Component
public class RateLimitFilter extends ZuulFilter {

// 每秒允许10个请求(100/10台服务器)
private RateLimiter rateLimiter = RateLimiter.create(10,
100, TimeUnit.MILLISECONDS); // 100ms后令牌桶满

@Override
public Object run() {
// 超时时间为0,拿不到直接抛弃
if (!rateLimiter.tryAcquire(0, TimeUnit.MILLISECONDS)) {
throw new RateLimitException("请求被限流");
}
return null;
}
}

配置说明

参数说明
permitsPerSecond100/10=10每秒产生10个令牌
warmupPeriod100毫秒令牌桶大小为1(10台服务器共10个)
tryAcquire超时0拿不到令牌直接抛弃

11.4 限流方案的注意事项

11.4.1 限流返回给客户端的错误代码

为了给用户带来好的体验:

  • 限流后被抛弃的请求返回一个特制的 HTTP CODE
  • 客户端展示专门的信息给用户

示例提示

很遗憾,商品已经秒光,您可以关注下次的秒杀活动。

第二次秒杀活动增加的提示:

您可以在10分钟后过来,有些秒杀成功但没有在10分钟内付款的用户,
他们锁定的商品会被释放出来。

11.4.2 实时监控

对限流日志随时做好记录并实时统计:

  • 有助于实时监控限流情况
  • 一旦出现意外,可以及时处理

11.4.3 实时配置

在配置中心实现对令牌桶的动态管理 + 实时设置,方便管理其他限流场景。

11.4.4 秒杀以外的场景限流配置

在平时的限流场景中,TPS 或 QPS 需要根据实际的压力测试结果来计算,从而进行限流的正确配置。

11.5 小结

限流总结

四种限流算法对比

算法原理优点缺点
固定窗口固定时间段计数简单边界突刺问题
滑动窗口滑动时间段计数精确度高可能被机器人抢占
漏桶恒定速率处理平滑流量无法应对突发流量
令牌桶按速率产生令牌可应对突发流量实现稍复杂

方案选择总结

决策点选择原因
算法令牌桶可应对突发流量
实现层Java网关层团队熟悉,可动态配置
限流方式分布式部分节点失效仍可工作
开源库Guava RateLimiter基于令牌桶,使用简单
面试常见问题
  1. 在秒杀架构中怎么保证不超卖?
  2. 熔断是基于什么条件触发的?这个条件的数据又是怎么收集的?
  3. 限流和熔断有什么不同?你了解几种限流算法?用过哪种?为什么?
  4. 项目中熔断(限流)的参数在上线后调整过吗?是根据什么调整的?

微服务的常见场景就介绍完了。接下来将进入第4部分——微服务进阶场景实战。