在 Spring Boot 项目中,为接口添加限流是保护服务稳定性的重要手段。许多问题往往在线上出现服务被刷、响应变慢或CPU飙升后,才被发现是限流缺失或配置不当所致。
常见的实现方案主要分布在三个层面:
单机限流 → 分布式限流 → 网关限流
下面我们来详细剖析这几种方案,帮助你根据实际场景做出选择。
一、最简单:基于 Guava RateLimiter(单机)
适用场景
- 单体应用
- 不要求多实例间限流一致
- 防止接口被刷、保护资源
实现方式
- 基于令牌桶算法
- 每个接口或实例在内存中独立维护限流器
1. 引入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
2. 使用方式
@RestController
@RequestMapping("/api")
public class TestController {
// 每秒最多 5 个请求
private static final RateLimiter rateLimiter = RateLimiter.create(5.0);
@GetMapping("/test")
public String test() {
if (!rateLimiter.tryAcquire()) {
throw new RuntimeException("请求过于频繁");
}
return "ok";
}
}
优点
- 实现极其简单,上手成本低
- 性能非常好(纯内存、无 IO)
- 适合快速防刷、兜底保护
- 官方成熟组件,稳定可靠
缺点
- 只能单机生效,多实例部署时总流量会被放大
- 无法做到多节点统一限流
- 不支持动态规则调整
- 不适合核心业务接口
二、推荐方案:拦截器 / AOP + 本地计数(单机)
这是许多中大型项目在处理非核心接口时采用的做法。
适用场景
实现方式
- 自定义注解
- 通过拦截器或 AOP 统计请求次数
- 在内存中使用固定窗口或滑动窗口算法进行计数
1. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int count(); // 允许次数
int time(); // 时间窗口(秒)
}
2. 拦截器实现(滑动窗口/固定窗口)
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
private final Map<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();
private final Map<String, Long> timeMap = new ConcurrentHashMap<>();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod method = (HandlerMethod) handler;
RateLimit limit = method.getMethodAnnotation(RateLimit.class);
if (limit == null) {
return true;
}
String key = request.getRequestURI();
long now = System.currentTimeMillis();
timeMap.putIfAbsent(key, now);
counterMap.putIfAbsent(key, new AtomicInteger(0));
long startTime = timeMap.get(key);
if (now - startTime > limit.time() * 1000L) {
timeMap.put(key, now);
counterMap.get(key).set(0);
}
if (counterMap.get(key).incrementAndGet() > limit.count()) {
response.setStatus(429);
response.getWriter().write("请求过于频繁");
return false;
}
return true;
}
}
3. 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RateLimitInterceptor());
}
}
4. 使用
@RateLimit(count = 10, time = 60)
@GetMapping("/order/create")
public String createOrder() {
return "success";
}
优点
- 控制粒度细(接口级、方法级)
- 侵入性低,对业务代码影响小
- 易扩展(IP、用户、接口组合)
- 不依赖外部组件
缺点
- 仍然是单机限流
- 多实例场景存在流量穿透问题
- 自实现算法容易有边界问题(如时间窗口临界点)
- 重启应用计数会丢失
三、Redis + Lua(分布式限流)
适用场景
- 多实例部署
- 核心业务接口
- 秒杀、支付、下单接口
- 外部 API 防刷
实现方式
- 以 Redis 作为统一的计数器存储
- 使用 Lua 脚本保证计数和判断的原子性
- 支持固定窗口、滑动窗口、漏桶等多种算法
1. Lua 脚本(固定窗口)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire = tonumber(ARGV[2])
local current = redis.call("get", key)
if current and tonumber(current) >= limit then
return 0
end
current = redis.call("incr", key)
if tonumber(current) == 1 then
redis.call("expire", key, expire)
end
return 1
2. Java 调用
@Service
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
private DefaultRedisScript<Long> script;
@PostConstruct
public void init() {
script = new DefaultRedisScript<>();
script.setScriptText(LUA_SCRIPT);
script.setResultType(Long.class);
}
public boolean allow(String key, int limit, int time) {
Long result = redisTemplate.execute(
script,
Collections.singletonList(key),
String.valueOf(limit),
String.valueOf(time)
);
return result != null && result == 1;
}
}
3. 使用
@GetMapping("/pay")
public String pay() {
if (!redisRateLimiter.allow("pay", 5, 60)) {
throw new RuntimeException("请求太频繁");
}
return "ok";
}
优点
- 支持分布式,多实例一致限流
- 性能高(Lua 原子执行,减少网络往返)
- 可扩展维度多(接口 / 用户 / IP)
- 规则可动态配置
- 生产环境最常见方案
缺点
- 依赖 Redis ,需考虑其可用性
- 实现复杂度较高
- 需评估 Redis 压力
- Lua 脚本维护成本稍高
四、Spring Cloud Gateway 网关限流
适用场景
实现方式
- 在 API 网关层统一进行限流
- 通常基于 Redis 的令牌桶算法实现
- 与后端业务服务完全解耦
spring:
cloud:
gateway:
routes:
- id: order
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
优点
- 限流逻辑集中,统一管理
- 服务无感知,零侵入
- 天然支持分布式
- 可结合鉴权、熔断等功能一起使用
缺点
- 只能控制入口流量
- 无法感知服务内部业务状态
- 网关压力集中,配置相对复杂
- 单体项目不适用
总结与选择
以上四种方案各有侧重。对于刚起步的单体应用,使用 Guava RateLimiter 或自定义拦截器是快速有效的选择。当业务增长,服务走向分布式部署时,基于 Redis 和 Lua 的分布式限流方案则成为保障核心接口稳定的基石。而在微服务架构中,将限流能力上移至 Spring Cloud Gateway 等网关层,可以实现流量的统一管控和防护。
在实际的 Java 项目开发中,理解这些方案的区别并灵活运用,是构建健壮后端系统的关键技能之一。如果你对系统设计、高并发处理有更多兴趣,欢迎到 云栈社区 与更多开发者交流探讨。