访问量一大服务器就挂?那是你不懂限流( 三 )


使用上述方式使用RateLimiter的方式不够优雅,自定义注解+AOP的方式实现(适用于单体应用),详细见下面代码:
自定义注解:

访问量一大服务器就挂?那是你不懂限流

文章插图
 
自定义切面类
import com.google.common.util.concurrent.RateLimiter;import com.test.cn.springbootdemo.util.ResultUtil;import net.sf.json.JSONObject;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component; import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletResponse;import java.io.IOException; @Component@Scope@Aspectpublic class RateLimitAop {@Autowired private HttpServletResponse response;private RateLimiter rateLimiter = RateLimiter.create(5.0); //比如说,我这里设置"并发数"为5@Pointcut("@annotation(com.test.cn.springbootdemo.aspect.RateLimitAspect)") public void serviceLimit() {}@Around("serviceLimit()") public Object around(ProceedingJoinPoint joinPoint) { Boolean flag = rateLimiter.tryAcquire(); Object obj = null; try { if (flag) { obj = joinPoint.proceed(); }else{ String result = JSONObject.fromObject(ResultUtil.success1(100, "failure")).toString(); output(response, result); } } catch (Throwable e) { e.printStackTrace(); } System.out.println("flag=" + flag + ",obj=" + obj); return obj; }public void output(HttpServletResponse response, String msg) throws IOException { response.setContentType("Application/json;charset=UTF-8"); ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); outputStream.write(msg.getBytes("UTF-8")); } catch (IOException e) { e.printStackTrace(); } finally { outputStream.flush(); outputStream.close(); } }}测试controller
import com.test.cn.springbootdemo.aspect.RateLimitAspect;import com.test.cn.springbootdemo.util.ResultUtil;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class TestController {@ResponseBody @RateLimitAspect @RequestMapping("/test") public String test(){ return ResultUtil.success1(1001, "success").toString(); }压测结果:
访问量一大服务器就挂?那是你不懂限流

文章插图
 
三、控制单位时间窗口内请求数
某些场景下,我们想限制某个接口或服务 每秒/每分钟/每天 的请求次数或调用次数 。例如限制服务每秒的调用次数为50,实现如下:
访问量一大服务器就挂?那是你不懂限流

文章插图
【访问量一大服务器就挂?那是你不懂限流】 
到此应用级限流的一些方法就介绍完了 。假设将应用部署到多台机器,应用级限流方式只是单应用内的请求限流,不能进行全局限流 。因此我们需要分布式限流和接入层限流来解决这个问题 。
分布式限流
自定义注解+拦截器+redis实现限流 (单体和分布式均适用,全局限流)
自定义注解:
访问量一大服务器就挂?那是你不懂限流

文章插图
 
拦截器:
public class AccessLimitInterceptor implements HandlerInterceptor {@Autowired private RedisTemplate<String, Integer> redisTemplate; //使用RedisTemplate操作redis@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); if (!method.isAnnotationPresent(AccessLimit.class)) { return true; } AccessLimit accessLimit = method.getAnnotation(AccessLimit.class); if (accessLimit == null) { return true; } int limit = accessLimit.limit(); int sec = accessLimit.sec(); String key = IPUtil.getIpAddr(request) + request.getRequestURI(); Integer maxLimit = redisTemplate.opsForValue().get(key); if (maxLimit == null) { redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS); //set时一定要加过期时间 } else if (maxLimit < limit) { redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS); } else { output(response, "请求太频繁!"); return false; } } return true; }public void output(HttpServletResponse response, String msg) throws IOException { response.setContentType("application/json;charset=UTF-8"); ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); outputStream.write(msg.getBytes("UTF-8")); } catch (IOException e) { e.printStackTrace(); } finally { outputStream.flush(); outputStream.close(); } }@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}}controller:@Controller@RequestMapping("/activity")public class AopController { @ResponseBody @RequestMapping("/seckill") @AccessLimit(limit = 4,sec = 10) //加上自定义注解即可 public String test (HttpServletRequest request,@RequestParam(value = https://www.isolves.com/it/wlyx/fwq/2019-11-04/"username",required = false) String userName){ //TODO somethings…… return "hello world !"; }}


推荐阅读