相关代码
1. 依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>
2. redission
@Bean
public RedissonClient redissonClient(){
return Redisson.create();
}
3. 注解
package com.jm.annotation;
import org.redisson.api.RateIntervalUnit;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SlidingWindowRateLimit {
String key() default "";
int rate() default 10;
int time() default 1;
RateIntervalUnit timeUnit() default RateIntervalUnit.SECONDS;
long timeout() default 0;
TimeUnit timeoutUnit() default TimeUnit.MILLISECONDS;
String fallbackMethod() default "";
boolean throwException() default true;
String errorMessage() default "Rate limit exceeded";
Class<? extends RuntimeException> exceptionClass() default RuntimeException.class;
boolean enableLog() default false;
}
4. 切面
package com.jm.aop;
import com.jm.annotation.SlidingWindowRateLimit;
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.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class SlidingWindowRateLimitAspect {
private static final Logger logger = LoggerFactory.getLogger(SlidingWindowRateLimitAspect.class);
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(slidingWindowRateLimit)")
public Object rateLimit(ProceedingJoinPoint joinPoint, SlidingWindowRateLimit slidingWindowRateLimit) throws Throwable {
String key = slidingWindowRateLimit.key();
int rate = slidingWindowRateLimit.rate();
int time = slidingWindowRateLimit.time();
RateIntervalUnit timeUnit = slidingWindowRateLimit.timeUnit();
long timeout = slidingWindowRateLimit.timeout();
TimeUnit timeoutUnit = slidingWindowRateLimit.timeoutUnit();
if (key.isEmpty()) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
key = signature.getDeclaringType().getName() + "." + signature.getName();
}
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
rateLimiter.trySetRate(RateType.OVERALL, rate, time, timeUnit);
boolean acquired;
if (timeout > 0) {
acquired = rateLimiter.tryAcquire(timeout, timeoutUnit);
if (slidingWindowRateLimit.enableLog()) {
logger.info("Rate limiter key: {}, timeout: {}{}, acquired: {}",
key, timeout, timeoutUnit, acquired);
}
} else {
acquired = rateLimiter.tryAcquire();
if (slidingWindowRateLimit.enableLog()) {
logger.info("Rate limiter key: {}, no timeout, acquired: {}", key, acquired);
}
}
if (acquired) {
return joinPoint.proceed();
} else {
return handleRateLimitFailure(joinPoint, slidingWindowRateLimit);
}
}
private Object handleRateLimitFailure(ProceedingJoinPoint joinPoint, SlidingWindowRateLimit slidingWindowRateLimit) throws Throwable {
String fallbackMethod = slidingWindowRateLimit.fallbackMethod();
if (!fallbackMethod.isEmpty()) {
try {
return invokeFallbackMethod(joinPoint, fallbackMethod);
} catch (Exception e) {
if (slidingWindowRateLimit.enableLog()) {
logger.error("Failed to invoke fallback method: {}", fallbackMethod, e);
}
}
}
if (slidingWindowRateLimit.throwException()) {
String errorMessage = slidingWindowRateLimit.errorMessage();
Class<? extends RuntimeException> exceptionClass = slidingWindowRateLimit.exceptionClass();
if (slidingWindowRateLimit.enableLog()) {
logger.warn("Rate limit exceeded for key: {}, message: {}",
generateKey(joinPoint), errorMessage);
}
RuntimeException exception;
try {
exception = exceptionClass.getConstructor(String.class).newInstance(errorMessage);
} catch (Exception e) {
exception = new RuntimeException(errorMessage);
}
throw exception;
}
if (slidingWindowRateLimit.enableLog()) {
logger.warn("Rate limit exceeded for key: {}, returning null", generateKey(joinPoint));
}
return null;
}
private Object invokeFallbackMethod(ProceedingJoinPoint joinPoint, String fallbackMethodName) throws Throwable {
Object target = joinPoint.getTarget();
Class<?> targetClass = target.getClass();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Class<?>[] parameterTypes = signature.getParameterTypes();
try {
Method fallbackMethod = targetClass.getDeclaredMethod(fallbackMethodName, parameterTypes);
fallbackMethod.setAccessible(true);
return fallbackMethod.invoke(target, joinPoint.getArgs());
} catch (NoSuchMethodException e) {
try {
Method fallbackMethod = targetClass.getDeclaredMethod(fallbackMethodName);
fallbackMethod.setAccessible(true);
return fallbackMethod.invoke(target);
} catch (NoSuchMethodException ex) {
throw new RuntimeException("Fallback method not found: " + fallbackMethodName, ex);
}
}
}
private String generateKey(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getDeclaringType().getName() + "." + signature.getName();
}
}
5. 测试 方法
package com.jm.service.impl;
import com.jm.annotation.SlidingWindowRateLimit;
import org.redisson.api.RateIntervalUnit;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class SlidingWindowServiceImpl {
@SlidingWindowRateLimit(
key = "basic-service",
rate = 1,
time = 1,
timeUnit = RateIntervalUnit.SECONDS
)
public String basicRateLimit() {
return "Basic rate limit success";
}
@SlidingWindowRateLimit(
key = "timeout-service",
rate = 1,
time = 1,
timeUnit = RateIntervalUnit.SECONDS,
timeout = 5000,
timeoutUnit = TimeUnit.MILLISECONDS,
enableLog = true
)
public String timeoutRateLimit() {
return "Timeout rate limit success";
}
@SlidingWindowRateLimit(
key = "fallback-service",
rate = 1,
time = 1,
timeUnit = RateIntervalUnit.SECONDS,
fallbackMethod = "fallbackMethod",
enableLog = true
)
public String fallbackRateLimit(String userId) {
return "Fallback rate limit success for user: " + userId;
}
private String fallbackMethod(String userId) {
return "Fallback response for user: " + userId;
}
@SlidingWindowRateLimit(
key = "custom-exception-service",
rate = 1,
time = 2,
timeUnit = RateIntervalUnit.SECONDS,
errorMessage = "服务繁忙,请稍后重试",
exceptionClass = ServiceBusyException.class,
enableLog = true
)
public String customExceptionRateLimit() {
return "Custom exception rate limit success";
}
@SlidingWindowRateLimit(
key = "no-exception-service",
rate = 1,
time = 1,
timeUnit = RateIntervalUnit.SECONDS,
throwException = false,
enableLog = true
)
public String noExceptionRateLimit() {
return "No exception rate limit success";
}
@SlidingWindowRateLimit(
key = "complex-service",
rate = 10,
time = 1,
timeUnit = RateIntervalUnit.MINUTES,
timeout = 1000,
timeoutUnit = TimeUnit.MILLISECONDS,
fallbackMethod = "complexFallback",
errorMessage = "请求过于频繁,请稍后再试",
exceptionClass = TooManyRequestsException.class,
enableLog = true
)
public String complexRateLimit(String operation, int count) {
return String.format("Complex operation %s completed with count %d", operation, count);
}
private String complexFallback(String operation, int count) {
return String.format("Fallback: Operation %s is temporarily unavailable, count: %d", operation, count);
}
@SlidingWindowRateLimit(
key = "no-param-fallback-service",
rate = 1,
time = 1,
timeUnit = RateIntervalUnit.SECONDS,
fallbackMethod = "noParamFallback",
enableLog = true
)
public String noParamFallbackRateLimit() {
return "No param fallback rate limit success";
}
private String noParamFallback() {
return "Default fallback response";
}
public static class ServiceBusyException extends RuntimeException {
public ServiceBusyException(String message) {
super(message);
}
}
public static class TooManyRequestsException extends RuntimeException {
public TooManyRequestsException(String message) {
super(message);
}
}
}
6. 测试controller
package com.jm.controller;
import com.jm.service.impl.SlidingWindowServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/slidingWindowLimiter")
public class SlidingWindowLimiterController {
@Autowired
private SlidingWindowServiceImpl slidingWindowService;
@GetMapping("/basicRateLimit")
public String basicRateLimit(){
return slidingWindowService.basicRateLimit();
}
@GetMapping("/timeoutRateLimit")
public String timeoutRateLimit(){
return slidingWindowService.timeoutRateLimit();
}
@GetMapping("/fallbackRateLimit")
public String fallbackRateLimit(){
return slidingWindowService.fallbackRateLimit("ads");
}
}