Skip to main content
  1. Posts/

Springboot中使用Redisson的RRateLimiter

·253 words·2 mins
Blogs Tutorials Redis Springboot Middleware
Gwen0x4c3
Author
Gwen0x4c3
java n go is my mother tongue
Table of Contents

首先引入依赖

    <dependency>
         <groupId>org.redisson</groupId>
         <artifactId>redisson</artifactId>
         <version>3.16.8</version>
    </dependency>

在controller或service里加上这俩成员变量

    private RedissonClient client = Redisson.create();
    private RRateLimiter limiter = client.getRateLimiter("limiter");
                               //他的参数是limiter的名字 自己可以改
    

我选择在构造函数里对limiter进行设置

    IndexController() {
        this.limiter.trySetRate(RateType.OVERALL, 1, 5, RateIntervalUnit.SECONDS);
                                    //此处参数1,5代表每5秒放入一个令牌
    }

使用方式一 limiter.acquire(long numPermits)

    @RequestMapping("rlimiter")
    public String redissonLimiter() {                     
        limiter.acquire(1); //此处参数1代表你要取走的令牌数量
        return "OK";
    }

这种方法在获取到令牌前会一直等待,访问页面先显示OK,点击刷新5秒后才又显示OK。

**使用方式二 limiter.tryAcquire(long numPermits)**

        @RequestMapping("rlimiter")
        public String redissonLimiter() {
            boolean flag = limiter.tryAcquire(1);
            if (flag) {
                System.out.println("开始处理业务");
                return "ok";
            } else {
                return "业务繁忙请稍后";
            }
        }

这种方法可以通过flag查看你有没有成功获取到令牌,假设令牌被别人占了你没有获取到,他就会立刻返回不再等了。首次访问页面先显示OK,再一刷新就立刻显示业务繁忙了。

方式三 limiter.tryAcquire(long timeout, TimeUnit unit)

        @RequestMapping("rlimiter")
        public String redissonLimiter() {
            boolean flag = limiter.tryAcquire(1500, TimeUnit.MILLISECONDS);
            if (flag) {
                System.out.println("开始处理业务");
                return "ok";
            } else {
                return "业务繁忙请稍后";
            }
        }

这种方式会在你请求取令牌时等待timeout时长,也就是1500毫秒,如果在这段时间内获取到了令牌,就返回ok,否则返回业务繁忙。

配合切面对service限流
#

引入依赖

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

创建RateLimit注解

    @Target(value = ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RateLimit {
    
        //一次取多少个令牌
        long permits();
    
        //获取令牌的超时时间,单位为毫秒
        long timeout() default 0;
    }

创建RateLimiterAspect

    @Aspect
    @Component
    public class RateLimiterAspect {
    
        RedissonClient client = Redisson.create();
    
        RRateLimiter limiter = client.getRateLimiter("limiter");
    
        RateLimiterAspect() {
                                        //直接规定每秒只有10个令牌
            limiter.setRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);
        }
    
        @Pointcut("execution(public * com.li.redislock.service.*.*(..))")
        public void pointcut() {
        }
    
        @Around("pointcut()")
        public Object process(ProceedingJoinPoint point) throws Throwable {
    
            MethodSignature signature = (MethodSignature) point.getSignature();
    
            //反射看方法上有没有@RateLimit注解
            RateLimit rateLimit = signature.getMethod().getAnnotation(RateLimit.class);
            if (rateLimit == null) {
                return point.proceed();
            }
    
            long permits = rateLimit.permits();
            long timeout = rateLimit.timeout();
    
            boolean res = limiter.tryAcquire(permits, timeout, TimeUnit.MILLISECONDS);
            if (!res) {
                throw new RuntimeException("业务繁忙等会再来吧");
            }
            return point.proceed();
        }
    }

service permits根据不同业务而设定不同数目

        @RateLimit(permits = 7, timeout = 200)
        public void seckill(String uid, String gid) {
            System.out.println("正在为用户" + uid + "抢购商品" + gid);
        }

controller

        @RequestMapping("seckill")
        public String seckill() {
            try {
                seckillService.seckill("u-101", "g-102");
            } catch (Exception e) {
                return "ERROR: " + e.getMessage();
            }
            return "抢购成功";
        }

打开两个页面,都输入localhost:8001/seckill,按刷新后立刻到另一个页面也按刷新