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


猜你喜欢
- 一、说明 添加视图文件的时候有两种方式:1、通过在xml文件定义layout;2、java代码编写二、前言说明1.构造xml文件2.Layo
- 前言传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事物,这么做有两个缺点:1、如果所有的内容都配置在.xml
- mybatis的SqlSession一定要关闭今天在使用mybatis查询数据时,出现了一个很奇怪的问题。同一条sql语句,查询时快时慢,并
- volatile 变量提供了线程的可见性,并不能保证线程安全性和原子性。什么是线程的可见性:锁提供了两种主要特性:互斥(mutual exc
- 简介OCSP在线证书状态协议是为了替换CRL而提出来的。对于现代web服务器来说一般都是支持OCSP的,OCSP也是现代web服务器的标配。
- Android 实现会旋转的饼状统计图实例代码最近在做一个项目,由于有需要统计的需要,于是就做成了下面饼状统计图。 下图是效果图: 大致思路
- 基础铺垫在java中,关于json的lib有很多,比如jackjson、fastjson、gson等等,本人都用过,但是对于我等只需要让ja
- 随着Android设备增多,不少网站都开始设备Android设备,而Android主流设备类型以手机和平板为主。网站在适配时通过User A
- 需求分析:我们在做winform开发的时候,有时候需要让程序休眠几秒钟,但是如果我们直接使用 Thread.Sleep()函数的话,页面UI
- 通常在使用service更新应用时最常出现的问题就是Notification进度的更新问题、service在什么时间关闭以及需要我们自己在S
- 1.概述:C语言中的单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始。链表中最简单的一种
- 本文实例讲述了Android编程获取GPS数据的方法。分享给大家供大家参考,具体如下:GPS是Android系统中重要的组成部分,通过它可以
- 一:.Net中有两个类 HttpWebRequest 和HttpWebResponse 类来实现Http的请求实现步骤:1.通过WebReq
- 本文实例讲述了C#内置队列类Queue用法。分享给大家供大家参考。具体分析如下:这里详细演示了C#内置的队列如何进行添加,移除等功能。usi
- 折半查找法仅适用于对已有顺序的数组、数据进行操作!!!(从小到大)自我总结:折半查找法就是相当于(通过改变low或high的大小)把中间位置
- 前言说起Android进行间通信,大家第一时间会想到AIDL,但是由于Binder机制的限制,AIDL无法传输超大数据。那么我们如何在进程间
- 在日常的开发中、我们都知道,Java的内存清理是通过垃圾回收器进行的,那么其是如何将没用的对象被被清理掉的呢?Java 语言的内存自动回收称
- 学过C#的人应该都知道抽象方法与虚拟方法,而很多初学者对二者之间的区别并不是很了解。今天本文就来分析一下二者之间的区别。并附上实例加以说明。
- java随机验证码生成实现实例代码摘要: 在项目中有很多情况下都需要使用到随机验证码,这里提供一个java的随机验证码生成方案,可以指定难度
- 通常,反射用于动态获取对象的类型、属性和方法等信息。今天带你玩转反射,来汇总一下反射的各种常见操作,捡漏看看有没有你不知道的。获取类型的成员