zoukankan      html  css  js  c++  java
  • guava limiter

    在应用中有很多场景需要限制流量防止突然的流量峰值影响服务器、数据库的性能。在谷歌提供的Guava包中就有这么一个工具用于对流量进行限制。

    令牌桶算法

    令牌桶算法是往一个固定大小的令牌桶中定时发放令牌,请求只有拿到令牌之后才能继续访问,当没有拿到令牌则阻塞或直接返回。

    使用

    guava中的RateLimiter实现了令牌桶算法,使用也很简单

        public void test() throws InterruptedException {
    		//创建limiter
            RateLimiter limiter=RateLimiter.create(2);
    		//使用limiter
            System.out.println(limiter.acquire());        
            System.out.println(limiter.acquire());
            System.out.println(limiter.acquire());
            Thread.sleep(1000);
            System.out.println(limiter.acquire());
            System.out.println(limiter.acquire());
        }
    

    limiter有两个方法acquire和tryAcquire,tryAcquire是直接返回是否获得了令牌,返回类型为bool,而acquire是阻塞的形式获得令牌,返回的结果是等待的时间(单位秒)。所以上面的例子,创建了一个每秒两个令牌的limiter,第一次acquire会立即获得,之后的两次会需要等待半秒钟,在sleep1秒之后,令牌桶会满,此时就可以连续两次无需等待获得令牌

    0.0
    0.498939
    0.472683
    0.0
    0.0
    

    实现

    guava并没有和原理一样使用另一个线程对令牌桶中加令牌,而是记录了令牌的状态,在每次调用acquire的时候,刷新令牌数,并计算是否可以获得令牌以及需要等待的时间,再强制sleep来实现流量的限制

      final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    	//根据当前时间刷新令牌状态
        resync(nowMicros);
    	//返回下一个令牌的时间
        long returnValue = nextFreeTicketMicros;
    	//当前已有的permit
        double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    	//需要等待的permit
        double freshPermits = requiredPermits - storedPermitsToSpend;
    	//算出需要等待的时间
        long waitMicros =
            storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
                + (long) (freshPermits * stableIntervalMicros);
    
        //保存令牌状态
    	this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
        this.storedPermits -= storedPermitsToSpend;
    	//返回需要下一个令牌的时间
        return returnValue;
      }
    

    在外层则会根据返回值进行sleep。
    对于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时间内获得一张令牌则返回false
          if (!canAcquire(nowMicros, timeoutMicros)) {
            return false;
          } else {
    	  //不然则获得全部令牌
            microsToWait = reserveAndGetWaitLength(permits, nowMicros);
          }
        }
    	//此处等待时间应该是0
        stopwatch.sleepMicrosUninterruptibly(microsToWait);
        return true;
      }
    

    由此看出,如果需要几秒发放一个请求,可以通过acquire、tryAcquire可以通过获取多个permit(令牌)来实现。

  • 相关阅读:
    LinQ&EF任我行(一)LinQ to SQL (转)
    WPF数据模板和控件模板
    Sql优化
    SQL锁表语句
    js动态创建dom
    js实现等待n秒后按钮可用
    js关于事件冒泡
    工作流学习(个人总结)
    sql常用函数
    将Datatable序列化为Json对象返回到客户端
  • 原文地址:https://www.cnblogs.com/resentment/p/7581716.html
Copyright © 2011-2022 走看看