本文目录导读:

Java限流实战指南:从计数器到滑动窗口,彻底告别服务雪崩
目录导读
- 为什么你的接口需要限流? —— 理解限流的三重价值
- 四大经典限流算法深度拆解 —— 计数器/滑动窗口/漏桶/令牌桶
- Java生态限流工具选型 —— Guava RateLimiter vs Sentinel vs Resilience4j
- 企业级限流实战案例 —— 秒杀系统+外部API调用双场景落地
- 限流常见坑与面试高频问答 —— 避免“误伤”与“穿透”
为什么你的接口需要限流?
当你的订单接口每秒被调用1万次,而数据库只能承受2000QPS时,系统不会“优雅降级”,只会“崩溃雪崩”,限流的核心价值有三:
- 保护自身:防止瞬时流量打垮数据库/线程池
- 公平服务:确保每个用户获得基础服务能力(如支付接口单用户每秒最多5次)
- 成本控制:避免外部API调用(如短信服务)产生巨额账单
真实案例:某电商平台曾因未做限流,大促期间Redis连接池被占满,导致全站卡死15分钟。
四大经典限流算法深度拆解
固定窗口计数器(最简单的入门实现)
// 核心实现:AtomicInteger + 时间窗口
private AtomicInteger count = new AtomicInteger(0);
private long windowStart = System.currentTimeMillis();
public boolean tryAcquire() {
long now = System.currentTimeMillis();
if (now - windowStart > 1000) {
windowStart = now;
count.set(0);
}
return count.incrementAndGet() <= MAX_PER_SECOND;
}
致命缺陷:窗口切换瞬间可能涌入双倍流量(如0.9s-1.1s期间可放行2倍请求)。
滑动窗口算法(解决临界突变)
将1秒拆成10个格子(每个100ms),每次请求滑动最近1秒统计,Spring Cloud Gateway默认使用此算法。
漏桶算法(强制匀速消费)
流量进桶,按固定速率漏出,适合保护下游敏感服务(如写入ES),但无法应对突发流量。
令牌桶算法(允许突发,最常用)
以固定速率生成令牌,桶内最多缓存N个,请求需获取令牌才可执行。Guava RateLimiter就是令牌桶实现。
Java生态限流工具选型
| 工具 | 算法支持 | 分布式 | 学习成本 | 适用场景 |
|---|---|---|---|---|
| Guava RateLimiter | 令牌桶 | 单机接口防刷 | ||
| Sentinel | 滑动窗口+令牌桶 | 微服务集群限流降级 | ||
| Resilience4j | 滑动窗口 | 轻量级容错 |
选型建议:单机应用用Guava,Spring Cloud全家桶推荐Sentinel(控制台可视化规则)。
企业级限流实战案例
案例1:秒杀系统防刷限流(Sentinel + Redis)
@SentinelResource(value = "seckill", blockHandler = "handleBlocked")
public void doSeckill(Long userId) {
// 秒杀逻辑
}
// 规则配置:每个用户每秒限流2次 + 全局限流5000QPS
private void initFlowRules() {
FlowRule userRule = new FlowRule("seckill_user");
userRule.setResource("seckill");
userRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
userRule.setLimitApp(userId); // 按参数限流
userRule.setCount(2);
}
关键点:用用户ID做参数热点限流,防止脚本刷单。
案例2:外部第三方API调用的令牌桶保护
RateLimiter limiter = RateLimiter.create(20.0); // 每秒20个令牌
public String callSmsApi(String mobile) {
if (!limiter.tryAcquire(1, 500, TimeUnit.MILLISECONDS)) {
throw new BusinessException("短信发送太频繁,请稍后再试");
}
return httpClient.post(SMS_URL);
}
实测效果:对接某云短信服务,因限流避免了1300元/天的超量费用。
限流常见坑与面试高频问答
面试官最爱问的3个问题
Q1:令牌桶和漏桶的区别?什么时候选哪个?
- 令牌桶:允许突发流量(最多突发桶容量个),平均速率恒定,适合:接口本身能应对短期毛刺。
- 漏桶:强制平滑流量,不管外面多堵,出去始终匀速,适合:写数据库、调用付费API。
Q2:分布式限流怎么做?令牌桶能直接分布吗?
- 不能,推荐方案:Sentinel(利用Redis+Nacos同步规则)或Redis+Lua原子脚本(INCR+EXPIRE实现滑动窗口)。
-- Redis滑动窗口Lua伪代码 local key = KEYS[1] local window = 1000 -- 窗口毫秒数 local max = 5 local current = redis.call('INCR', key) if current == 1 then redis.call('PEXPIRE', key, window) end if current > max then return 0 else return 1 end
Q3:限流和熔断的区别?
- 限流:控制入口流量(不让请求进来)
- 熔断:当自身节点出问题(响应慢/报错率高),主动断开调用(保护自己不再被拖垮)
最后避坑指南
- 别只做单机限流:线上多实例部署时,Guava会导致各实例流量不均,需改用Redis集中式限流。
- 超时时间要合理:
tryAcquire超时设置过短会误杀正常请求,建议500-1000ms。 - 限流后必须降级:返回友好提示(如“排队中”)比返回500错误更利于用户体验。
一句话总结:限流不是目的,让系统在极端流量下仍然可用才是终极目标。 从简单的计数器到Sentinel分布式治理,选择适合你业务阶段的方案,并确保每次拒绝都有明确的降级策略。