放在Controller中用Jemter压测
文章插图
注:RateLimiter控制的是速率,Samephore控制的是并发量 。RateLimiter的原理就是令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑地分发,若请求超过permitsPerSecond则RateLimiter按照每秒 1/permitsPerSecond 的速率释放许可 。注意:RateLimiter适用于单体应用,且RateLimiter不保证公平性访问 。
使用上述方式使用RateLimiter的方式不够优雅,自定义注解+AOP的方式实现(适用于单体应用),详细见下面代码:
自定义注解:
import java.lang.annotation.*;/** * 自定义注解可以不包含属性,成为一个标识注解 */@Inherited@Documented@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface RateLimitAspect {}
自定义切面类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 {@Autowiredprivate 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();}}}
测试controllerimport 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,实现如下:
private LoadingCache < Long, AtomicLong > counter = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS).build(new CacheLoader < Long, AtomicLong > () {@Overridepublic AtomicLong load(Long seconds) throws Exception {return new AtomicLong(0);}});public static long permit = 50;public ResponseEntity getData() throws ExecutionException {//得到当前秒long currentSeconds = System.currentTimeMillis() / 1000;if (counter.get(currentSeconds).incrementAndGet() > permit) {return ResponseEntity.builder().code(404).msg("访问速率过快").build();}//业务处理}
到此应用级限流的一些方法就介绍完了 。假设将应用部署到多台机器,应用级限流方式只是单应用内的请求限流,不能进行全局限流 。因此我们需要分布式限流和接入层限流来解决这个问题 。分布式限流自定义注解+拦截器+redis实现限流 (单体和分布式均适用,全局限流)
自定义注解:
@Inherited@Documented@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface AccessLimit {int limit() default 5;int sec() default 5;}
拦截器:public class AccessLimitInterceptor implements HandlerInterceptor {@Autowiredprivate RedisTemplate<String, Integer> redisTemplate;//使用RedisTemplate操作redis@Overridepublic 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();}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Web 攻击越发复杂,如何保证云上业务高可用性的同时系统不被入侵?| 专家谈
- Nginx 为什么是高效服务器,架构设计是怎样的?
- 床上可以写字的小桌子一般买多高,一般写字桌子多高
- 八零后弃外企高薪销售 改行做茶膏
- 都江堰四措并举推进茶业升级
- 教练|职业足球运动员真是个高危职业,这五点和三个方面,都要考虑
- 海豚跟虎鲸哪个聪明 海豚和白鲸哪个智商更高
- 三件事,能看出你情商高低
- 如何搭建一支高效的互联网运营团队
- 淘宝商品竞争度高了好还是低了好 淘宝竞争度越大越好吗