服务治理-调用限流
# 一、需求背景
对于一个业务服务通常它的请求是稳定在一个可承载的安全体量范围,但是对于一些突发或者恶意攻击的访问,我们需要把这些访问拦截掉,避免系统被这样的流量拖垮。
对于保护的接口,我们的方式如下:
- 前端节流
- 后端拦截超过一定限制的请求
# 二、方案设计
- 使用自定义注解和切面技术,拦截需要被限流保护的接口。
- 拦截到方法后,通过
RateLimiter
给方法设定已配置好的调用限流处理。
# 三、实现方案
# 1、自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoLimit {
/**
* 限流许可量
*/
double permitsPerSecond() default 0D;
/**
* 失败结果 JSON
*/
String returnJson() default "";
}
# 2、服务实现
常量参数
public class LimitConstant {
public static Map<String, RateLimiter> rateLimiterMap = Collections.synchronizedMap(new HashMap<String, RateLimiter>());
}
后续接口实现,从这里根据key来获取到RateLimiter
对象,然后进行对应请求次数判断。
接口定义
public interface LimitService {
Object access(ProceedingJoinPoint point, Method method, DoLimit doLimit, Object[] args) throws Throwable;
}
接口实现
@Service
public class LimitServiceImpl implements LimitService {
@Override
public Object access(ProceedingJoinPoint point, Method method, DoLimit doLimit, Object[] args) throws Throwable {
if (0 == doLimit.permitsPerSecond()) {
return point.proceed();
}
// 获取类名
String clazzName = point.getTarget().getClass().getName();
// 获取方法名
String methodName = method.getName();
// key
String key = clazzName + "." + methodName;
// 如果获取到的对象为空,放入map中
if (null == LimitConstant.rateLimiterMap.get(key)) {
LimitConstant.rateLimiterMap.put(key, RateLimiter.create(doLimit.permitsPerSecond()));
}
// 根据key获取到rateLimiter对象
RateLimiter rateLimiter = LimitConstant.rateLimiterMap.get(key);
// 判断是否请求超出限制
if (rateLimiter.tryAcquire()) {
return point.proceed();
}
return JSON.parseObject(doLimit.returnJson(), method.getReturnType());
}
}
# 3、切面实现
@Aspect
@Component
public class LimitPoint {
@Autowired
private LimitServiceImpl limitService;
@Pointcut("@annotation(me.xu.limit.annotation.DoLimit)")
public void aopPoint() {
}
@Around("aopPoint() && @annotation(doLimit)")
public Object doRouter(ProceedingJoinPoint point, DoLimit doLimit) throws Throwable {
return limitService.access(point, getMethod(point), doLimit, point.getArgs());
}
private Method getMethod(JoinPoint point) throws NoSuchMethodException {
Signature sig = point.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
return point.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
}
}
# 四、案例测试
# 1、依赖映入
<dependency>
<groupId>me.xu</groupId>
<artifactId>spring-limit</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
# 2、控制层调用
@RestController
public class UserController {
private Logger logger = LoggerFactory.getLogger(UserController.class);
/**
* 测试:http://localhost:8080/api/queryUserInfo?userId=aaa
*/
@DoLimit(permitsPerSecond = 1, returnJson = "{\"code\":\"1111\",\"info\":\"调用方法超过最大次数,限流返回!\"}")
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
logger.info("查询用户信息,userId:{}", userId);
return new UserInfo("wxx:" + userId, 19, "地址");
}
}
@DoLimit
设置了错误返回结果和每秒请求数量
# 参考
上次更新: 2024/06/29, 15:13:44