本文目录导读:

- 秒杀/抢购系统的接口限流(计数器算法 + 令牌桶)
- 微服务之间的RPC调用保护(漏桶 + Sentinel/Resilience4j)
- 爬虫与恶意请求识别(滑动窗口 + Redis)
- 线程池队列反压(线程池 + 自定义拒绝策略)
- 网关层的全局流量控制(Netty + 令牌桶)
- 总结:如何选择?
在Java生态中,适合做流量控制的案例通常涉及高并发场景,目的是防止系统被突发流量冲垮,以下整理了5个经典且实用的案例,从简单到复杂,覆盖了常见的限流算法和应用场景:
秒杀/抢购系统的接口限流(计数器算法 + 令牌桶)
-
案例背景: 电商平台的秒杀活动(如双十一、小米抢购),瞬间流量是平时的几十倍,需要保护数据库和订单服务。
-
实现方式:
- 计数器算法(简单粗暴): 使用
AtomicInteger或LongAdder,限制每秒最多处理100个请求,超过直接返回“活动太火爆”。 - 令牌桶(更平滑): 使用
Guava的RateLimiter,每1秒生成100个令牌,每个请求消耗一个令牌,如果没有令牌,则等待或快速失败。
- 计数器算法(简单粗暴): 使用
-
Java代码示例(Guava RateLimiter):
import com.google.common.util.concurrent.RateLimiter; // 每秒生成100个令牌 private final RateLimiter rateLimiter = RateLimiter.create(100.0); public String seckill(Long userId, Long productId) { // 尝试获取令牌,等待时间0毫秒(不等待,立即失败) if (!rateLimiter.tryAcquire(0, TimeUnit.MILLISECONDS)) { return "系统繁忙,请稍后重试"; } // 执行秒杀逻辑... return "秒杀成功"; } -
适用场景: 流量波动剧烈且需要平滑处理的场景。
微服务之间的RPC调用保护(漏桶 + Sentinel/Resilience4j)
- 案例背景: 服务A调用服务B,服务B的数据库性能较差,如果服务A接口被爬虫或瞬间请求冲击,大量请求会堆积导致服务B超时或崩溃。
- 实现方式:
- 漏桶算法: 固定处理速率,无论请求多快,服务B都按每秒50个的速度处理,多余的请求排队或丢弃。
- 框架组件:
- Sentinel(阿里开源): 通过注解
@SentinelResource(value = "serviceB", blockHandler = "handleBlock")快速配置QPS限流。 - Resilience4j(Netflix Hystrix替代): 使用
RateLimiter模块配置最大并发数。
- Sentinel(阿里开源): 通过注解
- 适用场景: 保护底层或第三方依赖服务,避免“雪崩”效应。
爬虫与恶意请求识别(滑动窗口 + Redis)
-
案例背景: 网站的用户详情页或搜索接口,正常用户每秒操作1-2次,但爬虫每秒可能请求100次。
-
实现方式:
- 滑动窗口算法(比计数器更精确): 使用
ZSET(有序集合),将每个请求的时间戳作为score和member,通过zcount统计最近1秒内的请求数,如果超过阈值(如5次/秒),则判定为疑似爬虫。
- 滑动窗口算法(比计数器更精确): 使用
-
Java代码示例(基于Redis的滑动窗口):
public boolean isAllowed(String userId) { String key = "rate:user:" + userId; long now = System.currentTimeMillis(); long windowSize = 1000; // 1秒窗口 int maxCount = 5; // 移除窗口外的旧数据 jedis.zremrangeByScore(key, 0, now - windowSize); // 统计当前窗口内的请求数 long count = jedis.zcard(key); if (count >= maxCount) { return false; // 限流 } // 加入当前请求 jedis.zadd(key, now, String.valueOf(now + Math.random())); jedis.expire(key, 1); // 设置过期时间 return true; } -
适用场景: 需要针对单个用户、IP或设备进行精细防刷的场景。
线程池队列反压(线程池 + 自定义拒绝策略)
-
案例背景: 采用异步消息处理的系统(如日志采集、订单处理),如果生产者(Producer)速度远大于消费者(Consumer),内存会飙升。
-
实现方式:
- 控制生产: 使用
Semaphore(信号量)或BlockingQueue进行流量控制,线程池的核心线程为10,最大线程为20,队列容量设为1000,当队列满时,生产者调用offer(timeout),如果超时,则丢弃或降级任务(Backpressure,即背压)。
- 控制生产: 使用
-
Java代码示例:
// 使用有界队列和显式拒绝策略 ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), // 队列容量限制 new ThreadPoolExecutor.DiscardOldestPolicy() // 丢弃最旧的任务 ); -
适用场景: 异步处理任务(MQ消费者、异步IO)中的资源控制。
网关层的全局流量控制(Netty + 令牌桶)
- 案例背景: 公司内部API网关(如Spring Cloud Gateway、Zuul),限制单个接口的QPS、限制总带宽(MB/s),或限制某个AppId的访问频率。
- 实现方式:
- 过滤器/拦截器: 使用
Java NIO或Netty的ChannelHandler,在请求到达业务逻辑前,先通过一个全局令牌桶(AtomicLong+ 定时任务)检查令牌数量。 - 分布式版本: 网关通常会结合Redis + Lua脚本实现原子性限流,避免集群中的计数器不同步。
- 过滤器/拦截器: 使用
- 适用场景: 企业级API网关,区分VIP用户和普通用户的接口限制。
如何选择?
| 案例场景 | 推荐方案 | 核心算法 | 关键挑战 |
|---|---|---|---|
| 单一应用接口 | Guava RateLimiter |
令牌桶 | 单机、重启丢失 |
| 分布式服务 | Sentinel / Resilience4j |
滑动窗口 + 漏桶 | 配置中心、规则持久化 |
| 防爬虫/防盗刷 | Redis + ZSET |
滑动窗口 | 精确时间戳、内存消耗 |
| 异步队列处理 | Semaphore / 有界队列 |
计数/背压 | 任务积压监控 |
| 大规模场景 | Nginx + Lua / Netty |
动态限流 | 毫秒级响应、集群协调 |
推荐入门路径: 先从 Guava RateLimiter 开始(简单直观),然后转移到 Sentinel(成熟度高、有控制台),最后基于 Redis 实现自定义的分布式滑动窗口。
如果你有具体的业务背景(比如是单机应用还是微服务、主要针对哪类流量),可以进一步给出更精准的代码建议。