服务限流之Guava源码解析

本文探讨了系统中用于保护稳定性的三大策略:缓存、限流与降级,重点解析了限流的常见算法(令牌桶与漏桶)及其应用场景。深入介绍了Guava的RateLimiter工具类,基于令牌桶算法实现流量控制,详细解析了其内部工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

在应用系统中有三把利器可以保护系统,提高系统健壮性:缓存、限流、降级,它们分别在不同场景下使用,但最终但目的都是用来保护系统稳定运行。其各自的使用场景及用途如下:

1)缓存:提高系统响应速度,为数据库减压,避免过多查询数据库;

2)限流:限制并发量,限制某一段时间只有指定数量的请求进入后台服务器,遇到流量高峰期或者流量突增(流量尖刺)时,把流量速率限制在系统所能接受的合理范围之内,不至于让系统被高流量击垮;

3)降级:当访问某个服务出现问题或影响核心业务时,暂时屏蔽该服务,直到请求高峰过后或者服务正常时再打开。

二、常用限流算法

常见等限流算法主要有2种:令牌桶、漏桶。令牌桶算法是令牌以固定速率流入桶内,桶满了则丢弃令牌,桶内没有令牌时则阻塞,当桶内有令牌但不够时,不会阻塞,允许预先消费,可解决流量突发情况。漏桶法则允许以任意速率流入桶内,但流出速率是恒定的,桶满了则丢弃令牌,桶内没有令牌时则阻塞,它不能解决流量突发情况。具体流程如图所示:

1)令牌桶法

5f59a0ccd8d7ff02c6fa4db3ebd04269db6.jpg

2)漏桶法

9d8fe505f4c07946774d2254f2cec1b4526.jpg

三、常见的限流场景

开发过程中或多或少会接触到服务限流,比如tomcat限制最大连接数、数据库连接池限制连接数、nginx限制ip访问数、秒杀、抢购等。这些都是通过限制一个时间窗口内的请求数,当达到设置的最大请求数后,会让后续请求进入等待队列或直接拒绝,防止系统过载。这些限流是怎么做到的呢,或者说限流主要有哪些方式呢?很多人把限流归纳成4种场景,其实也不外乎这4种场景,它们分别是:

1)限制总并发数或请求数:针对整个系统做拦截

2)限制接口的总并发或请求数:针对单个接口做拦截

3)限制接口每秒的请求数:针对整个接口做拦截,时间窗口为1秒

4)平滑限流接口的请求数:平滑突发限流(SmoothBursty)、平滑预热限流(SmoothWarmingUp)

四、Guava使用及原理解析

4.1 Guava使用

Guava是google提供的开源工具包,提供了限流工具类RateLimiter,该类基于令牌桶算法实现流量限制,只需在pom.xml文件引入guava依赖,使用起来很方便。

4d03f7428e5968d3c722e24a04a33639f69.jpg

632da17240ad4f8cef6140c7654ffdc7ff5.jpg

db1544bb5b3436f4f0c4225918f7dc2bbbd.jpg

从执行结果可以看到第一次获取时等待时间为0.0s,后续每次获取令牌都需等待0.5秒,0.5表示每秒往容器里平滑的放2个凭证,每0.5秒放入一个,0.5是怎么得来的呢?其实就是1/2=0.5,而2即是RateLimiter.create(2)这个方法中的参数值。那么Guava是怎么实现的呢?

4.2 Guava实现原理

Guava的RateLimiter限流步骤主要分2步:初始化、获取凭证,初始化主要是创建RateLimiter对象,并初始化对应的属性值,其中比较重要的几个属性如下:

7cbe2a2298fbb7fe1020eb880edd9868052.jpg

4.1.1 容器初始化

初始化过程会做如下赋值:storedPermits=0、nextFreeTicketMicros=当前时间、stableIntervalMicros=1/qps,具体流程如下:

ffc289bf6428188ca0697a110d8f2993c61.jpg

static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond) {
  // 创建RateLimiter对象
  RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
  // 初始化storedPermits、nextFreeTicketMicros、stableIntervalMicros的值
  rateLimiter.setRate(permitsPerSecond);
  return rateLimiter;
}

 

public final void setRate(double permitsPerSecond) {
  checkArgument(
      permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
  synchronized (mutex()) {
    doSetRate(permitsPerSecond, stopwatch.readMicros());
  }
}

 

final void doSetRate(double permitsPerSecond, long nowMicros) {
  // 计算storedPermits、nextFreeTicketMicros的值
  resync(nowMicros);
  // 计算每个凭证流入的间隔时间
  double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
  this.stableIntervalMicros = stableIntervalMicros;
  doSetRate(permitsPerSecond, stableIntervalMicros);
}

 

void resync(long nowMicros) {
  // 如果当前时间能够获取凭证,每次获取凭证时都会计算下一次能获取凭证的时间
  if (nowMicros > nextFreeTicketMicros) {
    // 容器里的总凭证数,即result=容器中原有凭证数+该时间点应该往容器里放多少凭证
    storedPermits = min(maxPermits,
        storedPermits
          + (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros());
    nextFreeTicketMicros = nowMicros;
  }
}

4.1.2 获取凭证

311e0b7f73eb6a114fd1282d2a13752fd56.jpg

public double acquire(int permits) {
  // 计算需要等待的时间
  long microsToWait = reserve(permits);
  // 线程阻塞指定时间(tryAcquire()方法未非阻塞方法)
  stopwatch.sleepMicrosUninterruptibly(microsToWait);
  return 1.0 * microsToWait / SECONDS.toMicros(1L);
}

 

final long reserve(int permits) {
  checkPermits(permits);
  synchronized (mutex()) {
    return reserveAndGetWaitLength(permits, stopwatch.readMicros());
  }
}

 

final long reserveAndGetWaitLength(int permits, long nowMicros) {
  // 返回可获取凭证的时间
  long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
  // 2个数取最大值,当存在预先消费或提前获取凭证时momentAvailable>nowMicros
  return max(momentAvailable - nowMicros, 0);
}

 

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  // 计算storedPermits、nextFreeTicketMicros当值
  resync(nowMicros);
  long returnValue = nextFreeTicketMicros;
  double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
  // 计算容器中还需刷新的凭证数,即预先消费时会存在requiredPermits>storedPermitsToSpend
  double freshPermits = requiredPermits - storedPermitsToSpend;
  // 计算下一次获取凭证时需等待的时间,预先消费时该值为freshPermits * stableIntervalMicros
  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;
  }
  // 减少容器中的凭证数
  this.storedPermits -= storedPermitsToSpend;
  return returnValue;
}

 

void resync(long nowMicros) {
  // 如果当前时间能够获取凭证,每次获取凭证时都会计算下一次能获取凭证的时间 
  if (nowMicros > nextFreeTicketMicros) {
    // 容器里的总凭证数,即result=容器中原有凭证数+该时间点应该往容器里放多少凭证 
    storedPermits = min(maxPermits,
        storedPermits
          + (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros());
    nextFreeTicketMicros = nowMicros;
  }
}

转载于:https://my.oschina.net/u/732520/blog/2993810

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值