图解高并发限流

图解高并发限流

背景

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流

  • 缓存:缓存的目的是提升系统访问速度和增大系统处理容量
  • 降级:降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行
  • 限流:限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理

我们经常在调别人的接口的时候会发现有限制,比如微信公众平台接口、高等API等等,对方会限制每天最多调多少次或者每分钟最多调多少次

限流算法

在这里插入图片描述

计数器

在指定周期内累加访问次数,当访问次数达到设定的阈值时,出发限流策略,当进入下一个时间周期时会将访问次数清零

优缺点:实现起来比较简单,但会出现「临界问题」,如上图,短时间内的访问量超过1000,大于了规定的10s内的访问量不超过500的限定

滑动窗口

将固定窗口中分割出多个小时间窗口,分别在每个小的时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口

优缺点:实现简单,但存在突刺现象(如果在单位时间10秒内的前100ms,通过了500个请求,则后面的990ms都无法接受任何请求,也就无法应对短时间高并发)

在这里插入图片描述

漏桶

水流速度不定, 漏桶水滴的流出速度始终保持不变

优缺点:起到削峰的作用,可以应对突发流量,不足之处是无法应对突发的并发流量

令牌桶

令牌桶是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法

  • 当请求速度大于令牌的生成速度,令牌会被拿完后限流
  • 当请求速度等于令牌的生成速度,流量正常稳定处理
  • 当请求速度小于令牌的生成速度,请求可以被正常处理,且多余的令牌被丢弃
令牌桶和漏桶对比
  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
  • 令牌桶限制的是平均流入速率,允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌;漏桶限制的是常量流出速率,即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2,从而平滑突发流入速率;
  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流出速率;

RateLimiter源码赏析

Guava有两种限流模式,一种为稳定模式(SmoothBursty:令牌生成速度恒定),一种为渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值)

在这里插入图片描述

RateLimiter构造方法
    public static RateLimiter create(double permitsPerSecond) {
        return create(RateLimiter.SleepingStopwatch.createFromSystemTimer(), permitsPerSecond);
    }

    @VisibleForTesting
    static RateLimiter create(RateLimiter.SleepingStopwatch stopwatch, double permitsPerSecond) {
        RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0D);
        rateLimiter.setRate(permitsPerSecond);
        return rateLimiter;
    }

其它是一些接口方法,常用到的是acquire()和tryAcquire():

public double acquire() {}
public double acquire(int permits) {}
 
public boolean tryAcquire() {}
public boolean tryAcquire(int permits) {}
public boolean tryAcquire(long timeout, TimeUnit unit) {}
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {}
 
public final double getRate() {}
public final void setRate(double permitsPerSecond) {}

两个属性:

//计时,以后的时间都是相对时间
private final RateLimiter.SleepingStopwatch stopwatch;
//锁,RateLimiter依赖synchronized来控制并发
private volatile Object mutexDoNotUseDirectly;
SmoothRateLimiter
属性
// 当前还有多少permits没有被使用,被存下来的 permits 数量
double storedPermits;
// 最大允许缓存的 permits 数量,也就是 storedPermits 能达到的最大值
double maxPermits;
// 每隔多少时间产生一个 permit, 假如构造方法中设置每秒5个,也就是每隔 200ms 一个,这里单位是微秒,也就是 200,000
double stableIntervalMicros;
// 下一次可以获取 permits 的时间,这个时间是相对 RateLimiter 的构造时间的,是一个相对时间
private long nextFreeTicketMicros
SmoothBursty
构造方法
  • 1.0的参数传进去,赋值给变量maxBurstSeconds,也就是说最多会缓存1秒钟,会有1.0 * permitsPerSecond这么多的permits到候选池中,会带来如下的影响:
0 <= storedPermits <= maxPermits = permitsPerSecond
static RateLimiter create(RateLimiter.SleepingStopwatch stopwatch, double permitsPerSecond) {
  RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0D);
  rateLimiter.setRate(permitsPerSecond);
  return rateLimiter;
 }

SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
    super(stopwatch, null);
    this.maxBurstSeconds = maxBurstSeconds;
}
setRate
//调整速率
public final void setRate(double permitsPerSecond) {
    Preconditions.checkArgument(permitsPerSecond > 0.0D && !Double.isNaN(permitsPerSecond), "rate must be positive");
  //synchronized关键字修饰控制并发
    synchronized(this.mutex()) {
        this.doSetRate(permitsPerSecond, this.stopwatch.readMicros());
    }
}
doSetRate & resync
final void doSetRate(double permitsPerSecond, long nowMicros) {
    this.resync(nowMicros);//同步调整参数
  //计算stableIntervalMicros属性
 // 每隔多少时间产生一个 permit, 假如构造方法中设置每秒5个,也就是每隔 200ms 一个,这里单位是微秒,也就是 200,000
    double stableIntervalMicros = (double)TimeUnit.SECONDS.toMicros(1L) / permitsPerSecond;
    this.stableIntervalMicros = stableIntervalMicros;
    this.doSetRate(permitsPerSecond, stableIntervalMicros);
}
	
//调整storedPermits和nextFreeTicketMicros的值
    void resync(long nowMicros) {
      //如果nextFreeTicket已经过期了需要重新rebase  nextFreeTicket 到当前的时间
        if (nowMicros > this.nextFreeTicketMicros) {
            this.storedPermits = Math.min(this.maxPermits, this.storedPermits + (double)(nowMicros - this.nextFreeTicketMicros) / this.coolDownIntervalMicros());
            this.nextFreeTicketMicros = nowMicros;
        }

    }
doSetRate
  • resync方法后,进入到doSetRate方法,计算storedPermits的值
  • 这个方法,原来的 RateLimiter 是用某个 permitsPerSecond 值初始化的,现在要调整这个频率。对于 maxPermits 来说,是重新计算,而对于 storedPermits 来说,是做等比例的缩放
 
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
            double oldMaxPermits = this.maxPermits;
            this.maxPermits = this.maxBurstSeconds * permitsPerSecond;
            if (oldMaxPermits == 1.0D / 0.0) {
                this.storedPermits = this.maxPermits;
            } else {
                this.storedPermits = oldMaxPermits == 0.0D ? 0.0D : this.storedPermits * this.maxPermits / oldMaxPermits;
            }
        }
acquire
    public double acquire() {
        return this.acquire(1);
    }

    public double acquire(int permits) {
      //如果当前不能获取到permits,需要返回需要等到的时间长度microsToWait
        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);
        synchronized(this.mutex()) {
            return this.reserveAndGetWaitLength(permits, this.stopwatch.readMicros());
        }
    }
	    final long reserveAndGetWaitLength(int permits, long nowMicros) {
        //返回nextFreeTicketMicros
        long momentAvailable = this.reserveEarliestAvailable(permits, nowMicros);
        //计算时长
        return Math.max(momentAvailable - nowMicros, 0L);
    }
reserveEarliestAvailable
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  // 同步,更新 storedPermits 和 nextFreeTicketMicros 
  resync(nowMicros);
  // 返回值 nextFreeTicketMicros,刚刚已经做了 resync 了,此时它是最新的正确的值
  long returnValue = nextFreeTicketMicros;
  // storedPermits 中可以使用多少个 permits
  double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
  // storedPermits 中不够的部分
  double freshPermits = requiredPermits - storedPermitsToSpend;
  // 为了这个不够的部分,需要等待多久时间
  long waitMicros =
      storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) // 这部分固定返回 0
          + (long) (freshPermits * stableIntervalMicros);
  // 将 nextFreeTicketMicros 往前推
  try {
    this.nextFreeTicketMicros = LongMath.checkedAdd(this.nextFreeTicketMicros, waitMicros);
  } catch (ArithmeticException var13) {
    this.nextFreeTicketMicros = 9223372036854775807L;
  }
    // storedPermits 减去被拿走的部分
  this.storedPermits -= storedPermitsToSpend;
  return returnValue;
}
  • 获取 permits 的时候,其实是获取了两部分,一部分来自于存量 storedPermits,存量不够的话,另一部分来自于预占未来的 freshPermits,返回值是 nextFreeTicketMicros 的旧值,因为只要到这个时间点,就说明当次 acquire 可以成功返回了,而不管 storedPermits 够不够。如果 storedPermits 不够,会将 nextFreeTicketMicros 往前推一定的时间,预占了一定的量

Reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值