限流
Laravel 用
RateLimiter+throttle中间件做接口限流;Spring Boot 没有内置对应的全局限流中间件,但有多种实现路径:Bucket4j(Java 版令牌桶,最贴近RateLimiter体验)、Redis 计数器、以及第三方网关限流。这篇给出最实用的两种。
方案一:Bucket4j(代码级限流,最接近 Laravel 体验)
RateLimiter::for('api', fn() => Limit::perMinute(60)) 的对应物是 Bucket4j 令牌桶算法。
依赖
xml
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<version>8.10.1</version>
</dependency>用拦截器实现限流(推荐做法)
先建一个「限流元数据」注解(想限哪些接口就标哪个):
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int perMinute() default 60; // 每分钟次数
}再写拦截器,按「IP + 方法」维度限流:
java
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod method) {
RateLimit limit = method.getMethodAnnotation(RateLimit.class);
if (limit != null) {
String key = request.getRemoteAddr() + ":" + method.getMethod().getName();
Bucket bucket = buckets.computeIfAbsent(key, k ->
Bucket.builder()
.addLimit(Bandwidth.simple(limit.perMinute(), Duration.ofMinutes(1)))
.build());
if (!bucket.tryConsume(1)) {
response.setStatus(429); // Too Many Requests
response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁\"}");
return false;
}
}
}
return true;
}
}注册(记得 excludePathPatterns 放行登录等接口):
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final RateLimitInterceptor interceptor;
public WebConfig(RateLimitInterceptor interceptor) {
this.interceptor = interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/api/**");
}
}使用:
java
@PostMapping("/send-code")
@RateLimit(perMinute = 3) // 一分钟最多发 3 次验证码
public void sendCode(@RequestBody SendCodeDto dto) { ... }对比 Laravel
| Laravel | Spring Boot |
|---|---|
RateLimiter::for('send-code', ...) | @RateLimit 注解 + 拦截器 |
->middleware('throttle:send-code') | 在方法上标注注注册 |
429 Too Many Requests | response.setStatus(429) |
| 按用户/IP 区分 key | 拦截器里自己拼 key |
方案二:Redis + 计数器(分布式限流)
多实例部署时,上面内存里的 ConcurrentHashMap 每个实例各算各的,加起来是真实限流的 N 倍。需要分布式限流:用 Redis 原子计数。
java
@Service
public class RedisRateLimiter {
private final StringRedisTemplate redis;
public RedisRateLimiter(StringRedisTemplate redis) {
this.redis = redis;
}
public boolean tryAcquire(String key, int max, Duration window) {
long now = System.currentTimeMillis();
// 简单滑动窗口:统计窗口内请求数(生产可用 Lua 脚本保证原子性)
Long count = redis.opsForValue().increment(key, 1);
if (count != null && count == 1) {
redis.expire(key, window);
}
return count != null && count <= max;
}
}TIP
RedisRateLimiter 的原子性问题(判断 + 递增不是原子的)生产要用 Lua 脚本保证,或直接用 Spring 的 spring-boot-starter-data-redis + RedisScript。这只是思路演示。
方案三:网关限流(不改代码)
如果项目有 Spring Cloud Gateway(微服务网关)或 Nginx,限流直接在入口层做:
- Nginx:
limit_req_zone/limit_conn_zone - Spring Cloud Gateway:
RequestRateLimiter+ Redis
优点:业务代码零改动、全局统一、性能最好。缺点:规则不灵活,改策略要动网关。
怎么选?
| 场景 | 方案 |
|---|---|
| 单实例 / 简单接口 | Bucket4j 拦截器 |
| 多实例 / 微服务 | Redis 分布式限流 |
| 统一入口管理 | 网关 / Nginx |
| 登录、验证码等敏感接口 | 任意方案,必须限 |
实践建议
- 给关键接口限流:登录、发送验证码、短信、支付回调、开放 API
- 返回 429:别返回 200 再带业务码,HTTP 语义要正确
- 按场景给不同额度:登录接口 5 次/分钟,普通接口 60 次/分钟
- 限流与缓存穿透防护结合:见 缓存