SpringBoot如何使用RateLimiter通过AOP方式进行限流
作者:yellow_han 发布时间:2023-09-16 18:18:44
使用RateLimiter通过AOP方式进行限流
1、引入依赖
<!-- guava 限流 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
2、自定义注解
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ServiceLimit {
String description() default "";
}
3、AOP实现类
@Component
@Scope
@Aspect
public class LimitAspect {
每秒只发出5个令牌,此处是单进程服务的限流,内部采用令牌捅算法实现
private static RateLimiter rateLimiter = RateLimiter.create(5.0);
//Service层切点 限流
@Pointcut("@annotation(com.itstyle.seckill.common.aop.ServiceLimit)")
public void ServiceAspect() {
}
@Around("ServiceAspect()")
public Object around(ProceedingJoinPoint joinPoint) {
Boolean flag = rateLimiter.tryAcquire();
Object obj = null;
try {
if(flag){
obj = joinPoint.proceed();
}
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
}
4、使用
@Override
@ServiceLimit
@Transactional
public Result startSeckil(long seckillId,long userId) {
//todo 操作
}
SpringBoot之限流
限流的基础算法
令牌桶和漏桶
漏桶算法 的实现往往依赖于队列,请求到达如果队列未满则直接放入队列,然后有一个处理器按照固定频率从队列头取出请求进行处理。如果请求量大,则会导致队列满,那么新来的请求就会被抛弃。
令牌桶算法 则是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝。当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直接处理,并且令牌桶删除一个令牌。如果获取不到,该请求就要被限流,要么直接丢弃,要么在缓冲区等待。
令牌桶和漏桶对比
令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
令牌桶限制的是平均流入速率,允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌;漏桶限制的是常量流出速率,即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2,从而平滑突发流入速率;
令牌桶允许一定程度的突发,而漏桶主要目的是平滑流出速率;
Guava RateLimiter
1.依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
<optional>true</optional>
</dependency>
2.示例代码
@Slf4j
@Configuration
public class RequestInterceptor implements HandlerInterceptor {
// 根据字符串分不同的令牌桶, 每天自动清理缓存
private static LoadingCache<String, RateLimiter> cachesRateLimiter = CacheBuilder.newBuilder()
.maximumSize(1000) //设置缓存个数
/**
* expireAfterWrite是在指定项在一定时间内没有创建/覆盖时,会移除该key,下次取的时候从loading中取
* expireAfterAccess是指定项在一定时间内没有读写,会移除该key,下次取的时候从loading中取
* refreshAfterWrite是在指定时间内没有被创建/覆盖,则指定时间过后,再次访问时,会去刷新该缓存,在新值没有到来之前,始终返回旧值
* 跟expire的区别是,指定时间过后,expire是remove该key,下次访问是同步去获取返回新值;
* 而refresh则是指定时间后,不会remove该key,下次访问会触发刷新,新值没有回来时返回旧值
*/
.expireAfterAccess(1, TimeUnit.HOURS)
.build(new CacheLoader<String, RateLimiter>() {
@Override
public RateLimiter load(String key) throws Exception {
// 新的字符串初始化 (限流每秒2个令牌响应)
return RateLimiter.create(2);
}
});
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.info("request请求地址path[{}] uri[{}]", request.getServletPath(), request.getRequestURI());
try {
String str = "hello";
// 令牌桶
RateLimiter rateLimiter = cachesRateLimiter.get(str);
if (!rateLimiter.tryAcquire()) {
System.out.println("too many requests.");
return false;
}
} catch (Exception e) {
// 解决 * 的异常,全局异常处理器捕获不到的问题
request.setAttribute("exception", e);
request.getRequestDispatcher("/error").forward(request, response);
}
return true;
}
}
3.测试
@RestController
@RequestMapping(value = "user")
public class UserController {
@GetMapping
public Result test2(){
System.out.println("1111");
return new Result(true,200,"");
}
}
http://localhost:8080/user/
如果没有result类,自己可以随便返回个字符串
4.测试结果
其他
创建
RateLimiter提供了两个工厂方法:
一个是平滑突发限流
RateLimiter r = RateLimiter.create(5); //项目启动,直接允许5个令牌
一个是平滑预热限流
RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS); //项目启动后3秒后才会到达设置的2个令牌
缺点
RateLimiter只能用于单机的限流,如果想要集群限流,则需要引入redis或者阿里开源的sentinel中间件。
TimeUnit.SECONDS);` //项目启动后3秒后才会到达设置的2个令牌
来源:https://blog.csdn.net/u014769528/article/details/84526363
猜你喜欢
- 这篇文章需要对git具有一定的了解,并且知道如何安装git工具(其实就是上git官网下载个文件,安装)git这种版本控制工具有什么好处第一个
- 前言:我们每天都在编写Java代码,编译,执行。很多人已经知道Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.cl
- spring的自动装配功能的定义:无须在Spring配置文件中描述javaBean之间的依赖关系(如配置<property>、&
- 如图所示的效果相信大家都不陌生,我们可以使用很多种方法去实现此效果,这里自己采用CountDownTimer定时器简单封装下此效果,方便我们
- 最近在配置OpenCV的时候,由于使用的是VS2019,结果找不到Microsoft.Cpp.X64.user这个文件。导致每次新建项目都得
- jpa EntityManager复杂查询概念EntityManager:EntityManager是JPA中用于增删改查的接口,它的作用相
- WPF实现一个简单的多运算符表达式计算器,供大家参考,具体内容如下1.先看下效果图首先外围给了一个grid 把他分成了两行 第一行用来显示文
- 有时候,我们在同一个activity里面有很多fragment,在横竖屏的时候,有些fragment要求重新加载数据,有些不需要,如何简单控
- 1、首先导入solrj需要的的架包2、需要注意的是低版本是solr是使用SolrServer进行URL实例的,5.0之后已经使用SolrCl
- PS:本文包含了大部分strings函数的说明,并附带举例说明。本来想自己整理一下的,发现已经有前辈整理过了,就转了过来。修改了原文一些源码
- pom.xml配置<dependency> <groupId>org.springframework.
- 早期的项目比较简单,多是用JSP 、Servlet + JDBC 直接搞定,后来使用 Struts1(Struts2)+Spring+Hib
- 一、maven引入依赖,数据库驱动根据项目需求自行引入<!-- https://mvnrepository.com/artifact/
- 本文实例为大家分享了java实现简单石头剪刀布游戏的具体代码,供大家参考,具体内容如下问题描述Alice, Bob和Cindy一起玩猜拳的游
- 无论是我们在使用word还是记事本,系统都会为我们提供撤销的功能,这几乎是人人都会使用到的功能,而在我们实际开发中,会不会存在一个很复杂的对
- 本文实例为大家分享了Java实现简单邮件发送的具体代码,供大家参考,具体内容如下需要的jar包:activation-1.1.1.jarma
- activity A和BA 获取数据的activity B返回数据的activity点击A上的按钮,在A的textview上显示
- java 读取本地文件实例详解用javax.xml、w3c解析实例代码:package cn.com.xinli.monitor.utils
- mapper文件使用in("str1","str2")mybatis的xxxMapper.xml文件
- springboot读取文件,打成jar包后访问不到最新开发出现一种情况,springboot打成jar包后读取不到文件,原因是打包之后,文