Guava RateLimter 基础学习

平滑突发限流

   public static void main(String[] args) {
        RateLimiter rateLimiter = RateLimiter.create(5);
        while (true) {
            double acquire = rateLimiter.acquire();
            System.out.println(acquire);
        }
    }
    /**
    0.0
	0.198756
	0.19432
	0.199067
	*/

可见 令牌并不是一次性给的,而是在这一秒中,按照固定的速率分发的,所以每一个请求大概0.2s。不过,RateLimiter有积累的作用,在桶还没有满的情况下,RateLimiter会一直分发,直到桶被装满位置,这时候如果来了请求,只要令牌还没有被分发完,就可以立即获得令牌,所以RateLimiter可以应对突发性流量。

	public static void main(String[] args) throws InterruptedException {
        RateLimiter rateLimiter = RateLimiter.create(5);
        Thread.sleep(1000);
        while (true) {
            double acquire = rateLimiter.acquire();
            System.out.println(acquire);
        }
    }
    /**
    0.0
	0.0
	0.0
	0.0
	0.0
	0.0
	0.198676
	0.194248
    */

RateLimiter还有一个特性,在没有足够令牌发放时,采用滞后处理的方式;比如,现在只有3个令牌,但是现在有一个请求要4个令牌,可以立即响应,先跟RateLimiter借1个令牌。这个请求获取令牌所需等待的时间由下一次请求来承受,也就是代替前一个请求进行等待。

   public static void main(String[] args) throws InterruptedException {
        RateLimiter rateLimiter = RateLimiter.create(6);
        Thread.sleep(1000);
        System.out.println(rateLimiter.acquire(3));
        System.out.println(rateLimiter.acquire(4)); //令牌不够,但是仍然能立即返回
        System.out.println(rateLimiter.acquire(1));
    }
 /**
 0.0
0.0
0.163041
 */

平滑预热限流

我们先来看一下源码

    //permitsPerSecond请求的令牌个数,warmupPeriod预热的时间,unit预热时间间隔单位
    public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
        Preconditions.checkArgument(warmupPeriod >= 0L, "warmupPeriod must not be negative: %s", new Object[]{warmupPeriod});
        return create(RateLimiter.SleepingStopwatch.createFromSystemTimer(), permitsPerSecond, warmupPeriod, unit);
    }

带有预热期的平滑限流,他启动后会有一段预热期,发放令牌的频率和时间呈一个梯度,等到时间过了warmupPeriod,才到达原本设置的频率。这种功能主要适合系统刚启动需要一点时间来“热身”的场景。

        RateLimiter rateLimiter = RateLimiter.create(6, 2, TimeUnit.SECONDS);
        while (true) {
            System.out.println(rateLimiter.acquire(2));
        }
 /**
    0.0
	0.888101	
	0.660152
	0.439506  上面加起来刚好差不多2s

	0.328413  热身完毕
	0.329504
	0.329148
*/

原理分析–以平滑突发限流为例

我们先从acquire看起

	public double acquire() {
        return this.acquire(1);
    }

    public double acquire(int permits) {
    	// 返回现在需要等待的时间
        long microsToWait = this.reserve(permits);
        //睡眠 阻塞
        this.stopwatch.sleepMicrosUninterruptibly(microsToWait);
        //返回等待的时间 转换成秒
        return 1.0D * (double)microsToWait / (double)TimeUnit.SECONDS.toMicros(1L); 
	}


    final long reserve(int permits) {
        checkPermits(permits); //检查permits是否合理 >0
        synchronized(this.mutex()) { //lock
            return this.reserveAndGetWaitLength(permits, this.stopwatch.readMicros()); //返回需要等待的时间
            //this.stopwatch.readMicros() 现在的时间 
        }
    }
    
       final long reserveAndGetWaitLength(int permits, long nowMicros) {
        long momentAvailable = this.reserveEarliestAvailable(permits, nowMicros); //算出距离现在最近的能够获取令牌的时间
        return Math.max(momentAvailable - nowMicros, 0L);//返回需要等待的时间
    }

接下来我们来看一下reserveEarliestAvailabe是怎么算出最近能够获取到令牌的时间

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    // 刷新令牌数,相当于每次acquire的时候才根据时间进行令牌的刷新
    resync(nowMicros);
    //nextFreeTicketMicros 下一次请求可以获取令牌的时间 成员属性
    long returnValue = nextFreeTicketMicros;
    // 获取当前已有的令牌数和需要获取的目标令牌数进行比较,计算出可以目前即可得到的令牌数。
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    // freshPermits是需要预先支付的令牌,也就是目标令牌数减去目前即可得到的令牌数
    // 因为会突然涌入大量请求,而现有令牌数又不够用,因此会预先支付一定的令牌数
    double freshPermits = requiredPermits - storedPermitsToSpend;
    
    // waitMicros即是产生预先支付令牌的数量时间,则将下次要添加令牌的时间应该计算时间加上watiMicros
    //stableIntervalMicros 添加令牌的时间间隔
    //SmoothBuresty的storedPermitsToWaitTime直接返回0,所以watiMicros就是预先支付的令牌所需等待的时间  但是在SmoothWarmingUp中就不一样,用于实现预热缓冲期
    long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
        + (long) (freshPermits * stableIntervalMicros);
    try {
      // 更新nextFreeTicketMicros,本次预先支付的令牌所需等待的时间让下一次请求来实际等待。
      this.nextFreeTicketMicros = LongMath.checkedAdd(nextFreeTicketMicros, waitMicros);
    } catch (ArithmeticException e) {
      this.nextFreeTicketMicros = Long.MAX_VALUE;
    }
    // 更新令牌数,最低数量为0
    this.storedPermits -= storedPermitsToSpend;
    // 返回旧的nextFreeTicketMicros数值,无需为预支付的令牌多加等待时间。
    return returnValue;
}


// SmoothBurest
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
    return 0L;
}

//更新令牌数
void resync(long nowMicros) {
    // 当前时间晚于nextFreeTicketMicros,所以刷新令牌和nextFreeTicketMicros
    if (nowMicros > nextFreeTicketMicros) {
      // coolDownIntervalMicros函数获取每几秒生成一个令牌,SmoothWarmingUp和SmoothBuresty的实现不同
      // SmoothBuresty的coolDownIntervalMicros直接返回stableIntervalMicros
      // 当前时间减去要更新令牌的时间获取时间间隔,再除以添加令牌时间间隔获取这段时间内要添加的令牌数
      storedPermits = min(maxPermits,
          storedPermits
            + (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros());
      nextFreeTicketMicros = nowMicros;
    }
    // 如果当前时间早于nextFreeTicketMicros,则获取令牌的线程要一直等待到nextFreeTicketMicros,该线程获取令牌所需
    // 额外等待的时间由下一次获取的线程来代替等待。
}
//返回生成令牌的时间间隔
double coolDownIntervalMicros() {
    return stableIntervalMicros;
}

缺点

  • RateLimiter只能运用与单机不能运用与分布式
  • 结合redis实现分布式

结合Redis实现分布式

思路

  • 自己按照这个原理写一个锁,把字段放在redis里面
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值