基于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限流工具类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Hibernate原理及应用

    Hibernate原理及应用

    本文主要介绍了Hibernate原理及应用。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • Spring Boot中使用Activiti的方法教程(二)

    Spring Boot中使用Activiti的方法教程(二)

    工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,下面这篇文章主要给大家介绍了关于Spring Boot中使用Activiti的相关资料,需要的朋友可以参考下
    2018-08-08
  • 多模块项目使用枚举配置spring-cache缓存方案详解

    多模块项目使用枚举配置spring-cache缓存方案详解

    这篇文章主要为大家介绍了多模块项目使用枚举配置spring-cache缓存的方案详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • SpringCloud微服务熔断器使用详解

    SpringCloud微服务熔断器使用详解

    这篇文章主要介绍了Spring Cloud Hyxtrix的基本使用,它是Spring Cloud中集成的一个组件,在整个生态中主要为我们提供服务隔离,服务熔断,服务降级功能,本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • MybatisPlus代码生成器含XML文件详解

    MybatisPlus代码生成器含XML文件详解

    这篇文章主要介绍了MybatisPlus代码生成器含XML文件详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • java面向对象之人机猜拳小游戏

    java面向对象之人机猜拳小游戏

    这篇文章主要为大家详细介绍了java面向对象之人机猜拳小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • Java泛型最全知识总结

    Java泛型最全知识总结

    面试被问到Java泛型怎么办,有了这篇文章,让你直接保送,文中有非常详细的知识总结及相关代码示例,需要的朋友可以参考下
    2021-06-06
  • Java基础篇之对象数组练习

    Java基础篇之对象数组练习

    对象数组就是数组里的每个元素都是类的对象,赋值时先定义对象,然后将对象直接赋给数组就行了,这篇文章主要给大家介绍了关于Java基础篇之对象数组练习的相关资料,需要的朋友可以参考下
    2024-03-03
  • java判断ip是否为指定网段示例

    java判断ip是否为指定网段示例

    这篇文章主要介绍了java判断ip是否为指定网段示例方法,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Java实现文本编译器

    Java实现文本编译器

    这篇文章主要为大家详细介绍了Java实现文本编译器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04

最新评论