• 接口访问频率限流


    快速开始:四、代码实现 -> 6.配置RateLimit注解,使限流生效

    一、限流场景

    • 淘宝秒杀活动,限1小时200件商品

    • 一个用户、一个手机号一天只能获取5次验证码

    • 限制某个接口一分钟最多只能访问500次

    二、处理方式

    • 抛异常

    • 排队等待

    • 服务降级

    三、令牌桶算法流程

    四、代码实现

    1.频率限制注解类

    /**频率限制注解
     * @author zhoujialin
     * @time 2022/1/20 16:22
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RateLimit {
    
    	/**
    	 * 限制对象键
    	 * @param
    	 * @return java.lang.String
    	 */
    	String key() default "";
    
    	/**
    	 * 一个周期(默认为1秒)生成令牌数
    	 * @param
    	 * @return long
    	 */
    	long rate();
    
    	/**
    	 * 周期(目前支持SECONDS、MINUTES、HOURS、DAYS,默认SECONDS,如果指定了其他类型将抛出异常)
    	 * @param
    	 * @return java.util.concurrent.TimeUnit
    	 */
    	TimeUnit cycle() default TimeUnit.SECONDS;
    
    	/**
    	 * 获取令牌数
    	 * @param
    	 * @return long
    	 */
    	long requested() default 1;
    
    	/**
    	 * 提示信息
    	 * @param
    	 * @return java.lang.String
    	 */
    	String msg() default "";
    }
    

    2.频率限制配置类

    /**
     * 频率限制配置
     * @author: zhoujialin
     * @time: 2022/2/17 13:02
     */
    public class RateLimiterConfig {
        /**
         * 限制对象键
         */
        private String key;
        /**
         * 每周期生成令牌数
         */
        private long rate;
        /**
         * 周期
         */
        private TimeUnit cycle;
    
        /**
         * 申请令牌数
         */
        private long requested;
    
        /**
         * 提示信息
         */
        private String msg;
    
        public static Builder builder(String key, long rate) {
            return new Builder(key, rate);
        }
    
        private RateLimiterConfig(Builder builder) {
            this.key = builder.key;
            this.rate = builder.rate;
            this.cycle = builder.cycle;
            this.requested = builder.requested;
            this.msg = builder.msg;
        }
    
        public String getKey() {
            return key;
        }
    
        public long getRate() {
            return rate;
        }
    
        public TimeUnit getCycle() {
            return cycle;
        }
    
        public long getRequested() {
            return requested;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public static class Builder {
            /**
             * 限制对象键
             */
            private String key;
            /**
             * 每周期生成令牌数
             */
            private long rate;
            /**
             * 周期
             */
            private TimeUnit cycle;
            /**
             * 申请令牌数
             */
            private long requested;
    
            private String msg = "RateLimiter does not permit further calls";
    
            private static final Set<TimeUnit> cycles = new HashSet<>();
    
            static {
                cycles.add(SECONDS);
                cycles.add(MINUTES);
                cycles.add(HOURS);
                cycles.add(DAYS);
            }
    
            public Builder(String key, long rate) {
                this.key = key;
                this.rate = rate;
            }
    
            public Builder cycle(TimeUnit cycle) {
                this.cycle = cycle;
                return this;
            }
    
            public Builder requested(long requested) {
                this.requested = requested;
                return this;
            }
    
            public Builder msg(String msg) {
                if(!Strings.isEmpty(msg)) {
                    this.msg = msg;
                }
                return this;
            }
    
            public RateLimiterConfig build() {
                if(key == null) {
                    throw new RateLimitAcquireException("key cannot be null");
                }
                if(rate < 1) {
                    throw new RateLimitAcquireException("rate must be a positive");
                }
                if(requested < 1) {
                    throw new RateLimitAcquireException("requested must be a positive");
                }
                if(!cycles.contains(cycle)) {
                    throw new RateLimitAcquireException("the value of cycle must be SECONDS、MINUTES、HOURS or DAYS");
                }
                return new RateLimiterConfig(this);
            }
        }
    }
    

    3.频率限制接口

    /**
     * 频率限制接口
     * @author zhoujialin
     * @time 2022/2/17 11:09
     */
    @FunctionalInterface
    public interface RateLimiter {
    
        /**
         * 获得许可
         * @param config 频率限制配置
         * @return boolean
         */
        boolean acquirePermission(RateLimiterConfig config);
    
        /**
         * 等待获得许可
         * @param config 频率限制配置
         * @return void
         * @throws RateLimitAcquireException
         */
        default void waitForPermission(RateLimiterConfig config) throws RateLimitAcquireException {
            boolean permission = acquirePermission(config);
            if (!permission) {
                throw new RateLimitAcquireException(config.getMsg());
            }
        }
    }
    

    4.默认频率限制器

    /**
     * 默认频率限制器
     *
     * @author: zhoujialin
     * @time: 2022/2/17 11:14
     */
    public class DefaultRateLimiter implements RateLimiter {
    
        /**
         * 上次刷新令牌的时间(秒)
         */
        private static Map<String, Long> lastRefreshedMap = new HashMap<>();
    
        /**
         * 上次剩余令牌数
         */
        private static Map<String, Long> lastTokensMap = new HashMap<>();
    
        /**
         * 基于本地令牌桶的实现
         * @param config 频率限制配置
         * @return boolean
         */
        @Override
        public boolean acquirePermission(RateLimiterConfig config) {
            String key = config.getKey();
            long rate = config.getRate();
            long capacity = rate;
            TimeUnit cycle = config.getCycle();
            long requested = config.getRequested();
            //细节key是String类型,值相等时,不一定是同个对象,使用key.intern()是为了把值相等的key当同一个对象加锁
            synchronized (key.intern()) {
                //获取系统时间
                long now = getNow(cycle);
                //获取上次刷新令牌的时间(首次为0)
                long lastRefreshed = lastRefreshedMap.getOrDefault(key, 0L);
                //距上次刷新令牌的时间差
                long delta = now - lastRefreshed;
                //超时时间为令牌生成周期的2倍
                if(lastRefreshed > 0 && delta < 2) {
                    //上次生成的令牌未过期,根据时间差生成的新的令牌数,重新计算桶中剩余令牌数
                    capacity = Math.min(capacity, lastTokensMap.getOrDefault(key, 0L) + delta * rate);
                }
                //桶中剩余令牌数大于将要获取令牌数时允许接口的访问,否则拒绝
                boolean allowed = capacity >= requested;
                //更新刷新令牌的时间
                lastRefreshedMap.put(key, now);
                if(!allowed) {
                    return false;
                }
                //更新桶中的令牌数
                lastTokensMap.put(key, capacity - requested);
                return true;
            }
        }
    
        /**
         * 按周期单位换算当前时间
         * @param cycle
         * @return long
         */
        private long getNow(TimeUnit cycle) {
            if(cycle == MINUTES) {
                return (long) Math.floor(Instant.now().getEpochSecond() / 60);
            }
            if(cycle == HOURS) {
                return (long) Math.floor(Instant.now().getEpochSecond() / 3600);
            }
            if(cycle == DAYS) {
                return (long) Math.floor(Instant.now().getEpochSecond() / 86400);
            }
            return Instant.now().getEpochSecond();
        }
    }
    

    5.请求频率限制切面

    /**
     * 请求频率限制切面
     * @author zhoujialin
     * @time 2022/2/16 16:54
     */
    @Aspect
    @Configuration
    public class RateLimitAspect {
    
        @Around("@annotation(rateLimit)")
        public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
            String key = rateLimit.key();
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            //key的前缀是"类名.方法"
            String prefix = point.getTarget().getClass().getSimpleName() + "." + method.getName();
            if(key.startsWith("#")) {
            	//如果key的值以"#"开头,表明值是spring的Expression表达式,需要用以下方法解析
                //如key="#user.name",表示被代理方法的参数user的属性name的值
                key = "." + ExpressionParserUtils.getValue(point, key);
            }
            //从注解中取出相关属性,用构造者模式初始化RateLimiterConfig,并调用获取令牌方法,如果调用失败,将抛出异常,成功则继续执行业务方法 
            rateLimiter().waitForPermission(RateLimiterConfig
                    .builder(prefix + key, rateLimit.rate())
                    .cycle(rateLimit.cycle())
                    .requested(rateLimit.requested())
                    .msg(rateLimit.msg())
                    .build());
            return point.proceed();
        }
    
        @Bean
        public RateLimiter rateLimiter() {
            return new DefaultRateLimiter();
        }
    }
    

    6.配置RateLimit注解,使限流生效

    @RestController
    @Api(tags = "测试")
    @RequestMapping("/test")
    public class TestController {
    ​
        @GetMapping("rateLimit")
        @ApiOperation("按接口访问频率限流(基于令牌桶算法)")
        @RateLimit(key = "#user.username", rate = 10)
        public boolean rateLimit(User user) {
            //do something
            //处理方式
            //1.默认抛异常
            //2.如果想以排队等待或服务降级的方式处理,可以将注解添加在下游的service上,这里捕获RateLimitAcquireException后,发mq或返回降级后的展示数据
            return true;
        }
    }
    

    五、测试

    1.限流配置:每秒10个请求

    2.操作方式:jmeter中每隔5ms发送1个请求,总共发送100个请求

    3.请求时间段15:02:56.083~15:02:58.111

    a. 56s期间总共32个请求,前10个成功,其他全部失败

    b. 57s期间总共61个请求,前10个成功,其他全部失败


    c. 58s期间总共7个请求,全部成功

    4.聚合报告,异常73%,表明只有27个请求通过了,达到限流的目的

  • 相关阅读:
    (原)ubuntu16在torch中使用caffe训练好的模型
    (原)Ubuntu16中卸载并重新安装google的Protocol Buffers
    (原)lua提示cannot load incompatible bytecode
    (原)ubuntu上安装nvidia及torch的nccl
    Ubuntu修改grub菜单引导选项和等待时间
    Servelet 简介
    JAVA JUC 线程池
    JAVA JUC synchronized 锁的理解
    JAVA JUC 读写锁
    JAVA JUC 线程按顺序执行
  • 原文地址:https://www.cnblogs.com/huanongying/p/16116668.html
Copyright © 2020-2023  润新知