基于Java编写一个限流工具类RateLimiter

 更新时间:2024年01月26日 17:10:37   作者:不归SUN  
这篇文章主要为大家详细介绍了如何基于Java编写一个限流工具类RateLimiter,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

限流工具类RateLimiter

原理:令牌桶算法

有一个桶,桶的容量固定。系统以恒定的速度往桶里放令牌,令牌数不超过桶的容量。

用户发送请求进来,需要先从桶里获取一个令牌才能通过,获取后桶里令牌数减一。如果系统两秒一个往桶里放令牌,用户请求一秒一次,那么当令牌被取空后,操作就需要等待,会被限流。

可以应对突发流量,当桶里有足够多的令牌,可以一次处理多个请求

1.导入guava依赖包

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

RateLimiter的集个核心方法:create()tryAcquire()

  • acquire() 获取一个令牌, 会阻塞当前线程,直到获取到一个令牌。该方法返回值类型为 double,表示当前线程需要等待的时间(单位:秒),这个时间取决于令牌桶中令牌的剩余数量和发放速率。当执行 rateLimiter.acquire() 方法时,如果令牌桶中还有剩余的令牌,则该方法会立即返回,返回值为 0,表示当前线程无需等待即可获取到一个令牌。如果令牌桶中没有令牌,那么该方法就会阻塞当前线程,直到令牌桶中有令牌可用或者线程被中断。
  • acquire(int permits) 获取指定数量的令牌, 该方法也会阻塞, 返回值为获取到这 N 个令牌花费的时间
  • tryAcquire() 判断时候能获取到令牌, 如果不能获取立即返回 false
  • tryAcquire(int permits) 获取指定数量的令牌, 如果不能获取立即返回 false
  • tryAcquire(long timeout, TimeUnit unit) 判断能否在指定时间内获取到令牌, 如果不能获取立即返回 false
  • tryAcquire(int permits, long timeout, TimeUnit unit) 判断能否在指定时间内获取到指定数量的令牌, 如果不能获取立即返回 false
  • create(double permitsPerSecond) 创建每秒放入指定数量的令牌桶。SmoothBursty模式
  • create(5.0, 1, TimeUnit.SECONDS) 每秒5个令牌,预热时间1秒,SmoothWarmingUp模式

2.代码

public void limitTest(){  
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");  
  
//创建令牌桶,一秒一个,容量为1  
RateLimiter rateLimiter = RateLimiter.create(1);  
//获取放令牌的速率  
System.out.println("放令牌的速率:"+rateLimiter.getRate());  
for (int i=0;i<5;i++){  
//获取令牌,会阻塞,返回等待的时间  
double acquire = rateLimiter.acquire();  
System.out.println("第"+i+" 个令牌获取到的时间:"+LocalDateTime.now().format(dtf)+",等待时间:"+acquire);  
}  
  
//是否会立即获取到令牌  
boolean tryAcquire = rateLimiter.tryAcquire();  
}

3.AOP+RateLimiter+注解,实现限流

1.创建注解

public @interface Limit {  
// 资源主键  
String key() default "";  
//最多访问次数,代表请求总数量  
double permitsPerSeconds();  
// 时间:即timeout时间内,只允许有permitsPerSeconds个请求总数量访问,超过的将被限制不能访问  
long timeout();  
//时间类型,默认秒
TimeUnit timeUnit() default TimeUnit.SECONDS;  
  
//提示信息  
String msg() default "系统繁忙,请稍后重试";  
}

2.AOP切面

@Aspect  
@Component  
public class LimitAop {  
  
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();  
  
private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");  

@Around("@annotation(com.limit.Limit)")  
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{  
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();  
    Method method = signature.getMethod();  
    //拿limit的注解  
    Limit limit = method.getAnnotation(Limit.class);  
    if (limit != null) {  
        // key作用:不同的接口,不同的流量控制,相当于每个接口有一个对应的令牌桶  
        String key = limit.key();   
        RateLimiter rateLimiter;  
        //验证缓存是否有命中key  
        if (!limitMap.containsKey(key)) {  
            //创建令牌桶  
            rateLimiter = RateLimiter.create(limit.permitsPerSeconds());  
            limitMap.put(key, rateLimiter);  
            log.info("新建了令牌桶={},容量={}", key, limit.permitsPerSeconds());  
        }  
        rateLimiter = limitMap.get(key);  
        //拿一个令牌,拿不到会一直阻塞 
        double acquire = rateLimiter.acquire(1);  
        log.info("{},获取令牌时间{}", key,LocalDateTime.now().format(dtf));  
        
        /*
        //是否能立即拿到令牌,不能则桶里没有,还没到一秒钟,进行限流
        boolean acquire = rateLimiter.tryAcquire();  
        if (!acquire) {  
        log.info("令牌桶={},获取令牌失败", key);  
        throw new RuntimeException(limit.msg());  
        }*/  
        
        }  
    return joinPoint.proceed();  
    }  
}

3.注解使用

@GetMapping("/limitTest")  
@Limit(key = "limitTest",permitsPerSeconds = 1,timeout = 1,msg = "触发接口限流,请重试")  
public void limitTest(){  
    //其它逻辑代码  
    //这里打印调用该接口执行的时间,以便观察限流  
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");  
    System.out.println(LocalDateTime.now().format(dtf));  
}

日志情况:

无论请求速度多快,一秒后才能处理请求。因为rateLimiter.acquire(1)拿不到,一直等待,会阻塞。 其它获取令牌的方法感兴趣可以按照上文方法详情自行测试。

到此这篇关于基于Java编写一个限流工具类RateLimiter的文章就介绍到这了,更多相关Java限流工具类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot使用Logback进行日志记录的代码示例

    SpringBoot使用Logback进行日志记录的代码示例

    在开发Web应用程序时,日志记录是非常重要的一部分,在SpringBoot中,我们可以使用Logback进行日志记录,Logback是一款高性能、灵活的日志框架,它可以满足各种不同的日志需求,在本文中,我们介绍了如何在SpringBoot中使用Logback进行日志记录
    2023-06-06
  • java模拟实现银行ATM机操作

    java模拟实现银行ATM机操作

    这篇文章主要为大家详细介绍了java模拟实现银行ATM机操作,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Java利用反射动态设置对象字段值的实现

    Java利用反射动态设置对象字段值的实现

    桥梁信息维护需要做到字段级别的权限控制,本文主要介绍了Java利用反射动态设置对象字段值的实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • Spring Boot文件上传简单实例代码

    Spring Boot文件上传简单实例代码

    在本篇文章里小编给大家分享的是关于Spring Boot 文件上传简易教程以及相关知识点,需要的朋友们参考下。
    2019-08-08
  • springboot tomcat的maxHttpFormPostSize参数示例解析

    springboot tomcat的maxHttpFormPostSize参数示例解析

    这篇文章主要介绍了springboot tomcat的maxHttpFormPostSize参数示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • SpringBoot单点登录实现过程详细分析

    SpringBoot单点登录实现过程详细分析

    这篇文章主要介绍了SpringBoot单点登录实现过程,单点登录英文全称Single Sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统
    2022-12-12
  • Spring定时任务轮询本地数据库实现过程解析

    Spring定时任务轮询本地数据库实现过程解析

    这篇文章主要介绍了Spring定时任务轮询本地数据库实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • java使用java.util.Date获取指定日期的年、月、日、时、分、秒

    java使用java.util.Date获取指定日期的年、月、日、时、分、秒

    在Java中获取当前时间和日期是很常见的操作,也是很重要的操作,下面这篇文章主要给大家介绍了关于java使用java.util.Date获取指定日期的年、月、日、时、分、秒的相关资料,需要的朋友可以参考下
    2024-01-01
  • 深入了解Java中成员变量与局部变量的使用与区别

    深入了解Java中成员变量与局部变量的使用与区别

    成员变量和局部变量在每种编程语言中都有涉及,本文将通过示例为大家详细讲讲Java中成员变量与局部变量的使用与区别,感兴趣的可以了解一下
    2022-08-08
  • JAVA初探设计模式的六大原则

    JAVA初探设计模式的六大原则

    这篇文章主要介绍了JAVA初探设计模式的六大原则,对设计模式感兴趣的同学,可以参考下
    2021-05-05

最新评论