zoukankan      html  css  js  c++  java
  • 高并发学习之使用RateLimiter实现令牌桶限流

        RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率。
    通常可应用于抢购限流防止冲垮系统;限制某接口、服务单位时间内的访问量,譬如一些第三方服务会对用户访问量进行限制;限制网速,单位时间内只允许上传下载多少字节等。

    guava的maven依赖

    <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
         <version>25.1-jre</version>
     </dependency>

        令牌桶的原理,有一个独立线程一直以一个固定的速率往桶中存放令牌,客户端去桶中获取令牌,获取到令牌,就可以访问,获取不到,说明请求过多,需要服务降级。

    示例代码:

    (1)请求限流注解

    /**
     * @创建人: hadoop
     * @创建时间: 2020/2/12
     * @描述:
     */
    @Target(value = {ElementType.METHOD,ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface LimitingAnnotation {
    
        /**
         * 获取令牌超时时间
         */
        long timeOut() default 0L;
    
        /**
         * 限流速率,每秒最多产生令牌数
         */
        long produceRate() default 1000L;
        
    }

    (2)请求限流切面

    /**
     * @创建人: hadoop
     * @创建时间: 2020/2/12
     * @描述: 服务限流降级切面,防止每秒请求数过多
     */
    @Component
    @Aspect
    @Slf4j
    public class LimitingAop {
    
        @Value("${request.limit}")
        private Integer limitValue ;
    
        @Autowired
        private HttpServletResponse response;
    
        // 令牌桶
        // limitValue : 表示每秒中生成limitValue个令牌存放在桶中
        @SuppressWarnings("UnstableApiUsage")
        private RateLimiter rateLimiter = RateLimiter.create(limitValue);
    
        /**
         * 限流切点
         */
        @Pointcut("@annotation(com.service.bussiness.aop.LimitingAnnotation)")
        public void limitingPointCut() {
        }
    
        @SuppressWarnings({"rawtypes", "UnstableApiUsage"})
        @Around("limitingPointCut()")
        public Object controllerAround( ProceedingJoinPoint point ) {
            String description = CommonConst.EMPTYSTRING;
            Method method = null;
            String methodName = point.getSignature().getName();
            Class[] paramTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
            try {
                method = point.getTarget().getClass().getMethod(methodName, paramTypes);
                if ( !method.isAnnotationPresent(LimitingAnnotation.class) ) {
                    return null;
                }
            } catch ( NoSuchMethodException e ) {
                log.error("限流切面出现异常,异常原因是: " + e.getMessage());
                throw new CustomException(
                        Integer.parseInt(CustomExceptionType.SYSTEM_ERROR.getCode()) ,
                        e.getMessage(),
                        "限流切面出现异常",
                        "请求的目标方法不存在"
                );
            }
            LimitingAnnotation limitingAnnotation =
                    method.getAnnotation(LimitingAnnotation.class);
            if ( null == limitingAnnotation ){
                return null;
            }
            long timeOut = limitingAnnotation.timeOut();
            long produceRate = limitingAnnotation.produceRate();
            // 设置限流速率,每秒产生多少令牌
            rateLimiter.setRate(produceRate);
            // 每次发送请求,在设定ms内没有获取到令牌,则对服务进行降级处理
            boolean acquire = rateLimiter.tryAcquire(timeOut, TimeUnit.MILLISECONDS);
            if ( !acquire ){
                getErrorMsg();
                return null;
            }
            try {
                return point.proceed();
            } catch (Throwable throwable) {
                log.error(methodName+"请求出现异常,异常原因是: " + throwable.getMessage());
                throwable.printStackTrace();
            }
            return null;
        }
    
        /**
         * 向客户端输出服务降级信息
         *
         */
        public void getErrorMsg(){
           response.setHeader("Content-Type","application/json;charset=UTF-8");
            PrintWriter printWriter = null;
            try {
                printWriter = response.getWriter();
                Map<String,Object> resultMap = new HashMap<>();
                resultMap.put("msg","前方任务过多,请稍后再试");
                printWriter.write(JSON.toJSONString(resultMap));
                printWriter.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }

    (3) 请求服务

        @RequestMapping(value = "/search", method = RequestMethod.POST, consumes = "application/json")
        @ResponseBody
        @LimitingAnnotation( timeOut = 500, produceRate = 1000 )
        public ResponseEntity<String> search( @RequestBody String param ) 
    {
    return this.searchService.searchBillInfo( xEncryption , params ); }

    参考:

    https://www.cnblogs.com/pickKnow/p/11252519.html

  • 相关阅读:
    解决ListView异步加载数据之后不能点击的问题
    android点击实现图片放大缩小 java技术博客
    关于 数据文件自增长 的一点理解
    RAC 实例不能启动 ORA1589 signalled during ALTER DATABASE OPEN
    Linux 超级用户的权利
    RAC 实例 迁移到 单实例 使用导出导入
    Shell 基本语法
    Linux 开机引导与关机过程
    RAC 实例不能启动 ORA1589 signalled during ALTER DATABASE OPEN
    Oracle RAC + Data Guard 环境搭建
  • 原文地址:https://www.cnblogs.com/sunfie/p/12298892.html
Copyright © 2011-2022 走看看