《高并发下如何优雅限流?4种方案保护你的SpringBoot应用》
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java 回复"Swagger源码",获取本文所有示例代码、配置模板及导出工具。
每天5分钟,掌握一个SpringBoot核心知识点。大家好,我是SpringBoot指南的小坏。前四期我们聊了自动配置、异常处理、配置管理和接口文档,今天来聊聊一个更"刺激"的话题——如何防止你的系统被流量打爆?
📊 先看数据:为什么你的系统需要限流?
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java 回复"Swagger源码",获取本文所有示例代码、配置模板及导出工具。
真实案例:某电商平台去年双十一,0点瞬间涌入 1200万 用户,其中:
- 正常用户:1180万
- 恶意爬虫:15万
- 刷单程序:5万
如果没有限流:
- 数据库连接池撑爆(1000个连接瞬间占满)
- CPU飙升至100%,持续10分钟
- 核心服务全部宕机
- 直接损失:500万+ 订单
有了限流之后:
- 核心服务正常运行
- 异常请求被优雅拒绝
- 用户体验几乎无影响
- 节省成本:80%+ 的服务器资源
今天,我就带你用 4种方案 为你的系统穿上"防弹衣"!
一、限流的本质:4种算法快速理解
在开始代码之前,先搞懂这4种核心算法:
1. 固定窗口(计数器)
比喻:电影院售票窗口,每分钟卖100张票
// 伪代码理解
if (当前分钟请求数 < 100) {
允许通过;
} else {
拒绝请求;
}
缺点:窗口切换时可能双倍流量(如59秒和1秒)
2. 滑动窗口
比喻:移动的时间窗口,统计最近1分钟请求
// 伪代码理解
窗口 = [第1秒, 第2秒, ..., 第60秒]; // 记录每秒请求数
当前请求数 = 最近60秒的请求总和;
if (当前请求数 < 阈值) {
允许通过;
}
优点:更平滑,解决临界问题
3. 令牌桶算法(最常用)
比喻:一个桶,以固定速率放令牌,请求需要拿到令牌才能通过
// 伪代码理解
if (桶里有令牌) {
取走一个令牌;
允许通过;
} else {
拒绝请求;
}
// 后台线程:每秒往桶里放10个令牌
4. 漏桶算法
比喻:一个漏水的桶,请求像水一样流入,以固定速率流出
// 伪代码理解
if (桶没满) {
请求入桶排队;
等待被处理; // 以固定速率流出
} else {
拒绝请求; // 水溢出
}
理解了原理,我们开始实战!
二、方案一:Guava RateLimiter(单机首选)
2.1 5分钟快速上手
import com.google.common.util.concurrent.RateLimiter;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// 创建限流器:每秒最多处理10个请求
private RateLimiter rateLimiter = RateLimiter.create(10.0);
@PostMapping("/create")
public ResponseEntity<String> createOrder(@RequestBody OrderRequest request) {
// 1. 尝试获取令牌(非阻塞)
if (!rateLimiter.tryAcquire()) {
return ResponseEntity.status(429)
.body("请求太频繁,请稍后再试");
}
// 2. 执行业务逻辑
return ResponseEntity.ok("订单创建成功");
}
}
2.2 高级用法:预热模式
// 预热模式:系统启动时逐渐增加处理能力
// 参数说明:每秒10个请求,预热时间5秒
private RateLimiter warmupLimiter = RateLimiter.create(10.0, 5, TimeUnit.SECONDS);
@GetMapping("/detail/{id}")
public String getDetail(@PathVariable String id) {
// 获取令牌,如果当前速率不够,会等待
double waitTime = warmupLimiter.acquire();
if (waitTime > 1.0) {
log.warn("请求等待时间过长: {}秒", waitTime);
}
return "商品详情";
}
2.3 按用户限流:不同用户不同限制
@Service
public class UserRateLimitService {
// 为每个用户维护一个限流器
private Map<Long, RateLimiter> userLimiters = new ConcurrentHashMap<>();
public boolean allowRequest(Long userId) {
// 获取或创建用户的限流器
RateLimiter limiter = userLimiters.computeIfAbsent(userId,
id -> RateLimiter.create(5.0)); // 每个用户每秒5次
return limiter.tryAcquire();
}
// 定期清理不活跃的用户
@Scheduled(fixedRate = 600000) // 每10分钟
public void cleanUp() {
userLimiters.entrySet().removeIf(entry ->
// 这里可以添加判断逻辑,比如用户最近是否活跃
isUserInactive(entry.getKey())
);
}
}
适用场景:单机应用、快速验证、开发测试
三、方案二:Redisson分布式限流(集群必选)
3.1 为什么需要分布式限流?
假设你有3台服务器,每台限流10QPS:
- Guava方案:每台都能处理10个,总共30个 → 超过预期!
- Redisson方案:3台共享一个计数器,总共10个 → 符合预期!
3.2 2步集成Redisson
步骤1:添加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.2</version>
</dependency>
步骤2:配置文件
spring:
redis:
host: localhost
port: 6379
password:
database: 0
3.3 3种分布式限流实现
@Service
public class DistributedRateLimitService {
@Autowired
private RedissonClient redissonClient;
/**
* 方法1:固定窗口(简单计数器)
* @param key 限流key(如:接口名+用户ID)
* @param windowSize 窗口大小(秒)
* @param limit 限制次数
*/
public boolean tryAcquireFixed(String key, int windowSize, int limit) {
String redisKey = "rate:limit:" + key;
// 使用Redis的INCR和EXPIRE
RAtomicLong counter = redissonClient.getAtomicLong(redisKey);
// 第一次访问,设置过期时间
if (counter.get() == 0) {
counter.expire(windowSize, TimeUnit.SECONDS);
}
// 增加计数并检查
long count = counter.incrementAndGet();
return count <= limit;
}
/**
* 方法2:滑动窗口(更精确)
*/
public boolean tryAcquireSliding(String key, int windowSize, int limit) {
long now = System.currentTimeMillis();
long windowMillis = windowSize * 1000L;
// 使用ZSet存储请求时间戳
RScoredSortedSet<String> window = redissonClient.getScoredSortedSet(key);
// 删除窗口外的旧记录
window.removeRangeByScore(0, true, now - windowMillis, true);
// 检查当前窗口内请求数
if (window.size() < limit) {
window.add(now, UUID.randomUUID().toString());
window.expire(windowSize + 1, TimeUnit.SECONDS);
return true;
}
return false;
}
/**
* 方法3:Redisson内置限流器(推荐)
*/
public boolean tryAcquireRRateLimiter(String key, int permitsPerSecond) {
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
// 初始化:1秒内允许的请求数
rateLimiter.trySetRate(RateType.OVERALL, permitsPerSecond, 1, RateIntervalUnit.SECONDS);
return rateLimiter.tryAcquire();
}
}
// 使用示例
@RestController
public class ApiController {
@Autowired
private DistributedRateLimitService rateLimitService;
@GetMapping("/api/products")
public ResponseEntity<?> getProducts(@RequestParam String keyword,
@RequestHeader("X-User-Id") String userId) {
// 用户维度限流:每个用户每秒10次
String userKey = "product:search:" + userId;
if (!rateLimitService.tryAcquireRRateLimiter(userKey, 10)) {
return ResponseEntity.status(429)
.body(Map.of("code", 429, "message", "搜索太频繁了,歇会儿吧"));
}
// IP维度限流:每个IP每分钟60次
String ip = getClientIp();
String ipKey = "product:search:ip:" + ip;
if (!rateLimitService.tryAcquireFixed(ipKey, 60, 60)) {
return ResponseEntity.status(429)
.body(Map.of("code", 429, "message", "IP请求超限"));
}
// 执行业务逻辑
return ResponseEntity.ok(searchService.search(keyword));
}
}
适用场景:微服务集群、分布式系统、生产环境
四、方案三:Sentinel(阿里生产级方案)
4.1 Sentinel vs 其他方案
| 特性 | Guava | Redisson | Sentinel |
|---|---|---|---|
| 分布式 | ❌ | ✅ | ✅ |
| 实时监控 | ❌ | ❌ | ✅ |
| 规则持久化 | ❌ | ❌ | ✅ |
| 熔断降级 | ❌ | ❌ | ✅ |
| 系统保护 | ❌ | ❌ | ✅ |
| 控制台 | ❌ | ❌ | ✅ |
4.2 10分钟搭建Sentinel
步骤1:添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2022.0.0.0</version>
</dependency>
步骤2:基础配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel控制台
port: 8719
eager: true # 立即初始化
步骤3:代码中使用
@RestController
public class PaymentController {
// 1. 定义资源
@SentinelResource(
value = "createPayment",
blockHandler = "createPaymentBlocked", // 限流处理
fallback = "createPaymentFallback" // 异常降级
)
@PostMapping("/payment")
public PaymentResult createPayment(@RequestBody PaymentRequest request) {
// 业务逻辑
return paymentService.process(request);
}
// 2. 限流处理方法
public PaymentResult createPaymentBlocked(PaymentRequest request,
BlockException ex) {
return PaymentResult.fail("系统繁忙,请稍后重试");
}
// 3. 降级处理方法
public PaymentResult createPaymentFallback(PaymentRequest request,
Throwable throwable) {
return PaymentResult.fail("支付服务暂时不可用");
}
}
4.3 Sentinel控制台:可视化配置
启动控制台(Docker方式):
docker run --name sentinel -p 8858:8858 \
-d bladex/sentinel-dashboard:1.8.6
访问:http://localhost:8858 (账号/密码:sentinel/sentinel)
控制台功能:
- 实时监控:查看QPS、响应时间、异常数
- 规则管理:在线配置限流规则、熔断规则
- 集群限流:支持集群维度的流量控制
- 热点规则:针对特定参数的限流
- 系统保护:CPU、负载、线程数保护
4.4 实战:电商秒杀场景
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java 回复"Swagger源码",获取本文所有示例代码、配置模板及导出工具。
@Service
public class SeckillService {
// 秒杀商品维度限流
@SentinelResource(
value = "seckillItem",
blockHandler = "seckillBlocked"
)
public SeckillResult seckill(Long itemId, Long userId) {
// 1. 检查库存
if (!checkStock(itemId)) {
return SeckillResult.fail("库存不足");
}
// 2. 扣减库存
reduceStock(itemId);
// 3. 创建订单
return createOrder(itemId, userId);
}
// 限流处理:返回排队中
public SeckillResult seckillBlocked(Long itemId, Long userId,
BlockException ex) {
// 记录到队列,异步处理
queueService.addToQueue(itemId, userId);
return SeckillResult.queue("排队中,请等待...");
}
}
// Sentinel规则配置(可以在控制台动态调整)
/*
限流规则:
- 资源名:seckillItem
- 阈值类型:QPS
- 单机阈值:1000
- 流控模式:直接
- 流控效果:快速失败
降级规则:
- 资源名:seckillItem
- 熔断策略:慢调用比例
- 最大RT:100ms
- 比例阈值:0.5
- 熔断时长:5s
*/
适用场景:大型电商、金融系统、需要精细化控制的场景
五、方案四:Resilience4j(轻量级选择)
5.1 为什么选择Resilience4j?
如果你想要:
- ✅ 比Sentinel更轻量
- ✅ 函数式编程风格
- ✅ 与Spring Boot完美集成
- ✅ 支持熔断、限流、重试、隔离
那么Resilience4j是你的菜!
5.2 快速集成
步骤1:添加依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
<version>2.1.0</version>
</dependency>
步骤2:配置文件
resilience4j:
ratelimiter:
instances:
userService:
limit-for-period: 10 # 每个周期允许的请求数
limit-refresh-period: 1s # 周期长度
timeout-duration: 500ms # 等待超时时间
步骤3:使用注解
@RestController
public class UserController {
// 1. 最简单的使用方式
@RateLimiter(name = "userService")
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// 2. 自定义降级方法
@RateLimiter(name = "userService", fallbackMethod = "getUserFallback")
@GetMapping("/users/v2/{id}")
public User getUserV2(@PathVariable Long id) {
return userService.findById(id);
}
// 降级方法
private User getUserFallback(Long id, RequestNotPermitted ex) {
log.warn("用户查询被限流,id: {}", id);
return User.defaultUser(); // 返回默认用户
}
}
5.3 组合使用:熔断 + 限流 + 重试
@Service
public class PaymentService {
// 组合拳:先熔断,再限流,失败重试
@CircuitBreaker(name = "paymentService")
@RateLimiter(name = "paymentService")
@Retry(name = "paymentService")
@Bulkhead(name = "paymentService") // 隔离舱
public PaymentResult pay(PaymentRequest request) {
// 调用第三方支付
return thirdPartyPaymentService.pay(request);
}
// 熔断降级
public PaymentResult payFallback(PaymentRequest request, Exception ex) {
// 1. 记录到本地,后续补偿
// 2. 返回友好提示
return PaymentResult.fail("支付服务暂时不可用,请稍后重试");
}
}
// 配置示例
/*
resilience4j:
circuitbreaker:
instances:
paymentService:
failure-rate-threshold: 50 # 失败率阈值
wait-duration-in-open-state: 10s # 熔断后等待时间
ratelimiter:
instances:
paymentService:
limit-for-period: 100
limit-refresh-period: 1s
retry:
instances:
paymentService:
max-attempts: 3 # 最大重试次数
wait-duration: 500ms # 重试间隔
bulkhead:
instances:
paymentService:
max-concurrent-calls: 10 # 最大并发数
*/
适用场景:需要多种容错机制、函数式编程偏好、轻量级集成
六、实战:电商系统限流架构设计
6.1 四层限流防护体系
第一层:Nginx网关层
↓ 全局QPS限制,防DDoS
第二层:应用入口层
↓ 接口级别限流,用Sentinel
第三层:业务服务层
↓ 用户级别限流,用Redisson
第四层:方法级别
↓ 关键方法保护,用Resilience4j
6.2 具体配置示例
# 1. Nginx层限流
# nginx.conf
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
server {
location /api/ {
limit_req zone=api burst=50 nodelay;
proxy_pass http://backend;
}
}
}
# 2. Spring Boot配置
# application.yml
sentinel:
transport:
dashboard: sentinel-dashboard:8858
# 流控规则
datasource:
flow:
nacos:
server-addr: nacos:8848
data-id: ${spring.application.name}-flow-rules
rule-type: flow
# 3. Redis分布式限流配置
redis:
rate-limit:
# 用户维度
user:
window-size: 60 # 60秒
max-requests: 100
# IP维度
ip:
window-size: 3600 # 1小时
max-requests: 1000
6.3 核心代码实现
@Component
public class MultiLevelRateLimiter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 多层限流检查
*/
public RateLimitResult checkRateLimit(HttpServletRequest request,
String apiPath,
Long userId) {
// 1. 全局频率检查(防刷)
if (!checkGlobalRate(request)) {
return RateLimitResult.globalLimit();
}
// 2. IP频率检查
if (!checkIpRate(request)) {
return RateLimitResult.ipLimit();
}
// 3. 用户频率检查
if (!checkUserRate(userId)) {
return RateLimitResult.userLimit();
}
// 4. 接口频率检查
if (!checkApiRate(apiPath, userId)) {
return RateLimitResult.apiLimit();
}
return RateLimitResult.success();
}
private boolean checkGlobalRate(HttpServletRequest request) {
String key = "rate:global:" + getMinuteKey();
Long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, 70, TimeUnit.SECONDS); // 多10秒缓冲
}
return count <= 10000; // 全局每分钟1万次
}
private boolean checkIpRate(HttpServletRequest request) {
String ip = getClientIp(request);
String key = "rate:ip:" + ip + ":" + getHourKey();
Long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, 3660, TimeUnit.SECONDS);
}
return count <= 1000; // 每个IP每小时1000次
}
private boolean checkUserRate(Long userId) {
if (userId == null) return true;
String key = "rate:user:" + userId + ":" + getMinuteKey();
Long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, 70, TimeUnit.SECONDS);
}
return count <= 60; // 每个用户每分钟60次
}
private String getMinuteKey() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
}
private String getHourKey() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
}
}
七、如何选择?一张图告诉你
graph TD
A[需要限流吗?] --> B{场景选择};
B --> C[单机应用/测试环境];
B --> D[分布式系统/生产环境];
B --> E[复杂场景/需要监控];
B --> F[微服务/云原生];
C --> G[Guava RateLimiter<br/>简单快速];
D --> H[Redisson<br/>分布式限流];
E --> I[Sentinel<br/>生产级方案];
F --> J[Resilience4j<br/>轻量级容错];
style G fill:#ccf,stroke:#333
style H fill:#ccf,stroke:#333
style I fill:#ccf,stroke:#333
style J fill:#ccf,stroke:#333
选择建议:
- 个人项目/测试 → 用 Guava,5分钟搞定
- 中小型分布式系统 → 用 Redisson,分布式支持
- 大型电商/金融系统 → 用 Sentinel,功能全面
- 微服务/云原生 → 用 Resilience4j,轻量集成
八、避坑指南
坑1:限流key设计不合理
// ❌ 错误:所有用户共用一个key
String key = "api:limit:getUser";
// ✅ 正确:按用户区分
String key = "api:limit:getUser:" + userId;
// ✅ 更细粒度:按用户+接口+时间
String key = String.format("api:limit:%s:%s:%s",
userId, apiName, getMinuteKey());
坑2:忘记设置过期时间
// ❌ 错误:Redis key永不过期,内存泄漏!
redisTemplate.opsForValue().increment(key);
// ✅ 正确:设置合适的过期时间
redisTemplate.opsForValue().increment(key);
redisTemplate.expire(key, windowSize + 10, TimeUnit.SECONDS); // 多10秒缓冲
坑3:单点瓶颈
// ❌ 错误:所有请求都访问同一个Redis节点
String key = "rate:limit:global";
// ✅ 正确:使用分片或集群
String key = "rate:limit:global:" + (userId % 10); // 分10个key
坑4:忽略用户体验
// ❌ 错误:直接返回"系统繁忙"
return ResponseEntity.status(429).body("系统繁忙");
// ✅ 正确:友好提示 + 建议重试时间
long retryAfter = 60; // 60秒后重试
return ResponseEntity.status(429)
.header("Retry-After", String.valueOf(retryAfter))
.body(Map.of(
"code", 429,
"message", "请求太频繁啦,休息一下再试试",
"retryAfter", retryAfter
));
九、今日小结
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java 回复"Swagger源码",获取本文所有示例代码、配置模板及导出工具。
今天我们学习了4种限流方案:
- Guava RateLimiter - 单机快速上手
- Redisson分布式限流 - 集群环境必备
- Sentinel - 生产级全功能方案
- Resilience4j - 轻量级容错库
核心要点:
- 限流不只是防刷,更是保护系统的"保险丝"
- 从简单方案开始,根据业务复杂度升级
- 一定要监控和告警,不要"黑盒"运行
- 考虑用户体验,给出明确的错误提示
🤔 哥哥们今日思考题?
如果你的社交APP突然爆火,日活从10万暴涨到1000万,你会如何设计限流策略?考虑以下维度:
- 新用户注册(防止机器注册)
- 消息发送(防止刷屏)
- 图片上传(防止带宽打满)
- API调用(防止接口被刷)
欢迎在评论区分享你的设计方案!
下期预告:《SpringBoot日志:从打印到ELK的完整方案》—— 让你的日志不再只是"printf"!
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java 回复"Swagger源码",获取本文所有示例代码、配置模板及导出工具。