⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 苏三说技术 「苏三」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前言

去年夏天某个凌晨,我接到某金融平台报警:支付接口错误率飙升至35%。

赶到机房时,发现数据库连接池耗尽,大量请求堆积成山——这就是典型的未做限流防护的灾难现场。

就像高速公路不设收费站,高峰期必然堵成停车场。

限流的本质不是拒绝服务,而是用可控的牺牲保护核心链路。

某电商大促时,他们用令牌桶算法将秒杀接口QPS限制在5万,虽然流失了20%的突发流量,但保住了99%的核心交易成功率。

1 常用限流方案

1.1 固定窗口计数器

核心原理:以固定时间窗口(如1秒)为周期,统计周期内请求数,超过阈值则拒绝后续请求。

具体代码实现如下:

// 线程安全实现(AtomicLong优化版)
publicclass FixedWindowCounter {
privatefinal AtomicLong counter = new AtomicLong(0);
privatevolatilelong windowStart = System.currentTimeMillis();
privatefinalint maxRequests;
privatefinallong windowMillis;

public boolean tryAcquire() {
long now = System.currentTimeMillis();
if (now - windowStart > windowMillis) {
if (counter.compareAndSet(counter.get(), 0)) {
windowStart = now;
}
}
return counter.incrementAndGet() <= maxRequests;
}
}

致命缺陷:假设设置1秒100次限制,0.9秒时突发100次请求,下一秒0.1秒又放行100次,实际两秒内通过200次。

就像红绿灯切换时车辆抢行,容易引发”临界点突刺”。

适用场景:日志采集、非关键性接口的粗粒度限流

1.2 滑动窗口

核心原理:将时间窗口细分为更小的时间片(如10秒),统计最近N个时间片的请求总和。

基于Redis的Lua脚本如下:

// Redis Lua实现滑动窗口(精确到毫秒)
String lua = """
local now = tonumber(ARGV
local window = tonumber(ARGV
local key = KEYS[1]

redis.call('ZREMRANGEBYSCORE', key, '-inf', now - window)
local count = redis.call('ZCARD', key)

if count < tonumber(ARGV then
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window/1000)
return 1
end
return 0
""";

技术亮点:某证券交易系统采用滑动窗口后,将API异常率从5%压降至0.3%。

通过Redis ZSET实现时间切片,误差控制在±10ms内。

优势对比

指标 固定窗口 滑动窗口
时间精度 1秒 100ms
临界突刺问题 存在 消除
实现复杂度 简单 中等

1.3 漏桶算法

核心原理:请求像水流一样进入漏桶,系统以固定速率处理请求。

桶满时新请求被丢弃。

具体实现如下:

// 漏桶动态实现(Semaphore优化版)
publicclass LeakyBucket {
privatefinal Semaphore permits;
privatefinal ScheduledExecutorService scheduler;

public LeakyBucket(int rate) {
this.permits = new Semaphore(rate);
this.scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> permits.release(rate), 1, 1, TimeUnit.SECONDS);
}

public boolean tryAcquire() {
return permits.tryAcquire();
}
}

技术痛点:某智能家居平台用此方案,确保即使10万台设备同时上报数据,系统仍按500条/秒的速率稳定处理。

但突发流量会导致队列积压,就像用漏斗倒奶茶——珍珠容易卡住。

适用场景:IoT设备控制指令下发、支付渠道限额等需要严格恒定速率的场景

1.4 令牌桶算法

核心原理:以固定速率生成令牌,请求需获取令牌才能执行。

突发流量可消耗桶内积攒的令牌。

具体实现如下:

// Guava RateLimiter高级用法
RateLimiter limiter = RateLimiter.create(10.0, 1, TimeUnit.SECONDS); // 初始预热
limiter.acquire(5); // 尝试获取5个令牌

// 动态调整速率(需反射实现)
Field field = RateLimiter.class.getDeclaredField("tokens");
field.setAccessible(true);
AtomicDouble tokens = (AtomicDouble) field.get(limiter);
tokens.set(20); // 突发时注入20个令牌

实战案例:某视频平台用此方案应对热点事件:平时限制10万QPS,突发时允许3秒内超限50%,既防雪崩又保用户体验。

动态特性

  • 正常时限制QPS
  • 突发时允许透支
  • 持续突发会耗尽令牌

2 生产环境实战

2.1 网关层分布式限流

某电商双11方案:通过Redis+Lua实现分布式计数,配合Nginx本地缓存,在网关层拦截了83%的恶意请求。

2.2 自适应熔断机制

我们还需要自适应熔断机制。

某社交平台用此方案,在突发流量时自动将限流阈值从5万降到3万,等系统恢复后再逐步提升。

3 避坑指南与性能优化

3.1 致命误区

在数据库连接池前做限流!

某公司曾因此导致连接泄漏,最终撑爆数据库。

正确做法应遵循熔断三原则:

  1. 快速失败:在入口层拦截无效请求
  2. 动态降级:核心服务保留最小资源
  3. 自动恢复:熔断后渐进式放量

3.2 性能优化

某金融系统通过JMH测试发现,使用LongAdder替代AtomicLong,限流吞吐量提升220%。

性能优化手段:减少CAS竞争 和 分段锁基座。

总结

上面列举了工作中最常用的4种限流方案。

对于不同的业务场景,我们需要选择不同的限流方案。

限流的黄金法则如下:

记住:好的限流方案就像高铁闸机——既保证通行效率,又守住安全底线。

文章目录
  1. 1. 前言
  2. 2. 1 常用限流方案
    1. 2.1. 1.1 固定窗口计数器
    2. 2.2. 1.2 滑动窗口
    3. 2.3. 1.3 漏桶算法
    4. 2.4. 1.4 令牌桶算法
  3. 3. 2 生产环境实战
    1. 3.1. 2.1 网关层分布式限流
    2. 3.2. 2.2 自适应熔断机制
  4. 4. 3 避坑指南与性能优化
    1. 4.1. 3.1 致命误区
    2. 4.2. 3.2 性能优化
  5. 5. 总结