一个注解搞定接口限流的完整实现指南
注解实现的功能如下:
- 支持根据配置动态选择分布式限流器或者单机限流器
- 支持类级别使用限流
- 支持方法级别限流(优先)
- 支持多个方法使用相同的限流器(分组限流)
- 支持spel表达指定限流主体
限流器实现的功能:
- 双存储结构:漏桶算法+滑动窗口
- 自旋锁优化:SpinLock实现轻量级同步
- 自动清理机制:过期数据定时回收
一、限流器核心实现(面向接口设计)
1. 漏桶算法实现
/**
* @Author: 公众号: 加瓦点灯
* @Date: 2025-03-20-18:00
* @Description:
*/
public class LocalRateLimiter implements IRateLimiter {
private double rate; // 漏水速率(请求/秒)
private long capacity; // 桶容量(放大1000倍提升精度)
private Map requestCountMap = new HashMap<>(); // 当前水量
private Map requestTimeMap = new HashMap<>(); // 最后请求时间
private SpinLock lock = new SpinLock(); // 自旋锁
// 核心限流判断
public boolean isGranted(String key) {
try {
lock.lock();
long current = System.currentTimeMillis();
cleanUp(current); // 清理过期数据
Long lastTime = requestTimeMap.get(key);
long count = requestCountMap.getOrDefault(key, 0L);
// 首次访问初始化
if (lastTime == null) {
requestTimeMap.put(key, current);
requestCountMap.put(key, 1000L);
return true;
}
// 计算漏水量 = 时间间隔 * 漏水速率
long leaked = (long) ((current - lastTime) * rate);
count = Math.max(count - leaked, 0);
// 判断剩余容量
if (count < capacity requestcountmap.putkey count 1000 requesttimemap.putkey current return true return false finally lock.unlock 2 private void cleanuplong current if current - lastcleantime> EXPIRE_MS) {
Iterator<Map.Entry> it = requestTimeMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
if (entry.getValue() < current - EXPIRE_MS) {
it.remove();
requestCountMap.remove(entry.getKey());
}
}
lastCleanTime = current;
}
}
}
实现要点:
- 使用双Map分离存储时间戳和计数器
- 自旋锁保证线程安全(适合高并发场景)
- 清理机制防止内存泄漏
限流器代码详细解读移步:轻量级限流器源码解读
二、注解定义与字段说明
/**
* @Author: 公众号: 加瓦点灯
* @Date: 2025-03-20-18:00
* @Description:
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
/**
* SPEL表达式指定限流主体
* 示例: #user.id / #request.getHeader('X-UID')
*/
String key() default "";
/**
* 时间窗口内允许的请求数
*/
int limit() default 1;
/**
* 时间窗口长度(秒)
*/
int interval() default 1;
/**
* 限流器分组(相同分组共享限流器)
*/
String group() default "";
/**
* 触发限流时的提示信息
*/
String msg() default "请求过于频繁";
/**
* 类级别限流时排除的方法名
*/
String[] excludeMethods() default {};
}
三、切面实现与限流控制
1. 切面处理流程
/**
* @Author: 公众号: 加瓦点灯
* @Date: 2025-03-20-18:00
* @Description:
*/
@Aspect
@Component
public class AccessLimitAspect {
@Around("within(@com.shemuel.site.annotation.AccessLimit *) && within(com.shemuel.site.controller..*) || " +
"@annotation(com.shemuel.site.annotation.AccessLimit) && within(com.shemuel.site.controller..*)")
public Object beforeClassAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Object target = joinPoint.getTarget();
// 获取方法上的注解
AccessLimit methodAnnotation = AnnotationUtils.findAnnotation(method, AccessLimit.class);
// 获取类上的注解
AccessLimit classAnnotation = AnnotationUtils.findAnnotation(target.getClass(), AccessLimit.class);
AccessLimit activeAnnotation = null;
if (classAnnotation != null) {
activeAnnotation = classAnnotation;
}
// method上的优先生效
if (methodAnnotation != null) {
activeAnnotation = methodAnnotation;
}
if (activeAnnotation == null) {
return joinPoint.proceed();
}
// 1. 解析SpEL表达式,获取限流Key对应的参数值
String keyValue = parseKey(activeAnnotation.key(), joinPoint);
log.info("拦截类注解 - 方法名: {}" , method.getName());
log.info("拦截类注解 - 类名: {} " , joinPoint.getTarget().getClass().getName());
String accessGroup = getAccessGroup(activeAnnotation.group(), target.getClass().getName(), method.getName());
IRateLimiter rateLimiter = rateLimiterFactory.getRateLimiter(accessGroup);
if (rateLimiter == null) {
return joinPoint.proceed();
}
log.info("限流的key: " + keyValue);
log.info("限流的group: " + accessGroup);
boolean granted = rateLimiter.isGranted(keyValue);
if (!granted) {
return RestResult.error(activeAnnotation.msg());
}
return joinPoint.proceed();
}
/**
* 指定了限流key, 按照key进行限流
* key 是spel 则解析
* 未指定key, 按照用户维度进行限流
* 已登录的接口,都需要按照用户维度限流
*/
public String parseKey(String keyRegex, ProceedingJoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getName();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 优先使用指定的spel表达式的key
if (StringUtils.isNotEmpty(keyRegex)) {
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
StandardEvaluationContext context = new StandardEvaluationContext();
// 将方法参数名和值绑定到上下文
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
// 解析表达式
Expression expression = parser.parseExpression(keyRegex);
String value = expression.getValue(context, String.class);
// 解析成功,返回解析结果
if (StringUtils.isNotEmpty(value)) {
return value;
}
// 解析失败,就按照原始的key进行限流
return keyRegex;
}
// 如果key为空,使用用户id
if (StpUtil.isLogin()) {
// 已登录
// ,按照用户 + 方法级别进行限流
return StpUtil.getLoginIdAsString();
}
// 没有登录,没有key,按照方法级别进行限流
return className + method.getName();
}
public String getAccessGroup(String group, String className, String methodName) {
return StringUtils.isEmpty(group)
? className + "#" + methodName
: group;
}
}
四、启动时初始化限流器
1. 限流器工厂实现
package com.shemuel.site.utils;
import com.shemuel.site.annotation.AccessLimit;
import com.shemuel.site.common.LocalRateLimiter;
import com.shemuel.site.exception.ServiceException;
import com.shemuel.site.service.IRateLimiter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author: 公众号: 加瓦点灯
* @Date: 2025-03-20-18:00
* @Description:
*/
@Component
public class RateLimiterFactory {
@Value("${rateLimiter.type: local}")
private String rateLimiterType;
private ConcurrentHashMap rateLimiterMap = new ConcurrentHashMap<>();
public IRateLimiter createRateLimiter(AccessLimit accessLimit) {
if ("redis".equals(rateLimiterType)){
throw new RuntimeException("redis限流暂未实现");
}else {
return new LocalRateLimiter( accessLimit.interval(), accessLimit.limit());
}
}
public void addRateLimiter(String group, IRateLimiter rateLimiter) {
rateLimiterMap.put(group, rateLimiter);
}
public IRateLimiter getRateLimiter(String group) {
return rateLimiterMap.get(group);
}
public Set getAllRateLimiterGroups() {
return rateLimiterMap.keySet();
}
public IRateLimiter removeRateLimiter(String group) {
return rateLimiterMap.remove(group);
}
}
2. 项目启动初始化限流器工厂
/**
* @Author: 公众号: 加瓦点灯
* @Date: 2025-03-20-18:00
* @Description:
*/
@Component
@Slf4j
public class RateLimiterInitializer implements ApplicationRunner {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private RateLimiterFactory rateLimiterFactory;
@Override
public void run(ApplicationArguments args) {
Map beansWithAnnotation = applicationContext.getBeansWithAnnotation(RestController.class);
for (Object controller : beansWithAnnotation.values()) {
Class> clazz = AopUtils.getTargetClass(controller);
processClassAnnotations(clazz);
}
log.info("RateLimiterInitializer initialized {}", JSON.toJSONString(rateLimiterFactory.getAllRateLimiterGroups()));
}
private void processClassAnnotations(Class> clazz) {
AccessLimit classAnnotation = clazz.getAnnotation(AccessLimit.class);
Set excludes = new HashSet<>();
// 先处理类上的注解
if (classAnnotation != null) {
// 记录排除的方法
excludes.addAll(new HashSet<>(Arrays.asList(classAnnotation.excludeMethods())));
}
// 遍历所有方法
for (Method method : clazz.getDeclaredMethods()) {
// 优先处理类注解
if (classAnnotation != null) {
doCreateLimiter(clazz, classAnnotation, method);
}
AccessLimit methodAnnotation = method.getAnnotation(AccessLimit.class);
// 再处理方法注解
if (methodAnnotation != null) {
doCreateLimiter(clazz, methodAnnotation, method);
}
}
// 处理排除的方法
if (!CollectionUtils.isEmpty(excludes)) {
for (String exclude : excludes) {
String group = classAnnotation.group().isEmpty()
? clazz.getName() + "#" + exclude
: classAnnotation.group();
rateLimiterFactory.removeRateLimiter(group);
}
}
}
private void doCreateLimiter(Class> clazz, AccessLimit classAnnotation, Method method) {
String group = classAnnotation.group().isEmpty()
? clazz.getName() + "#" + method.getName()
: classAnnotation.group();
IRateLimiter rateLimiter = rateLimiterFactory.createRateLimiter(classAnnotation);
rateLimiterFactory.addRateLimiter(group, rateLimiter);
}
五、使用场景示例
1. 类级别限流(所有方法共享)
/**
* @Author: 公众号: 加瓦点灯
* @Date: 2025-03-20-18:00
* @Description:
*/
@RestController
@AccessLimit(
group = "orderAPI",
limit = 100,
interval = 60
)
public class OrderController {
// 所有方法共享每秒100次限制
@GetMapping("/list")
public Result listOrders() { ... }
// 排除特定方法
@AccessLimit(excludeMethods = "getDetail")
@GetMapping("/detail")
public Result getDetail() { ... }
}
2. 方法级别独立限流
/**
* @Author: 公众号: 加瓦点灯
* @Date: 2025-03-20-18:00
* @Description:
*/
@RestController
public class PaymentController {
@AccessLimit(
limit = 10,
interval = 5
)
@PostMapping("/pay")
public Result createPayment() { ... }
}
3. 跨接口共享限流器
// 支付相关接口共享限流
@AccessLimit(group = "payment", limit = 50, interval = 10)
@PostMapping("/alipay")
public Result aliPay() { ... }
@AccessLimit(group = "payment", limit = 50, interval = 10)
@PostMapping("/wechatPay")
public Result wechatPay() { ... }
4. SPEL动态限流主体
// 根据用户ID限流
@AccessLimit(
key = "#user.id",
limit = 5,
interval = 60
)
@GetMapping("/profile")
public Result getProfile(@Auth User user) { ... }
// 根据请求IP限流
@AccessLimit(
key = "#request.remoteAddr",
limit = 100,
interval = 60
)
@GetMapping("/api")
public Result publicApi(HttpServletRequest request) { ... }
通过以上实现,我们构建了一个灵活、可扩展的注解式限流系统。开发人员只需通过简单的注解配置,即可实现从简单到复杂的各种限流场景,同时保持业务代码的整洁性。
最后
欢迎关注gzh:加瓦点灯, 您的支持是我创作的最大动力!