这里简单说一下用Redis实现令牌桶算法的方法。
用户每一次访问请求的时候,从redis中获取一个令牌,如果拿到令牌了,就说明没有超出限制,就通过这个请求,相反如果拿不到令牌就拦截这个请求或者返回错误信息,下面是代码实现部分。
1.定义Redis限流规则类
public final class RRateLimiter {
private String rediskey = "limit";
private Long all_conut = 20L;//令牌的总数量
private Long conut = 10L;//生产的令牌数量
private Long time = 5L;//产生的令牌的时间间隔
private TimeUnit timetype = TimeUnit.SECONDS;
private ListOperations<String,Object> redis;
private void start() {
new Thread(() -> {
try {
while (true) {
timetype.sleep(time);
if(redis.size(rediskey)<all_conut)
LongStream.range(0,conut).forEach(x->redis.rightPush(rediskey,UUID.randomUUID().toString().replace("-","")));
}
} catch(Exception e){}
}).start();
}
public RRateLimiter(ListOperations<String, Object> list, String rediskey, Long conut, Long time,
TimeUnit timetype) {
this.rediskey = rediskey;
this.conut = conut;
this.time = time;
this.timetype = timetype;
this.redis = list;
start();
}
public RRateLimiter(ListOperations<String, Object> redis) {
this.redis = redis;
start();
}
public synchronized boolean tryAcquire() {
return redis.leftPop(rediskey)!=null?true:false;
}
}
2.自定义注解拦截器
@Component
public class LimtiInterceptor implements HandlerInterceptor,HandlerMethodArgumentResolver{
@Autowired
private RRateLimiter rateLimiter;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if(!(handler instanceof HandlerMethod))return true;
HandlerMethod handlers = (HandlerMethod)handler;
RequestLimit requestLimit = handlers.getMethodAnnotation(RequestLimit.class);
if(requestLimit==null)requestLimit = handlers.getBeanType().getAnnotation(RequestLimit.class);
if(requestLimit==null)return true;
if(rateLimiter.tryAcquire()){
return true;
}else{
if(requestLimit.requesttype()==CODE){
response.setStatus(requestLimit.status());
}else if(requestLimit.requesttype()==MESSAGE){
response.setContentType("application/json;charset=UTF-8");
try(PrintWriter printWriter = response.getWriter()){
printWriter.write(JSONObject.toJSONString(ResultUtil.resultErr(requestLimit.message())));
}
}
}
return false;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Token.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// if(parameter.hasParameterAnnotation(Token.class) && authentication!=null && authentication.getPrincipal()!=null){
// JwtUser jwtUser = (JwtUser)authentication.getPrincipal();
// if(parameter.getParameterType()==JwtUser.class)
// return jwtUser;
// }
return authentication!=null?authentication.getPrincipal():null;
}
}
3.自定义注解类,用于注释要限流的方法和类,如果放在类上面对类中所有接口都有限流作用
@Documented
@Inherited
@Target({METHOD,TYPE})
@Retention(RUNTIME)
public @interface RequestLimit {
enum REQUEST_TYPE{CODE,MESSAGE}
String message() default "访问数率过快";
int status() default 426;
int count() default 10;
TimeUnit type() default TimeUnit.SECONDS;
REQUEST_TYPE requesttype() default REQUEST_TYPE.CODE;
}
4.自定Controller类测试自定义注解限流
@Api("用户Controller")
@CacheConfig(cacheNames = "users")
@RestController
@RequestMapping("user")
public class UserController extends BaseController{
@ApiOperation(value = "查看当前登陆的用户信息",httpMethod = "GET",response =ResultUtil.class)
@RequestLimit(requesttype = REQUEST_TYPE.MESSAGE)
@GetMapping(value="/getuser")
public ResultUtil<?> getuser(@Token UserMsg object){
return ResultUtil.resultOK(object);
}
}
5.打开浏览器访问http://localhost:8083/user/getuser
正常情况下访问接口返回当前的用户登陆信息
快速刷新浏览器会出现以下错误信息或者直接返回状态码
?
?
|