• SpringBoot基于guava集成令牌桶算法


    一、什么是令牌桶

    1、令牌桶

    有一个固定大小的水桶,在水桶的水满之前,水龙头按照一定的频率恒定往里面滴水。水满之后就不滴了。
    每次处理业务请求之前,都要先尝试从水桶里取出一滴水,才能处理业务。如果取不到,就不处理业务。
    如此以来,桶的大小固定,水龙头滴水频率恒定,那么总的访问量也就恒定。从而也就保证了数据接口的访问流量,从而达到限流的目的。

    2、功能图

    在这里插入图片描述

    二、Guava

    1、简单介绍

    guava是来自谷歌的Java核心类库。包含了大量Java工具类、集合类型、不可变集合、并发、I/O、hashing、缓存、原型、字符串等的通用功能。

    2、pom引入

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>29.0-jre</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、限速器

    创建一个限速器,预热1秒,产生5个令牌
    permitsPerSecond:每秒产生多少个令牌
    warmupPeriod:预热时间
    unit:预热时间warmupPeriod的单位

    RateLimiter rateLimiter = RateLimiter.create(5, 1, TimeUnit.SECONDS);
    
    • 1

    尝试获取1个令牌,如果没有,会阻塞当前线程。直到获取成功返回。
    返回值:阻塞的秒数。

    double waitSeconds = rateLimiter.acquire();
    
    • 1

    尝试获取1个令牌,不会阻塞当前线程。
    返回值:是否获取成功。

    boolean success = rateLimiter.tryAcquire();
    
    • 1

    以上就是主要基础代码,创建限速器(生成水桶),(阻塞/不阻塞)获取令牌。

    4、浅析重载方法

    阻塞线程获取令牌

    默认获取一个令牌。
    public double acquire();
    获取令牌数量手动传参,参数:permitspublic double acquire(int permits);
    
    • 1
    • 2
    • 3
    • 4

    非阻塞线程获取令牌

    参数说明如下
    permits:获取令牌数量
    timeout:等待时长
    unit:时间单位
    
    默认获取1个令牌,等待0秒,时间单位为微秒,其余同理
    public boolean tryAcquire();
    public boolean tryAcquire(int permits);
    public boolean tryAcquire(Duration timeout);
    public boolean tryAcquire(long timeout, TimeUnit unit);
    public boolean tryAcquire(int permits, Duration timeout);
    public boolean tryAcquire(int permits, long timeout, TimeUnit unit);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    三、系统应用

    1、单个接口应用

    单接口测试如下

    package com;
    
    import com.google.common.util.concurrent.RateLimiter;
    import java.util.concurrent.TimeUnit;
    
    public class Test {
    
        private final RateLimiter rateLimiter;
    
        public Test(RateLimiter rateLimiter) {
            this.rateLimiter = rateLimiter;
        }
    
        public static void main(String[] args) {
            Test test = new Test(RateLimiter.create(1, 1, TimeUnit.SECONDS));
            for(int i = 0; i < 10; i++){
                rateLimiter(test.rateLimiter);
            }
        }
    
        private static void rateLimiter(RateLimiter rateLimiter){
            if(rateLimiter.tryAcquire()) {
                doBusiness();
                return;
            }
            System.out.println("服务器忙,请稍后重试!");
        }
    
        private static void doBusiness(){
            System.out.println("可以通行,处理业务");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行结果
    在这里插入图片描述

    2、多个接口应用

    单接口可以处理之后,那么就需要用到一批接口上,做成配置的形式。
    由于限流的本质就是对请求的拦截,那么自然而然就能想到使用拦截器处理。
    限流处理的本质,类似于java中的切片,所以除了拦截器,也可以想到的是AOP,可以采用监听,使用注解等等方式。

    那么就用拦截器先搞一搞吧,建一个拦截器如下

    package com;
    
    import com.alibaba.fastjson.JSON;
    import com.Result;
    import com.google.common.util.concurrent.RateLimiter;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.nio.charset.StandardCharsets;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 令牌桶算法拦截器
     */
    @Component
    @Slf4j
    public class RateLimiterInterceptor extends HandlerInterceptorAdapter {
    
        private final RateLimiter rateLimiter;
    
        /**
         * 通过构造函数默认初始化限速器,创建一个限速器,预热1秒,产生10个令牌
         * permitsPerSecond:每秒产生多少个令牌
         * warmupPeriod:预热时间
         * unit:预热时间warmupPeriod的单位
         */
        public RateLimiterInterceptor() {
            super();
            this.rateLimiter = RateLimiter.create(5, 1, TimeUnit.SECONDS);
        }
    
        /**
         * 通过构造函数手动初始化限速器
         */
        public RateLimiterInterceptor(RateLimiter rateLimiter) {
            super();
            this.rateLimiter = rateLimiter;
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if(this.rateLimiter.tryAcquire()) {
                //成功获取到令牌
                return true;
            }
            //获取失败,直接响应“错误信息”
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.getWriter().write(JSON.toJSONString(Result.error(400, "服务器繁忙,请稍后重试!")));
            return false;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    在WebMvcConfiguration中配置

    package com.config;
    
    @Slf4j
    @Configuration
    public class WebMvcConfiguration implements WebMvcConfigurer {
    
    	@Autowired
        private RateLimiterInterceptor rateLimiterInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(rateLimiterInterceptor).addPathPatterns("/rate/limiter/**");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    测试Controller接口
    注意:路径必须是 /rate/limiter

    @Controller
    @RequestMapping("/rate/limiter")
    public class Test Controller {
    
        @Resource
        private ITest test;
        
        @RequestMapping("/api")
        @ResponseBody
        public Result doBusiness(@RequestBody String json) throws Exception {
            return test.doBusiness(json);
        }
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    效果同上。
    加入每秒产生1个令牌,那在1秒内,只有第一次调用接口可以正常处理,其余接口均提示系统繁忙。

    OK,整理到这吧!

    如有不正确之处,还望指正!书写不易,觉得有帮助就点个赞吧!☺☺☺

  • 相关阅读:
    CTO 说了,用错@Autowired 和@Resource 的人可以领盒饭了
    zookeeper集群
    学习笔记1--自动驾驶系统架构
    【广州华锐互动】全屋智能家电VR虚拟仿真演示系统
    Docker数据卷&&自定义Docker镜像
    F3L600R10W4S7FC22BPSA1 EasyPACK IGBT模块 950V(F3L600R10W4S7FC22)
    CSS进阶篇——布局 (Layout)
    c语言:通讯录管理系统(动态分配内存版)
    循迹模式-差速转弯
    Linux(二)
  • 原文地址:https://blog.csdn.net/qq_38254635/article/details/126398730