RateLimiter源码解析

目录

一、令牌桶算法

1 原理

2 实现方式

二、RateLimiter使用方式及原理

1. acquire()阻塞等待请求通过

2. tryAcquire()判断请求是否允许通过

3.create()初始化

4.SmoothWarmingUp平滑预热限流

三、自己实现一个简单的令牌桶

1. 最简单的实现

2. 能预消费的实现

四、参考文献


一、令牌桶算法

1 原理

令牌桶算法会限制请求以基本恒定的速率通过服务端,同时又允许一定的突发流量。

基本原理:维持一个令牌桶,以恒定的速度往桶里添加令牌。请求到来时,如果桶里有足够的令牌,允许请求通过,否则被限流。

可以设置一个请求需要的令牌数,比如1个请求需要5个令牌,那桶中需要又至少5个令牌,才能放行该请求。

桶的容量可以按需要设置,如果桶里令牌满了,再生产的令牌就会丢弃。通过设置这个容量,可以控制允许突发流量的大小,比如桶容量是10,一下过来10笔请求,每个请求需要1个令牌,那这10个请求都会立即放行。

2 实现方式

令牌桶一般有两种实现方式,一种是启动一个线程,以恒定的速度向桶中添加令牌,这种开销比较大;还有一种方式是当请求到来时,计算能够产生的令牌数,再判断能否允许请求通过,google开源的Google Guava RateLimiter就是使用的第二种实现方式。

二、RateLimiter使用方式及原理

RateLimiter有两种不同的限流方式:平滑突发限流(SmoothBursty)平滑预热限流(SmoothWarmingUp)。在限流器初始化时,平滑突发限流按照设定的限流速度生产令牌,平滑预热限流则是以低于设定阈值的速度生产令牌,直到一段时间后才按设定阈值来生产令牌。比如我设定的限流阈值是10/s,则100ms应该生产1个令牌,但平滑预热限流器在初始化时,会以更慢的速度比如50ms才生产1个令牌,直到比如1s之后才开始100ms生产1个令牌,这样保证应用启动时,不会有大量突发流量涌入。

如下方法可以创建一个平滑突发限流器(SmoothBursty):

RateLimiter limiter = RateLimiter.create(10);//阈值是10/s

1. acquire()阻塞等待请求通过

acquire()方法会阻塞等待,直到请求允许通过,并返回等待时间。因为我们设置的阈值是10/s,也就是每0.1s会允许一个请求通过,看打印输出也是间隔基本等于0.1s。另外第一笔请求等待时间是0,也就是立即放行了。

RateLimiter limiter = RateLimiter.create(10);//阈值是10/s
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());

// 打印输出:
// 0.0
// 0.09723
// 0.095937
// 0.097646
// 0.098113

acquire()方法可以接收一个int类型的入参,表示该笔请求需要几个令牌,如果没传的话,默认1个令牌。我们试下第一笔请求设置需要10个令牌,发现第一笔请求等待时间还是0,但下笔请求等待时间要将近1s,这是因为平滑突发限流器初始化时允许“提前消费”,即放过第一笔请求,第一笔请求需要的令牌需要在之后的时间补上,所以等待1s后才允许第二笔请求通过。

RateLimiter limiter = RateLimiter.create(10);//阈值是10/s
System.out.println(limiter.acquire(10));//该笔请求需要10个令牌
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());

// 打印输出:
// 0.0
// 0.99686
// 0.095173
// 0.096402
// 0.096716

我们看下

com.google.common.util.concurrent.RateLimiter#acquire(int)

public double acquire(int permits) {
	//1. 计算等待时间
    long microsToWait = reserve(permits);
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
	//将ms转成s
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
}

final long reserve(int permits) {
    //2. 检查permits>0
	checkPermits(permits);
	//3. 加锁
    synchronized (mutex()) {
      return reserveAndGetWaitLength(permits, stopwatch.readMicros());
    }
}

final long reserveAndGetWaitLength(int permits, long nowMicros) {
    //4. 计算可以提供令牌的时间
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    //5. 可以提供令牌的时间 - 当前时间,得到等待时间
    return max(momentAvailable - nowMicros, 0);
}

看下如何计算 可以提供令牌的时间 的:

com.google.common.util.concurrent.SmoothRateLimiter#reserveEarliestAvailable

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    //1. 根据当前时间更新令牌数、‘下次允许请求通过的时间:nextFreeTicketMicros’等
    resync(nowMicros);
    long returnValue = nextFreeTicketMicros;
    //3. 计算还需要生产多少令牌freshPermits
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    double freshPermits = requiredPermits - storedPermitsToSpend;
    //4. 计算产生freshPermits个令牌需要多长时间
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)//用于平滑预热限流时,减慢令牌产生速率,增加生产令牌时间
            + (long) (freshPermits * stableIntervalMicros);//需要生产的令牌数 * 产生令牌速率 = 产生令牌的消耗时间
	//5. 更新下次允许请求通过的时间
    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    //6. 更新存储的令牌数
    this.storedPermits -= storedPermitsToSpend;
    //7. 返回下次允许请求通过的时间
    //这里是取的更新前的nextFreeTicketMicros,所以可以看出允许请求提前消费,花费未来的时间产生令牌,让下次请求等待更多的时间
    return returnValue;
}

void resync(long nowMicros) {
    //2. 如果当前时间大于下次允许请求通过的时间的话,
    if (nowMicros > nextFreeTicketMicros) {
      //计算这段时间产生的令牌数
      //coolDownIntervalMicros()方法计算产生令牌的时间间隔,对于平滑突发限流就是 1000/阈值,比如阈值是10,那产生令牌的时间间隔就是100ms
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      //更新令牌数storedPermits,maxPermits就是设置的阈值
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
}

2. tryAcquire()判断请求是否允许通过

tryAcquire()方法会判断请求是否允许通过,返回true or false,看下面的例子,第一笔请求通过,后面请求因为间隔时间太短,都被拒绝通过。

RateLimiter limiter = RateLimiter.create(10);

System.out.println(limiter.tryAcquire());
System.out.println(limiter.tryAcquire());
System.out.println(limiter.tryAcquire());
System.out.println(limiter.tryAcquire());
System.out.println(limiter.tryAcquire());

//打印输出:
// true
// false
// false
// false
// false

我们在限流器初始化后先睡眠1s,这时连续几笔请求都通过了,因为1s的时间里限流器已经生成了10个令牌,足以让这几笔请求都通过,所以这也能看到令牌桶算法是允许一定突发流量的,这也是令牌桶和漏桶算法的最大区别,漏桶是不允许突发流量通过的。

RateLimiter limiter = RateLimiter.create(10);

TimeUnit.SECONDS.sleep(1);//睡眠1s
System.out.println(limiter.tryAcquire());
System.out.println(limiter.tryAcquire());
System.out.println(limiter.tryAcquire());
System.out.println(limiter.tryAcquire());
System.out.println(limiter.tryAcquire());

//打印输出:
// true
// true
// true
// true
// true

我们看下tryAcquire()源码,核心逻辑跟acquire()方法是一样的:

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();
      //如果本次请求不允许通过,直接返回false
      if (!canAcquire(nowMicros, timeoutMicros)) {
        return false;
      } else {
        //reserveAndGetWaitLength()方法上面讲过了,会返回允许本次请求通过的话,需要的等待时间
        microsToWait = reserveAndGetWaitLength(permits, nowMicros);
      }
    }
    //睡眠等待时间
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return true;
}

3.create()初始化

回过头来我们再看下限流器初始化时做了什么:

public abstract class RateLimiter {
  public static RateLimiter create(double permitsPerSecond) {
    return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
  }

  @VisibleForTesting
  static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
    //这里的maxBurstSeconds * permitsPerSecond 就是桶中最多存放的令牌数
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    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) {
    //根据当前时间更新令牌数、下次允许请求通过的时间等,上面有讲过这个方法
    resync(nowMicros);
    //计算产生令牌的时间间隔
    double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
    this.stableIntervalMicros = stableIntervalMicros;
    doSetRate(permitsPerSecond, stableIntervalMicros);
  }
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
    double oldMaxPermits = this.maxPermits;
    maxPermits = maxBurstSeconds * permitsPerSecond;
    if (oldMaxPermits == Double.POSITIVE_INFINITY) {
        // if we don't special-case this, we would get storedPermits == NaN, below
        storedPermits = maxPermits;
    } else {
        storedPermits =
        (oldMaxPermits == 0.0)
        ? 0.0 // 初始化时,maxPermits是double类型自动赋值0
        : storedPermits * maxPermits / oldMaxPermits;
    }
}

4.SmoothWarmingUp平滑预热限流

上面讲了平滑突发限流SmoothBursty,下面看下SmoothWarmingUp平滑预热限流是怎么实现的,SmoothWarmingUp实现方式非常地绕,不好理解。

先看下SmoothWarmingUp的使用跟SmoothBursty有何不同:

如下创建了一个SmoothWarmingUp限流器,10标识限流阈值是10/s,1表示预热期长度,后面参数是单位。

RateLimiter rateLimiter = RateLimiter.create(10, 1, TimeUnit.SECONDS);

调acquire()方法测试下每笔请求通过的等待时间,如果是SmoothBursty限流的话,每笔请求通过时间应该都是0.1s,但SmoothWarmingUp限流下前几笔请求等待时间都比较长,但逐渐缩短,但到后面稳定在0.1s,把前几笔不是0.1s的时间加起来恰好是1s左右,就是我们设置的预热期时间长度,也就是说明在预热器内产生令牌的速度比正常要慢,然后速度逐渐加快,过了预热期后速度刚好达到正常速度。

RateLimiter rateLimiter = RateLimiter.create(10, 1, TimeUnit.SECONDS);
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());
System.out.println(rateLimiter.acquire());

//打印输出:
// 0.0
// 0.278217
// 0.235424
// 0.195448
// 0.155514
// 0.11602
// 0.099218
// 0.096669
// 0.097302
// 0.099566
// 0.097036
// 0.0948
// 0.098321

看下其实现原理,下面是SmoothWarmingUp源码注释中的一个图,还是比较晦涩难懂,按我的理解,横轴是令牌数,纵轴是产生令牌的时间间隔,

maxPermits:最大令牌数

thresholdPermits:阈值令牌数

stableinterval:稳定时产生令牌的时间间隔

coleinterval:预热期最大的令牌时间间隔

这个曲线要从右往左看,限流器初始化后横轴坐标在maxPermits处,随着请求到来,不断有令牌通过,在横轴上往左走,产生令牌的时间间隔随之变小,直到超过阈值点thresholdPermits后,产生令牌的时间间隔维持在稳定的stableinterval上。

/**
*          ^ throttling
*          |
*    cold  +                  /
* interval |                 /.
*          |                / .
*          |               /  .   ← "warmup period" is the area of the trapezoid between
*          |              /   .     thresholdPermits and maxPermits
*          |             /    .
*          |            /     .
*          |           /      .
*   stable +----------/  WARM .
* interval |          .   UP  .
*          |          . PERIOD.
*          |          .       .
*        0 +----------+-------+--------------→ storedPermits
*          0 thresholdPermits maxPermits
*/

比如上面创建的SmoothWarmingUp限流器,10标识限流阈值是10/s,1表示预热期长度,后面参数是单位。初始化后stableinterval=100000微秒=100ms,即稳定后100ms通过一个令牌,coleinterval=3*stableinterval,maxPermits=10,thresholdPermits=5,计算方式在下面doSetRate()方法中。


void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
  double oldMaxPermits = maxPermits;
  //coldFactor=3,是初始化时写死的
  double coldIntervalMicros = stableIntervalMicros * coldFactor;
  thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;
  maxPermits =
      thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);
  slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);
  if (oldMaxPermits == Double.POSITIVE_INFINITY) {
    storedPermits = 0.0;
  } else {
    storedPermits =
        (oldMaxPermits == 0.0)
            ? maxPermits // 初始化时,storedPermits为maxPermits
            : storedPermits * maxPermits / oldMaxPermits;
  }
}

SmoothWarmingUp与SmoothBursty在计算过程中,大部分代码是一样的,最大区别在storedPermitsToWaitTime()方法,在上面讲acquire()方法源码中有看到过,该方法计算花掉桶里的permitsToTake个令牌需要额外等待多久,SmoothBursty限流器直接返回0,SmoothWarmingUp限流器会计算预热期多花费的时间。

//花掉桶里的permitsToTake个令牌需要额外等待多久
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
  double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
  long micros = 0;
  // measuring the integral on the right part of the function (the climbing line)
  if (availablePermitsAboveThreshold > 0.0) {
    double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
    // TODO(cpovirk): Figure out a good name for this variable.
    double length =
        permitsToTime(availablePermitsAboveThreshold)
            + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);
    micros = (long) (permitsAboveThresholdToTake * length / 2.0);
    permitsToTake -= permitsAboveThresholdToTake;
  }
  // measuring the integral on the left part of the function (the horizontal line)
  micros += (long) (stableIntervalMicros * permitsToTake);
  return micros;
}

三、自己实现一个简单的令牌桶

1. 最简单的实现

import lombok.Data;

import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * @author qiuyu
 * @date 2023/7/1 4:13 下午
 */
@Data
public class MyRateLimiter1 {
    /**
     * 令牌桶容量
     */
    private double maxPermits;

    /**
     * 存储的令牌数
     */
    private double storePermits;

    /**
     * 上次请求的时间,ms
     */
    private long lastTimeMill;

    /**
     * 产生令牌的时间间隔,ms
     */
    private double intervelMill;

    private MyRateLimiter1() {
    }

    private static MyRateLimiter1 create(double permitsPerSecond) {
        MyRateLimiter1 limiter = new MyRateLimiter1();
        limiter.setMaxPermits(permitsPerSecond);
        limiter.setIntervelMill(1.0 * 1000 / permitsPerSecond);
        limiter.setLastTimeMill(System.currentTimeMillis());
        return limiter;
    }

    /**
     * 是否允许请求通过
     * @param permits 请求需要的令牌数
     * @return 
     */
    public boolean acquire(double permits) {
        long now = System.currentTimeMillis();
        if (now > lastTimeMill) {
            double newPermits = (now - lastTimeMill) / intervelMill;
            this.storePermits = Math.min(maxPermits, this.storePermits + newPermits);
            this.lastTimeMill = now;
        }
        if (this.storePermits >= permits) {
            this.storePermits = this.storePermits - permits;
            return true;
        } else {
            return false;
        }
    }
}

2. 能预消费的实现

上面的实现是最简单的,下面再实现一个能够预消费的。

再回顾下预消费的意思,指的是本次消费的令牌数不够了,先把请求放行,不够的令牌数在未来产生,由下笔请求买单。比如现在桶里有1个令牌,过来1个请求,需要消耗5个令牌,先把该笔请求放行,差的4个令牌在未来的时间去生产,所以下笔请求至少要等待4个令牌的产生时间。那为什么这样设计呢?RateLimiter的解释是为了突发性,假如令牌产生速度是1/s,系统平时很空闲,突然过来1个需要100个令牌的请求,那需要等100s再执行吗?这样是有点浪费资源的,所以直接放行,后面再补。

import com.google.common.base.Stopwatch;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * 令牌桶限流器
 * 
 * @author qiuyu
 * @date 2023/6/27 8:08 下午
 */
@Data
@Slf4j
public class MyRateLimiter {
    /**
     * 下次请求可以通过的时间
     */
    private long nextFreeTicketMicros = 0L;

    /**
     * 当前存储的令牌数
     */
    private double storedPermits = 0;

    /**
     * 桶里的最大令牌数
     */
    private double maxPermits;

    /**
     * 生产令牌的时间间隔
     */
    private double intervalMicros;

    /**
     * 时间表
     */
    private Stopwatch stopWatch;

    private MyRateLimiter() {
    }

    public static MyRateLimiter create(double permitsPerSecond) {
        check(permitsPerSecond);
        MyRateLimiter rateLimiter = new MyRateLimiter();
        rateLimiter.setIntervalMicros(SECONDS.toMicros(1L) / permitsPerSecond);
        rateLimiter.setMaxPermits(permitsPerSecond);
        rateLimiter.setStopWatch(Stopwatch.createStarted());
        rateLimiter.setNextFreeTicketMicros(rateLimiter.getStopWatch().elapsed(MICROSECONDS));
        return rateLimiter;
    }

    public double acquire() throws InterruptedException {
        return acquire(1);
    }

    /**
     * 放行requiredPermits个令牌,返回等待时间
     * @param requiredPermits 需要的令牌数
     * @return 等待时间
     * @throws InterruptedException
     */
    public double acquire(double requiredPermits) throws InterruptedException {
        long nowMicros = stopWatch.elapsed(MICROSECONDS);
        long waitMicros = getWaitLengthInAcquirePermits(requiredPermits, nowMicros);
        MICROSECONDS.sleep(waitMicros);
        return 1.0 * waitMicros / SECONDS.toMicros(1L);
    }

    /**
     * 获取允许requiredPermits个令牌通过的话,需要的等待时间
     * @param requiredPermits 需要的令牌数
     * @param nowMicros 当前时间
     * @return 等待时间
     */
    private long getWaitLengthInAcquirePermits(double requiredPermits, long nowMicros){
        if (nowMicros > nextFreeTicketMicros) {
            double newPermits = (nowMicros - nextFreeTicketMicros) / intervalMicros;
            storedPermits = Math.min(maxPermits, storedPermits + newPermits);
            nextFreeTicketMicros = nowMicros;
        }
        long waitMicros = nextFreeTicketMicros - nowMicros;
        double freshPermits = Math.max(requiredPermits - storedPermits, 0);
        nextFreeTicketMicros = nextFreeTicketMicros + (long) (freshPermits * intervalMicros);
        storedPermits = Math.max(storedPermits - requiredPermits, 0);

        return waitMicros;
    }
    
    public boolean tryAcquire() throws InterruptedException {
        return tryAcquire(1);
    }

    /**
     * 尝试放行requiredPermits个令牌,返回是否允许放行
     * @param requiredPermits 需要的令牌数
     * @return 是否允许本次请求通过
     * @throws InterruptedException
     */
    public boolean tryAcquire(double requiredPermits) throws InterruptedException {
        long nowMicros = stopWatch.elapsed(MICROSECONDS);
        boolean isAcquire = nowMicros >= nextFreeTicketMicros;
        if (isAcquire) {
            long waitMicros = getWaitLengthInAcquirePermits(requiredPermits, nowMicros);

            MICROSECONDS.sleep(waitMicros);
            return true;
        }
        return false;
    }

    private static void check(double permitsPerSecond) {
        if (permitsPerSecond <= 0.0 || Double.isNaN(permitsPerSecond)) {
            throw new RuntimeException("permitsPerSecond is illegal");
        }
    }
}

测试一下,可以看到acquire()方法输出是符合预期的,每隔0.1s通过一次请求:

public static void main(String[] args) throws InterruptedException {
    MyRateLimiter myRateLimiter = MyRateLimiter.create(10);
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
}

//打印输出:
// 0.0
// 0.098591
// 0.095315
// 0.099849
// 0.098357
// 0.099653

把第一笔请求需要令牌数设为10,看到第二笔请求等待了1s才允许通过,也是符合预期的:

public static void main(String[] args) throws InterruptedException {
    MyRateLimiter myRateLimiter = MyRateLimiter.create(10);
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
   System.out.println(myRateLimiter.acquire());
}

//打印输出:
// 0.0
// 0.998834
// 0.094905
// 0.099425
// 0.099982
// 0.095753

再看下tryAcquire()方法,也是符合预期的:

public static void main(String[] args) throws InterruptedException {
    MyRateLimiter myRateLimiter = MyRateLimiter.create(10);
    System.out.println(myRateLimiter.tryAcquire());
    System.out.println(myRateLimiter.tryAcquire());
    System.out.println(myRateLimiter.tryAcquire());
    System.out.println(myRateLimiter.tryAcquire());
    System.out.println(myRateLimiter.tryAcquire());
}

//打印输出:
// true
// false
// false
// false
// false
public static void main(String[] args) throws InterruptedException {
    MyRateLimiter myRateLimiter = MyRateLimiter.create(10);
    SECONDS.sleep(1); //睡眠1s
    System.out.println(myRateLimiter.tryAcquire());
    System.out.println(myRateLimiter.tryAcquire());
    System.out.println(myRateLimiter.tryAcquire());
    System.out.println(myRateLimiter.tryAcquire());
    System.out.println(myRateLimiter.tryAcquire());
}

//打印输出:
// true
// true
// true
// true
// true

当然这只是最简单的实现,RateLimiter还做了许多其他的事情,比如支持高并发,我们自己写的是有并发问题的;RateLimiter也有很多复杂的算法来提高精度、避免边界问题等;其功能已经经过了广泛测试和使用,是比较稳定可靠的;

举个边界问题的例子,在对时间求和时用了下面方法避免溢出:

public static long saturatedAdd(long a, long b) {
    long naiveSum = a + b;
    if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) {
        // a ^ b:a和b按位异或,相同得0,不同得1,(a ^ b) < 0说明首位不同,即一个正数一个负数,这样的话相加肯定不会溢出
        // (a ^ naiveSum) >= 0,这里用(b ^ naiveSum) >= 0判断也一样。说明a(或b)和sum是同为正或同为负,所以不可能是两个大正数或大负数相加,因为两个大正数相加溢出sum是负的,两个大负数相加溢出sum是正的
        return naiveSum;
    }
    //此时已经判断有溢出了,如果sum是负的,说明是两个大正数相加,结果应该是Long.MAX_VALUE;如果sum是正的,说明是两个大负数相加,结果应该是Long.MIN_VALUE
    //Long.SIZE是64,naiveSum右移63位,取出最高位,跟1异或相当于取反,得到是1的话跟Long.MAX_VALUE相加溢出得到Long.MIN_VALUE
    return Long.MAX_VALUE + ((naiveSum >>> (Long.SIZE - 1)) ^ 1);
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Spring Boot中关闭RateLimiter,你可以采取以下步骤: 1. 导入所需的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> ``` 2. 创建一个自定义注解,用于标记需要限流的方法: ```java import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimited { String value() default ""; } ``` 3. 创建一个切面类,用于实现限流逻辑: ```java import com.google.common.util.concurrent.RateLimiter; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class RateLimitAspect { private final RateLimiter rateLimiter = RateLimiter.create(10); // 设置每秒最大访问次数 @Pointcut("@annotation(com.example.RateLimited)") public void rateLimited() {} @Around("rateLimited()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { if (rateLimiter.tryAcquire()) { return joinPoint.proceed(); } else { throw new RuntimeException("请求过于频繁,请稍后再试!"); } } } ``` 4. 在需要进行限流的方法上添加自定义注解: ```java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class MyController { @RateLimited // 添加此注解进行限流 @GetMapping("/my-api") public String myApi() { // 处理业务逻辑 return "Hello World!"; } } ``` 这样,当请求频率超过每秒最大访问次数时,将会抛出一个RuntimeException。你可以根据实际需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值