springmvc限流 * 的示例代码
作者:valleychen1111 时间:2021-09-08 02:50:55
限流器算法
目前常用限流器算法为两种:令牌桶算法和漏桶算法,主要区别在于:漏桶算法能够强行限制请求速率,平滑突发请求,而令牌桶算法在限定平均速率的情况下,允许一定量的突发请求
下面是从网上找到的两张算法图示,就很容易区分这两种算法的特性了
漏桶算法
令牌桶算法
针对接口来说,一般会允许处理一定量突发请求,只要求限制平均速率,所以令牌桶算法更加常见。
令牌桶算法工具RateLimiter
目前本人常用的令牌桶算法实现类当属google guava的RateLimiter,guava不仅实现了令牌桶算法,还有缓存、新的集合类、并发工具类、字符串处理类等等。是一个强大的工具集
RateLimiter api可以查看并发编程网guava RateLimiter的介绍
RateLimiter源码分析
RateLimiter默认情况下,最核心的属性有两个nextFreeTicketMicros,下次可获取令牌时间,storedPermits桶内令牌数。
判断是否可获取令牌:
每次获取令牌的时候,根据桶内令牌数计算最快下次能获取令牌的时间nextFreeTicketMicros,判断是否可以获取资源时,只要比较nextFreeTicketMicros和当前时间就可以了,so easy
获取令牌操作:
对于获取令牌,根据nextFreeTicketMicros和当前时间计算出新增的令牌数,写入当前令牌桶令牌数,重新计算nextFreeTicketMicros,桶内还有令牌,则写入当前时间,并减少本次请求获取的令牌数。
如同java的AQS类一样,RateLimiter的核心在tryAcquire方法
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
//尝试获取资源最多等待时间
long timeoutMicros = max(unit.toMicros(timeout), 0);
//检查获取资源数目是否正确
checkPermits(permits);
long microsToWait;
//加锁
synchronized (mutex()) {
//当前时间
long nowMicros = stopwatch.readMicros();
//判断是否可以在timeout时间内获取资源
if (!canAcquire(nowMicros, timeoutMicros)) {
return false;
} else {
//可获取资源,对资源进行重新计算,并返回当前线程需要休眠时间
microsToWait = reserveAndGetWaitLength(permits, nowMicros);
}
}
//休眠
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return true;
}
判断是否可获取令牌:
private boolean canAcquire(long nowMicros, long timeoutMicros) {
//最早可获取资源时间-等待时间<=当前时间 方可获取资源
return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
}
RateLimiter默认实现类的queryEarliestAvailable是取成员变量nextFreeTicketMicros
获取令牌并计算需要等待时间操作:
final long reserveAndGetWaitLength(int permits, long nowMicros) {
//获取下次可获取时间
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
//计算当前线程需要休眠时间
return max(momentAvailable - nowMicros, 0);
}
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//重新计算桶内令牌数storedPermits
resync(nowMicros);
long returnValue = nextFreeTicketMicros;
//本次消耗的令牌数
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
//重新计算下次可获取时间nextFreeTicketMicros
double freshPermits = requiredPermits - storedPermitsToSpend;
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
//减少桶内令牌数
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
实现简单的spring mvc限流 *
实现一个HandlerInterceptor,在构造方法中创建一个RateLimiter限流器
public SimpleRateLimitInterceptor(int rate) {
if (rate > 0)
globalRateLimiter = RateLimiter.create(rate);
else
throw new RuntimeException("rate must greater than zero");
}
在preHandle调用限流器的tryAcquire方法,判断是否已经超过限制速率
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!globalRateLimiter.tryAcquire()) {
LoggerUtil.log(request.getRequestURI()+"请求超过限流器速率");
return false;
}
return true;
}
在dispatcher-servlet.xml中配置限流 *
<mvc:interceptors>
<!--限流 * -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="limit.SimpleRateLimitInterceptor">
<constructor-arg index="0" value="${totalRate}"/>
</bean>
</mvc:interceptor>
</mvc:interceptors>
复杂版本的spring mvc限流 *
使用Properties传入拦截的url表达式->速率rate
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="limit.RateLimitInterceptor">
<!--单url限流-->
<property name="urlProperties">
<props>
<prop key="/get/{id}">1</prop>
<prop key="/post">2</prop>
</props>
</property>
</bean>
</mvc:interceptor>
为每个url表达式创建一个对应的RateLimiter限流器。url表达式则封装为org.springframework.web.servlet.mvc.condition.PatternsRequestCondition。PatternsRequestCondition是springmvc 的DispatcherServlet中用来匹配请求和Controller的类,可以判断请求是否符合这些url表达式。
在 * preHandle方法中
//当前请求路径
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
//迭代所有url表达式对应的PatternsRequestCondition
for (PatternsRequestCondition patternsRequestCondition : urlRateMap.keySet()) {
//进行匹配
List<String> matches = patternsRequestCondition.getMatchingPatterns(lookupPath);
if (!matches.isEmpty()) {
//匹配成功的则获取对应限流器的令牌
if (urlRateMap.get(patternsRequestCondition).tryAcquire()) {
LoggerUtil.log(lookupPath + " 请求匹配到" + Joiner.on(",").join(patternsRequestCondition.getPatterns()) + "限流器");
} else {
//获取令牌失败
LoggerUtil.log(lookupPath + " 请求超过" + Joiner.on(",").join(patternsRequestCondition.getPatterns()) + "限流器速率");
return false;
}
}
}
具体的实现类
请见github
来源:http://blog.csdn.net/valleychen1111/article/details/78038366
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
详解解密Java中的类型转换问题
浅析Mybatis 在CS程序中的应用
![](https://img.aspxhome.com/file/2023/2/57422_0s.jpg)
SpringBoot项目依赖和配置最新示例讲解
如何将默认的maven仓库改为阿里的maven仓库
![](https://img.aspxhome.com/file/2023/6/61526_0s.png)
Java8 使用工厂方法supplyAsync创建CompletableFuture实例
![](https://img.aspxhome.com/file/2023/4/64404_0s.png)
Java 数据库连接池详解及简单实例
Java常见数据结构面试题(带答案)
Java创建与结束线程代码示例
Java 继承与多态超详细梳理
![](https://img.aspxhome.com/file/2023/6/59736_0s.png)
java编程下字符串的16位,32位md5加密实现方法
Java实现颜色渐变效果
![](https://img.aspxhome.com/file/2023/2/58062_0s.png)
使用@Order控制配置类/AOP/方法/字段的加载顺序详解
![](https://img.aspxhome.com/file/2023/1/58421_0s.png)
Java代码精简之道(推荐)
SpringBoot实现PPT格式文件上传并在线预览功能
![](https://img.aspxhome.com/file/2023/3/57403_0s.png)
Java内存模型JMM详解
![](https://img.aspxhome.com/file/2023/4/59784_0s.png)
Java序列化和反序列化示例介绍
Java实战之在线寄查快递系统的实现
![](https://img.aspxhome.com/file/2023/2/58182_0s.png)
利用Java计算某个日期是星期几
Java中Servlet的生命周期详解
![](https://img.aspxhome.com/file/2023/7/61057_0s.png)
SpringBoot 配置文件总结
![](https://img.aspxhome.com/file/2023/3/59343_0s.webp)