【转】springmvc限流拦截器(限制数据流量)

转自:http://blog.csdn.net/chenwanli1111/article/details/56013427

springmvc限流拦截器

限流器算法

目前常用限流器算法为两种:令牌桶算法和漏桶算法,主要区别在于:漏桶算法能够强行限制请求速率,平滑突发请求,而令牌桶算法在限定平均速率的情况下,允许一定量的突发请求下面是从网上找到的两张算法图示,就很容易区分这两种算法的特性了
漏桶算法

漏桶算法

令牌桶算法
令牌桶算法

针对接口来说,一般会允许处理一定量突发请求,只要求限制平均速率,所以令牌桶算法更加常见。

令牌桶算法工具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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 聊聊高并发系统限流特技-1来自开涛的博客 在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是...
    meng_philip123阅读 11,715评论 1 20
  • 最近一直都在研究压力测试客户端的问题,如果突破客户端压力测试线程,端口等问题,如果服务器端处理网络请求处理不过来,...
    望月成三人阅读 12,764评论 1 25
  • 摘要:在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。而有些场景并不能用缓存和降级来解决,因此需有一种...
    落羽成霜丶阅读 6,485评论 0 18
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,169评论 19 139
  • 当太阳最后一角消失在山头,晚霞褪去浓妆,暮色四合,月升东山的时候,一盏盏路灯也亮了。就像天上的星星,睁开朦胧的眼睛...
    能饮壹杯无阅读 3,003评论 0 3

友情链接更多精彩内容