springboot利用resilience4j 令牌桶实现限流防爆破
一:引入 resilience4j
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.1</version>
</dependency>
二:编写自定义注解类
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 {
double permitsPerSecond() default 1.0; // 每秒允许的请求数,默认是 1.0
}
三:编写实际限流防爆破逻辑,MyException是自定义注解,你不想写的话直接抛出去就行
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import com.iMagine.iMagine_common.exception.MyException;
import com.iMagine.iMagine_mapper.entity.User;
import com.iMagine.iMagine_pro.utils.RequestUtils;
import com.iMagine.iMagine_pro.utils.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* 限制每个ip对同一个接口的访问频率
*/
@Component
@Aspect
@Slf4j
@RestController
public class IpLimiterAspect {
@Autowired
private RequestUtils requestUtils;
// 创建本地缓存
private final Cache<String, RateLimiter> limiterCache = CacheBuilder.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES) // 缓存失效时间
.build();
@Around("@annotation(rateLimit)") // 只拦截有 @RateLimit 注解的方法
public Object around(ProceedingJoinPoint proceedingJoinPoint, RateLimit rateLimit) throws Throwable {
// 获取方法名和类名
Signature signature = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String methodName = proceedingJoinPoint.getTarget().getClass().getName() + "." + methodSignature.getName();
// 获取 @RateLimit 注解中的速率参数
double permitPerSecond = rateLimit.permitsPerSecond();
// 构造key,使用 IP + 接口名 作为缓存的 key
String key = requestUtils.getCurrentIp() + "->" + methodName;
// 获取对应的 RateLimiter
RateLimiter rateLimiter = limiterCache.get(key, () -> RateLimiter.create(permitPerSecond));
User user = TokenUtil.getUserByToken();
// rateLimiter.acquire(); // 阻塞直到获取到令牌
// 判断是否可以获取令牌,若不能获取,则触发限流,并发情况不保证,因为tryAcquire是非线程安全的
if (!rateLimiter.tryAcquire(200, TimeUnit.MILLISECONDS)) {
log.warn("User {} accessed {} too frequently. Rate limit exceeded!", user.getPhone(), methodName);
throw new MyException("User " + user.getPhone() + " accessed " + methodName + " too frequently. Please try again later.");
}
// 执行原方法
return proceedingJoinPoint.proceed();
}
}
五:涉及工具类方法 RequestUtils
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class RequestUtils {
@Autowired
private HttpServletRequest httpServletRequest;
public String getCurrentIp() {
return httpServletRequest.getHeader("X-Real-IP");
}
}
六:其他
rateLimiter.tryAcquire(200, TimeUnit.MILLISECONDS),tryAcquire()方法不是线程安全的,在并发情况下,可能会出现问题,我这里让其等待200 毫秒,可以明显避免此问题

浙公网安备 33010602011771号