- 首先,我们需要创建一个自定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int value(); // 限流阈值
long duration(); // 限流时间窗口
RateLimitType type(); // 限流策略类型
String limitKey() default "global"; // 限流维度,默认为全局维度
}
// 定义限流策略类型枚举
public enum RateLimitType {
// 固定时间窗口
FIXED_TIME_WINDOW,
// 滑动时间窗口
SLIDING_TIME_WINDOW,
// 其他限流策略类型
}
- 然后,我们需要创建一个切面来处理这个注解:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
@Around("@annotation(rateLimit)")
public Object limit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
// 获取注解中的限流设置
int limit = rateLimit.value();
long duration = rateLimit.duration();
RateLimitType type = rateLimit.type();
String limitKey = rateLimit.limitKey ();
// 生成key
String key = generateKey(limitKey);
// 根据限流策略类型,执行相应的限流策略
boolean allowed = false;
if (type == RateLimitType.FIXED_TIME_WINDOW) {
allowed = fixedTimeWindowRateLimit(key, limit, duration);
} else if (type == RateLimitType.SLIDING_TIME_WINDOW) {
allowed = slidingTimeWindowRateLimit(key, limit, duration);
} else {
// 其他限流策略的实现
// ...
}
if (!allowed) {
throw new RuntimeException("Rate limit exceeded");
}
// 如果没有超过限流阈值,执行方法
return joinPoint.proceed();
}
private String generateKey(String limitKey) {
// 根据限流维度生成不同的key
return "rate_limit:" + limitKey;
}
private boolean fixedTimeWindowRateLimit(String key, int limit, long duration) {
String luaScript = "local current\n" +
"current = redis.call('incr',KEYS[1])\n" +
"if tonumber(current) == 1 then\n" +
"redis.call('expire',KEYS[1],ARGV[2])\n" +
"end\n" +
"if tonumber(current) > tonumber(ARGV[1]) then\n" +
"return 0\n" +
"end\n" +
"return 1";
Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), Collections.singletonList(key), limit, duration);
return result != 0;
}
private boolean slidingTimeWindowRateLimit(String key, int limit, long duration) {
String luaScript = "local current\n" +
"current = redis.call('incr',KEYS[1])\n" +
"if tonumber(current) == 1 then\n" +
"redis.call('expire',KEYS[1],ARGV[2])\n" +
"else if tonumber(current) > tonumber(ARGV[1]) then\n" +
"redis.call('decr',KEYS[1])\n" +
"return 0\n" +
"end\n" +
"end\n" +
"return 1";
Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), Collections.singletonList(key), limit, duration);
return result != 0;
}
}
3.使用
你可以在你的方法上使用@RateLimit注解,并指定limitKey属性,例如
@RateLimit(value=10, duration=60, type=RateLimitType.FIXED_TIME_WINDOW, limitKey="#ip")
或
@RateLimit(value=10, duration=60, type=RateLimitType.FIXED_TIME_WINDOW, limitKey="#userId")
这样,你就可以根据limitKey进行限流了。