目录
[2.1、API 接口级限流](#2.1、API 接口级限流)
[2.3、AOP 切面限流](#2.3、AOP 切面限流)
前沿
在高并发场景下,限流是保护系统不被压垮的"安全阀"。
如下所示:

更多详细介绍,可参考:分布式系统设计的容错机制
https://dyclt.blog.csdn.net/article/details/150345015?spm=1011.2415.3001.5331
本文将带你深入理解 网关限流、API 接口限流、AOP 限流、拦截器限流 四种主流方案.
1、限流
1.1、设计背景
如下所示:

- 防止恶意刷接口(如爬虫、DDoS)
- 保护下游服务(数据库、第三方 API)
- 保证核心业务可用性(如支付 > 查询)
- 实现公平资源分配(如免费用户 vs VIP)
⚠️ 限流 ≠ 熔断/降级,它是入口控制,而非故障处理。
1.2、限流算法
如下所示:
| 算法 | 原理 | 特点 |
|---|---|---|
| 计数器(固定窗口) | 每秒最多 N 次请求 | 实现简单,但存在"临界突刺"问题 |
| 滑动窗口 | 将时间窗口细分,更平滑 | 精度高,内存占用略大 |
| 漏桶(Leaky Bucket) | 请求以恒定速率流出 | 平滑流量,但无法应对突发 |
| 令牌桶(Token Bucket) | 以固定速率生成令牌,请求消耗令牌 | 允许突发流量,最常用 |
Spring Cloud Gateway / Redis + Lua 通常基于令牌桶或滑动窗口
1.3、使用建议
1.第一道防线:网关限流
→ 全局兜底,防 DDoS,按 IP 限流
2.第二道防线:AOP 限流
→ 核心业务方法(如下单、发短信),按用户 ID 限流
3.补充:拦截器限流
→ 传统项目无网关时的替代方案
4.避免:纯 API 限流
→ 仅用于临时调试或单机场景
⚠️注意:限流策略应与 监控告警(如 Prometheus + Grafana) 结合,实时观察限流效果!
2、解决方案
2.1、API 接口级限流
(最细粒度)
1、原理
在 Controller 方法上直接加限流注解或代码,针对单个接口进行限流。
如下所示:

2、实现方式(基于 Guava RateLimiter)
java
@RestController
@Slf4j
public class SmsController {
// 每秒生成 1 个令牌,桶容量 = 2(允许突发 2 次)
private final RateLimiter smsRateLimiter = RateLimiter.create(1.0, 2, TimeUnit.SECONDS);
@PostMapping("/api/sms/send")
public ResponseEntity<?> sendSms(@RequestBody SmsRequest request) {
// 尝试获取令牌,最多等待 500ms
if (!smsRateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS)) {
log.warn("短信接口限流,userId={}", request.getUserId());
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.body(ApiResponse.error("发送太频繁,请1秒后再试"));
}
try {
smsService.send(request.getPhone(), request.getContent());
return ResponseEntity.ok(ApiResponse.success());
} catch (Exception e) {
log.error("发送短信失败", e);
return ResponseEntity.status(500).body(ApiResponse.error("发送失败"));
}
}
}
⚠️ 关键细节
tryAcquire(timeout):避免线程阻塞- 单机有效:多实例部署时每个节点独立计数
- 无持久化:服务重启后计数清零
📊 效果
- QPS ≤ 1
- 突发请求:前 2 次立即通过,第 3 次需等待
生产慎用:仅适合单体应用或临时调试
2.2、网关层限流
1、原理
在 Spring Cloud Gateway 或 Nginx 等网关层统一限流,所有请求先经过网关 ,天然支持分布式。推荐!全局入口。
如下所示:

2.Spring Cloud Gateway + Redis 实现(令牌桶)
- 添加依赖
XML
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- 支持 SpEL 表达式 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 配置限流规则
java
spring:
cloud:
gateway:
routes:
# 订单服务
- id: order_route
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 令牌生成速率(每秒)
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
key-resolver: "#{@userKeyResolver}" # 按用户ID限流
# 短信服务(更严格)
- id: sms_route
uri: lb://sms-service
predicates:
- Path=/api/sms/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 2
key-resolver: "#{@ipKeyResolver}" # 按IP限流
- 定义限流 Key(按 IP 限流)
java
@Configuration
public class KeyResolverConfig {
// 按用户ID限流(从 Header 获取)
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-ID");
return Mono.justOrEmpty(userId).defaultIfEmpty("anonymous");
};
}
// 按 IP 限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
// 按 API 路径限流
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}
- 自定义限流响应
java
@Component
@Primary
public class JsonBlockHandler implements BlockRequestHandler {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
Map<String, Object> body = Map.of(
"code", 429,
"message", "请求过于频繁,请稍后再试",
"retryAfter", "1" // 建议重试时间(秒)
);
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.header("Retry-After", "1") // HTTP 标准头
.bodyValue(body);
}
}
- Redis 配置优化(连接池)
java
spring:
redis:
host: redis-cluster
port: 6379
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 2
⚠️ 生产避坑指南
| 问题 | 解决方案 |
|---|---|
| Redis 宕机导致限流失效 | 配置降级策略(如本地缓存兜底) |
| Key 过多导致 Redis 内存爆炸 | 设置 Key TTL(Gateway 自动设置) |
| 限流不生效 | 检查路由顺序(先匹配的路由先生效) |
| 高并发下 Lua 脚本性能瓶颈 | 使用 Redis Cluster 分片 |
⚠️ 优缺点

强烈推荐:所有微服务架构的首选方案!
2.3、AOP 切面限流
1.原理
通过自定义注解 + AOP,在任意 Service/Controller 方法上添加限流逻辑。(方法级,灵活).
如下所示:

2.实现步骤
- 自定义限流注解
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
// 限流策略
int limit() default 10; // 最大请求数
int timeout() default 60; // 时间窗口(秒)
String key() default ""; // 自定义 key(SpEL 表达式)
String prefix() default "rate"; // Redis key 前缀
boolean throwException() default true; // 是否抛异常
}
2.滑动窗口
java
@Component
public class RateLimitLuaScript {
// KEYS[1] = 限流 key
// ARGV[1] = limit
// ARGV[2] = timeout(秒)
public static final String SCRIPT =
"local current = redis.call('GET', KEYS[1])\n" +
"if current then\n" +
" if tonumber(current) >= tonumber(ARGV[1]) then\n" +
" return 0\n" +
" else\n" +
" redis.call('INCR', KEYS[1])\n" +
" return 1\n" +
" end\n" +
"else\n" +
" redis.call('SET', KEYS[1], 1, 'EX', ARGV[2])\n" +
" return 1\n" +
"end";
}
- AOP 切面实现(基于 Redis + Lua)
java
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Around("@annotation(rateLimit)")
public Object intercept(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
// 1. 生成限流 key
String key = resolveKey(joinPoint, rateLimit);
String redisKey = rateLimit.prefix() + ":" + key;
// 2. 执行 Lua 脚本
DefaultRedisScript<Long> script = new DefaultRedisScript<>(RateLimitLuaScript.SCRIPT, Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(redisKey),
String.valueOf(rateLimit.limit()),
String.valueOf(rateLimit.timeout())
);
// 3. 判断结果
if (result != null && result == 1) {
return joinPoint.proceed();
} else {
String errorMsg = String.format("接口限流,key=%s, limit=%d/%ds",
redisKey, rateLimit.limit(), rateLimit.timeout());
log.warn(errorMsg);
if (rateLimit.throwException()) {
throw new BusinessException("请求过于频繁,请稍后再试");
}
return null; // 或返回默认值
}
}
private String resolveKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
if (!StringUtils.hasText(rateLimit.key())) {
// 默认 key:类名 + 方法名
return joinPoint.getSignature().toLongString();
}
// 支持 SpEL 表达式,如 "#userId" 或 "#request.phone"
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] paramNames = signature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
try {
return parser.parseExpression(rateLimit.key()).getValue(context, String.class);
} catch (Exception e) {
log.error("解析 SpEL 表达式失败: {}", rateLimit.key(), e);
return joinPoint.getSignature().getName(); // 降级
}
}
}
- 使用示例
java
@Service
public class OrderService {
// 每个用户每分钟最多创建 5 个订单
@RateLimit(limit = 5, timeout = 60, key = "#userId")
public Order createOrder(String userId, CreateOrderDTO dto) {
// ...
}
// 每个手机号每天最多发送 3 条短信
@RateLimit(limit = 3, timeout = 86400, key = "#request.phone", prefix = "sms")
public void sendSms(SmsRequest request) {
// ...
}
// 全局限流:整个方法每秒最多 10 次
@RateLimit(limit = 10, timeout = 1)
public List<Product> listProducts() {
// ...
}
}
⚠️ 关键优势
- 动态 Key:支持方法参数(如用户ID、手机号)
- 分布式安全:Redis 保证全局一致性
- 灵活降级:可选择抛异常或返回 null
适用场景:核心业务方法的精细化限流
⚠️ 优缺点

适用场景:需要对特定业务方法限流(如"下单"、"发送短信")
2.4、拦截器限流
(Controller 层统一控制)
1.原理
通过 Spring MVC 的 HandlerInterceptor,在请求进入 Controller 前进行限流。
如下所示:

2.实现步骤
自定义拦截器(带防刷 + 日志)
代码如下所示:
java
@Component
@Slf4j
public class RateLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 白名单(内部 IP 不限流)
private static final Set<String> WHITE_LIST = Set.of("127.0.0.1", "192.168.1.100");
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String clientIp = getClientIp(request);
if (WHITE_LIST.contains(clientIp)) {
return true; // 白名单放行
}
String uri = request.getRequestURI();
String key = "interceptor:limit:" + clientIp + ":" + uri;
// 使用 INCR + EXPIRE 原子操作(Redis 2.6+)
Long count = redisTemplate.execute(
(RedisCallback<Long>) connection -> {
byte[] redisKey = key.getBytes();
Long current = connection.incr(redisKey);
if (current == 1) {
connection.expire(redisKey, 60); // 1分钟窗口
}
return current;
}
);
if (count != null && count > 20) { // 每分钟最多20次
log.warn("拦截器限流触发,ip={}, uri={}", clientIp, uri);
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":429,\"msg\":\"请求过于频繁\"}");
return false;
}
return true;
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Real-IP");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 处理代理情况(取第一个 IP)
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
}
- 注册拦截器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor())
.addPathPatterns("/api/**"); // 仅拦截 API 路径
}
}
⚠️ 注意事项
- 路径匹配:确保排除健康检查、监控端点
- IP 获取:正确处理 Nginx 代理(X-Forwarded-For)
- 性能:每个请求都访问 Redis,需监控延迟
适用场景:传统 Spring MVC 单体应用,无网关时的替代方案
优缺点

适用场景:传统单体应用,需要对所有 API 接口统一限流
总结:

参考文章:
1、分布式系统限流