本文目录导读:

Java接口限流的实现方式有很多种,这里我为你梳理几种常用的方案,并附上核心代码示例。
基于RateLimiter的令牌桶限流
使用Guava的RateLimiter实现,适合单机限流。
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
@RestController
public class TokenBucketLimiterController {
// 每秒生成2个令牌,即QPS限制为2
private RateLimiter rateLimiter;
@PostConstruct
public void init() {
rateLimiter = RateLimiter.create(2.0);
}
@GetMapping("/api/limit")
public String limitApi() {
// 尝试获取令牌,等待超时时间为500ms
if (rateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS)) {
return "请求成功,处理业务逻辑";
} else {
return "请求过于频繁,请稍后再试";
}
}
}
基于Spring AOP的注解限流
这种方式更灵活,可以自定义限流策略。
1 自定义限流注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
// 每秒允许的请求数
double limit() default 10.0;
// 获取令牌的超时时间
long timeout() default 500;
// 超时时间单位
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
// 限流key,支持SpEL表达式
String key() default "";
}
2 AOP切面实现
import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
@Aspect
@Component
public class RateLimitAspect {
// 存储每个方法对应的RateLimiter
private final ConcurrentHashMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
@Around("@annotation(com.example.annotation.RateLimit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RateLimit rateLimit = method.getAnnotation(RateLimit.class);
if (rateLimit != null) {
// 生成限流key
String key = getKey(joinPoint, rateLimit);
RateLimiter limiter = rateLimiterMap.computeIfAbsent(key,
k -> RateLimiter.create(rateLimit.limit()));
// 尝试获取令牌
if (limiter.tryAcquire(rateLimit.timeout(), rateLimit.timeUnit())) {
return joinPoint.proceed();
} else {
throw new RuntimeException("请求过于频繁,请稍后再试");
}
}
return joinPoint.proceed();
}
private String getKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
StringBuilder key = new StringBuilder(joinPoint.getSignature().toString());
// 支持SpEL表达式的key扩展
if (!rateLimit.key().isEmpty()) {
key.append(":").append(rateLimit.key());
}
return key.toString();
}
}
3 使用示例
@RestController
public class UserController {
@RateLimit(limit = 5, key = "#userId")
@GetMapping("/user/{userId}")
public String getUserInfo(@PathVariable String userId) {
// 处理业务逻辑
return "用户信息";
}
@RateLimit(limit = 1.0) // 每秒1次
@PostMapping("/order/create")
public String createOrder() {
return "创建订单成功";
}
}
基于Redis的分布式限流
适用于分布式系统,使用Redis Lua脚本保证原子性。
1 Redis Lua限流脚本
-- 限流脚本
local key = KEYS[1] -- 限流key
local limit = tonumber(ARGV[1]) -- 最大请求数
local windowSize = tonumber(ARGV[2]) -- 窗口大小(秒)
local currentTime = tonumber(ARGV[3]) -- 当前时间戳
-- 移除过期的请求记录
redis.call('zremrangebyscore', key, 0, currentTime - windowSize * 1000)
-- 获取当前窗口内的请求数
local currentCount = redis.call('zcard', key)
if currentCount < limit then
-- 添加当前请求
redis.call('zadd', key, currentTime, currentTime .. math.random())
redis.call('expire', key, windowSize)
return 1 -- 允许通过
else
return 0 -- 限流
end
2 分布式限流实现
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
@Component
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
private DefaultRedisScript<Long> rateLimitScript;
@PostConstruct
public void init() {
rateLimitScript = new DefaultRedisScript<>();
rateLimitScript.setScriptText(
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local windowSize = tonumber(ARGV[2])\n" +
"local currentTime = tonumber(ARGV[3])\n\n" +
"redis.call('zremrangebyscore', key, 0, currentTime - windowSize * 1000)\n\n" +
"local currentCount = redis.call('zcard', key)\n\n" +
"if currentCount < limit then\n" +
" redis.call('zadd', key, currentTime, currentTime .. math.random())\n" +
" redis.call('expire', key, windowSize)\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end"
);
rateLimitScript.setResultType(Long.class);
}
public boolean tryAcquire(String key, int limit, int windowSize) {
List<String> keys = Arrays.asList(key);
long currentTime = System.currentTimeMillis();
Long result = redisTemplate.execute(
rateLimitScript,
keys,
String.valueOf(limit),
String.valueOf(windowSize),
String.valueOf(currentTime)
);
return result != null && result == 1;
}
}
@RestController
public class RedisLimitController {
@Autowired
private RedisRateLimiter rateLimiter;
@GetMapping("/api/redis-limit")
public String redisLimitApi() {
String userId = "user_123";
String key = "rate_limit:" + userId;
boolean allowed = rateLimiter.tryAcquire(key, 5, 1); // 每秒5次
if (allowed) {
return "分布式限流-请求成功";
} else {
return "请求过于频繁,请稍后再试";
}
}
}
基于拦截器的限流实现
通过Spring拦截器实现,适合全局限流。
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
// 存储不同API的限流器
private final ConcurrentHashMap<String, RateLimiter> limiters = new ConcurrentHashMap<>();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String requestURI = request.getRequestURI();
// 根据不同接口配置不同限流策略
RateLimiter limiter = limiters.computeIfAbsent(requestURI, uri -> {
if (uri.contains("/api/important")) {
return RateLimiter.create(1.0); // 重要接口每秒1次
} else if (uri.contains("/api/common")) {
return RateLimiter.create(10.0); // 普通接口每秒10次
} else {
return RateLimiter.create(5.0); // 默认每秒5次
}
});
if (limiter.tryAcquire()) {
return true;
} else {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁\"}");
return false;
}
}
}
最佳实践建议
-
选择适合的方案
- 单机应用:使用RateLimiter
- 分布式系统:使用Redis限流
- 需要灵活配置:使用注解方式
-
限流粒度
- 按接口限流
- 按用户限流
- 按IP限流
-
限流策略组合
- 可以组合使用多个限流策略
- 全局QPS限制 + 单个用户限制
-
降级处理
- 限流后提供友好的提示信息
- 可以使用降级服务或缓存数据
-
监控告警
- 记录限流日志
- 监控限流触发次数
- 设置告警阈值
选择哪种方案取决于你的具体业务场景和系统架构,建议先确定需求,再选择合适的实现方式。