Skip to content

Latest commit

 

History

History
358 lines (280 loc) · 8.61 KB

File metadata and controls

358 lines (280 loc) · 8.61 KB

springboot整合 guava 限流

案例

//create方法传入的是每秒生成令牌的个数
RateLimiter rateLimiter=RateLimiter.create(1);
        for(int i=0;i< 5;i++){
        //acquire方法传入的是需要的令牌个数,当令牌不足时会进行等待,该方法返回的是等待的时间
        double waitTime=rateLimiter.acquire(1);
        System.out.println(System.currentTimeMillis()/1000+" , "+waitTime);
        }

第一步:spring-boot-ratelimit-guava

<dependencies>
    <!-- 工具类大全 -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
    </dependency>
    <!-- google公司java工具包 -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
    </dependency>
    <!--切面-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

第二步:创建application.yml文件

server:
  port: 8080

第三步: 注解

package com.example.annoation;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * @Author: wgs
 * @Date 2022/5/22 09:05
 * @Classname RateLimit
 * @Description
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomRateLimiter {
    int NOT_LIMITED = 0;

    /**
     * qps
     */
    @AliasFor("qps") double value() default NOT_LIMITED;

    /**
     * qps
     */
    @AliasFor("value") double qps() default NOT_LIMITED;

    /**
     * 超时时长
     */
    int timeout() default 0;

    /**
     * 超时时间单位
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

第四步:创建RateLimiterAspect

package com.example.aspect;

import com.example.annoation.CustomRateLimiter;


/**
 * @Author: wgs
 * @Date 2022/5/22 14:04
 * @Classname RateLimiterAspect
 * @Description
 */
@Slf4j
@Aspect
@Component
public class RateLimiterAspect {
    private static final ConcurrentHashMap<String, RateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap();

    /**
     * 切点
     *
     * @param customRateLimiter
     */
    @Pointcut(value = "@annotation(customRateLimiter)")
    public void pointcut(CustomRateLimiter customRateLimiter) {
    }

    /**
     * 环绕操作
     *
     * @param point 切入点
     * @return 原方法返回值
     * @throws Throwable 异常信息
     */
    @Around(value = "pointcut(customRateLimiter)")
    public Object aroundLog(ProceedingJoinPoint point, CustomRateLimiter customRateLimiter) throws Throwable {

        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        System.out.println(customRateLimiter);
        System.out.println(customRateLimiter.qps());


        if (customRateLimiter != null && customRateLimiter.qps() > CustomRateLimiter.NOT_LIMITED) {
            double qps = customRateLimiter.qps();
            // TODO 这个key可以根据具体需求配置,例如根据ip限制,或用户
            if (RATE_LIMITER_CACHE.get(method.getName()) == null) {
                // 初始化 QPS
                RATE_LIMITER_CACHE.put(method.getName(), RateLimiter.create(qps));
            }

            log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate());
            // 尝试获取令牌
            if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(customRateLimiter.timeout(), customRateLimiter.timeUnit())) {
                throw new RateException(CommonEnum.ResultEnum.RATE_ERROR);
            }
        }
        return point.proceed();
    }
}

第五步:创建枚举

package com.example.enums;

import lombok.Getter;

/**
 * @Author: wgs
 * @Date 2022/5/22 14:38
 * @Classname CommonEnum
 * @Description
 */
public interface CommonEnum {
    @Getter
    enum ResultEnum implements Ienum<Integer> {

        /**
         * 成功
         */
        SUCCESS(0, "成功"),
        /**
         * 参数不正确
         */
        PARAM_ERROR(1, "参数不正确"),

        RATE_ERROR(10, "限流中"),
        ;
        private Integer code;
        private String message;

        ResultEnum(Integer code, String message) {
            this.code = code;
            this.message = message;
        }

        @Override
        public boolean is(Integer code) {
            return get().equals(code);
        }

        @Override
        public Integer get() {
            return code;
        }
    }

    interface Ienum<T> {
        boolean is(T t);

        T get();
    }
}

第六步:创建异常

package com.example.exception;

import com.example.enums.CommonEnum;
import lombok.Getter;

/**
 * @Author: wgs
 * @Date 2022/5/22 14:45
 * @Classname RateException
 * @Description
 */
@Getter
public class RateException extends RuntimeException {
    private Integer code;

    public RateException(CommonEnum.ResultEnum resultEnum) {
        super(resultEnum.getMessage());
        this.code = resultEnum.getCode();
    }

    public RateException(Integer code, String msg) {
        super(msg);
        this.code = code;
    }
}

第七步:创建异常拦截

package com.example.handle;

import cn.hutool.core.lang.Dict;
import com.example.exception.RateException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @Author: wgs
 * @Date 2022/5/22 14:50
 * @Classname RateExcetionHandler
 * @Description
 */
@RestControllerAdvice
public class RateExceptionHandler {
    @ExceptionHandler(RateException.class)
    public Dict rateException(RateException rateException) {
        Dict dict = new Dict();
        dict.put("code", rateException.getCode());
        dict.put("msg", rateException.getMessage());
        return dict;

    }
}

第八步:创建测试

package com.example.controller;

import cn.hutool.core.lang.Dict;
import com.example.annoation.CustomRateLimiter;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @Author: wgs
 * @Date 2022/5/22 09:11
 * @Classname IndexController
 * @Description
 */
@RestController
@RequestMapping("/limit")
@Slf4j
public class IndexController {
    // 创建令牌桶每秒一个
    private RateLimiter rl = RateLimiter.create(1);

    @GetMapping("/test1")
    public Dict rate() {
        //获取令牌,如果没有则等待至超时,本代码超时时间为0,立刻返回错误信息
        boolean flag = rl.tryAcquire(0, TimeUnit.SECONDS);
        Dict dict = new Dict();
        if (!flag) {
            dict.put("code", 777);
            dict.put("msg", "限流");
            return dict;
        }

        dict.put("code", 0);
        dict.put("msg", "成功");
        return dict;
    }

    @CustomRateLimiter(qps = 1, value = 1.0, timeout = 300)
    @GetMapping("/test2")
    public Dict test1() {
        return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~");
    }


    @CustomRateLimiter(value = 2.0, timeout = 300)
    @GetMapping("/test3")
    public Dict test3() {
        return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~");
    }
}

第九步:创建启动类SpringBootUiKnife4jApplication

@SpringBootApplication
public class SpringBootRatelimitGuavaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRatelimitGuavaApplication.class, args);
    }

}

执行启动类main方法启动项目,访问地址:http://localhost:8008/limit/test1

图示 image.png

通俗的理解就是,有一个固定大小的水桶,水龙头一直按照一定的频率往里面滴水。水满了,就不滴了。客户端每次进行请求之前,都要先尝试从水桶里面起码取出“一滴水”,才能处理业务。因为桶的大小固定,水龙头滴水频率固定。从而也就保证了数据接口的访问流量。