本文主要介绍了SpringBoot集成Redisson实现限流,主要涉及到的类为Redisson
中的org.redisson.api.RRateLimiter
,其实现的是令牌桶
限流
<dependency>
<groupId>org.redissongroupId>
<artifactId>redisson-spring-boot-starterartifactId>
<version>3.17.0version>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
exclusion>
exclusions>
dependency>
redisson相关参数配置请参考:springboot集成redisson
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
/**
*
* @return
*/
LimitType limitType() default LimitType.DEFAULT;
/**
* 限流key,支持使用Spring el表达式来动态获取方法上的参数值
* 格式类似于 #code.id #{#code}
*/
String key() default "";
/**
* 单位时间产生的令牌数,默认100
*/
long rate() default 100;
/**
* 时间间隔,默认1秒
*/
long rateInterval() default 1;
/**
* 拒绝请求时的提示信息
*/
String showPromptMsg() default "服务器繁忙,请稍候再试";
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiters {
RateLimiter[] value();
}
public enum LimitType {
DEFAULT, // 全局
IP, // 根据客户端ip地址限制
CLUSTER // 对应服务器实例
}
@Slf4j
@Aspect
public class RateLimiterAspect {
private static final String RATE_LIMIT_KEY = "ratelimiter:";
/**
* 定义spel表达式解析器
*/
private final ExpressionParser parser = new SpelExpressionParser();
/**
* 方法参数解析器
*/
private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
isAllow(point, rateLimiter);
}
@Before("@annotation(rateLimiters)")
public void doBefore(JoinPoint point, RateLimiters rateLimiters) {
}
private void isAllow(JoinPoint point, RateLimiter rateLimiter) {
long rateInterval = rateLimiter.rateInterval();
long rate = rateLimiter.rate();
String combineKey = getCombineKey(rateLimiter, point);
RateType rateType = RateType.OVERALL;
if (rateLimiter.limitType() == LimitType.CLUSTER) {
rateType = RateType.PER_CLIENT;
}
long number = RedisUtil.rateLimiter(combineKey, rateType, rate, rateInterval);
if (number == -1) {
String message = rateLimiter.showPromptMsg();
throw new RuntimeException(message);
}
log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", rate, number, combineKey);
}
/**
* 获取rateLimite对应的复合型key值
* @param rateLimiter
* @param point
* @return
*/
private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
StringBuilder stringBuffer = new StringBuilder(RATE_LIMIT_KEY);
String key = rateLimiter.key();
Method method = getMethod(point);
// 获取URI,然后计算md5
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String uniqueKey = DigestUtils.md5DigestAsHex(request.getRequestURI().getBytes(StandardCharsets.UTF_8));
stringBuffer.append(uniqueKey);
String ip = ServletUtil.getClientIP(request);
// 判断是否是spel格式
if (StrUtil.containsAny(key, "#")) {
EvaluationContext context = new MethodBasedEvaluationContext(null, method, point.getArgs(), discoverer);
try {
Object value = parser.parseExpression(key).getValue(context);
if (value != null) {
key = value.toString();
}
} catch (Exception e) {
log.error("【{}】请求,线程:【{}】,获取spel数据失败:{}", ip, Thread.currentThread().getName(), e.getMessage());
}
}
if (rateLimiter.limitType() == LimitType.IP) {
// 获取请求ip
stringBuffer.append(":").append(ip);
}
// key名称
if (!StrUtil.hasBlank(key)) {
stringBuffer.append(":").append(key);
}
return stringBuffer.toString();
}
/**
* 获取切面方法对象
*
* @param point
* @return
*/
private Method getMethod(JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
if (method.getDeclaringClass().isInterface()) {
try {
method = point.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
log.error(null, e);
}
}
return method;
}
}
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RedisUtil {
private volatile static RedissonClient redissonClient;
public static void setRedissonClient(RedissonClient redissonClient) {
RedisUtil.redissonClient = redissonClient;
}
public static RedissonClient getRedissonClient() {
return redissonClient;
}
// =========================缓存-限流器开始=============================
/**
* 令牌桶限流(整流器)
*
* @param key 限流key
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔
* @return -1 表示失败
*/
public static long rateLimiter(String key, RateType rateType, long rate, long rateInterval) {
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits();
} else {
return -1L;
}
}
// =========================缓存-限流器结束=============================
}
@RestController
public class SsoController {
@RequestMapping("/hello")
@ResponseBody
@WebLog(value = "请求数据")
@RateLimiter(limitType = LimitType.IP, rate = 10, rateInterval = 100)
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
return "Hello ";
}
// http://127.0.0.1:8082/login
@PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE)
@RateLimiter(key = "#user.name", limitType = LimitType.IP, rate = 10, rateInterval = 100)
public String login(@Valid @RequestBody User user) {
return "Hello ";
}
}