其他分享
首页 > 其他分享> > 【干货分享】深入理解高可用之限流

【干货分享】深入理解高可用之限流

作者:互联网

关于故障

故障产生的原因可以分为以下几大类:

  1. 网络问题:网络链接出现问题、宽带出现堵塞。
  2. 性能问题:数据库慢SQL、Java full gc、硬盘IO过大、CPU飙高、内存不足。
  3. 安全问题:被网络攻击,如DDoS。
  4. 运维问题:系统不断进行更新和修改。
  5. 管理问题:没有梳理出关键服务及服务依赖关系。
  6. 硬件问题:硬盘损坏、网卡故障、机房掉电。

换个角度从流量时间顺序来说,故障来自四个方面:

  1. 上游流量突增。
  2. 系统本身出现问题,比如断电、磁盘打满等。
  3. 人为变更导致,比如发布上线。
  4. 下游服务出现异常。

所以说故障是正常的,而且是常见的,“Everything will fails”

不要尝试着去避免故障,而是把处理故障的代码当成正常的功能做在架构里写在代码中。

降级

降级(Degradation)本质是为了解决资源不足和访问量过大的问题,暂时牺牲掉一些东西以保证整个系统的平稳运行。

对服务调用方来说,服务降级可以在被调用服务不可用时,通过临时的替代方案向上提供有损服务,保证业务柔性可用;

对于服务提供方来说,服务降级极大的减轻了对服务提供方的压力,有助于服务提供方快速恢复。

限流可以认为是服务降级的一种,限流就是限制系统的输入和输出流量来达到保护系统的目的,很多地方都有限流的思想,如数据库连接池,线程池,Nginx下的用于限制瞬时并发连接数的limit_conn模块,限制每秒平均速率的limit_req模块。

限制接口每秒可以通过的请求数,超出的流量可以选择延迟处理、部分拒绝或者直接拒绝。

降级是一种思想,限流、熔断是措施。

降级的三种模式:

  1. 强制降级:在强制降级模式下,客户端对某一服务的调用会全部降级,降级后会根据配置的降级策略返回。强制降级的开关需要由开发负责人手动打开和关闭,一般用于在紧急情况下对后端调用做全部降级,并在恢复正常后手动关闭降级开关。
  2. 失败降级:在失败降级模式下,客户端始终会调用后端服务,但仅当调用失败时才进行服务降级,这个时候将根据配置的降级策略返回。
  3. 自动降级:在自动降级模式下,客户端会根据当前的服务调用情况,自动识别是否需要降级。如果需要降级,会根据配置的降级策略返回;如果不需要降级,就进行正常的服务调用。

限流策略

限流的实现方式

在这里插入图片描述

在这里插入图片描述

漏斗算法和令牌桶算法使用场景不同,并不能说令牌桶一定比漏斗算法好。

令牌桶可以用来保护自己,主要用来对调用者频率进行限流,为的是让自己不被打垮。所以如果自己本身有处理能力的时候,如果流量突发(实际消费能力强于配置的流量限制),那么实际处理速率可以超过配置的限制。

而漏桶算法,这是用来保护他人,也就是保护他所调用的系统。主要场景是,当调用的第三方系统本身没有保护机制,或者有流量限制的时候,我们的调用速度不能超过他的限制,由于我们不能更改第三方系统,所以只有在主调方控制。这个时候,即使流量突发,也必须舍弃。因为消费能力是第三方决定的。

限流策略

  1. 单机限流:单机固定限流,基于令牌桶算法实现,可以应对突发流量,默认桶中会保存设置qps数量的令牌。
  2. 集群限流(非精确):单机限流,通过指定集群的QPS,单机QPS会随着业务扩容缩容,根据业务的机器数动态的计算。
  3. 集群限流(精确):基于Redis的计数实现精确集群限流,每次限流需要和Redis进行通信,有1ms左右的性能损耗,如果Redis挂了,将不进行限流。
  4. 集群限频:内部通过uuid关键字,实现各个uuid的访问频次,比如有用户a,b,c,d… z,访问接口/index,目前的需求是限制每个用户的访问频次 2次/ 5s,可以通过集群限频并基于redis的计数实现,用户id+接口名作为key,过期时间为5s,保存在redis中
  5. 集群配额:和集群限频的实现原理一致,但是每种策略的限流只能创建一个,如果遇到下面的需求,可以使用该策略,例如:需要限制每个用户的访问频次 2次/ 5s,同时还要限制每天的调用总数为2000,对于这二个需求,就可以创建一个集群配额,限制每个用户每天的调用总次数

最佳实践

TODO配置

源码分析

        RateLimiter rateLimiter = RateLimiter.create(2);
        
        boolean res = rateLimiter.tryAcquire();
        
        rateLimiter.getRate();
        
        rateLimiter.setRate(100);
        
        rateLimiter.tryAcquire();


@ThreadSafe
@Beta
public abstract class RateLimiter {

  public static RateLimiter create(double permitsPerSecond) {

    return create(SleepingStopwatch.createFromSystemTimer(), permitsPerSecond);
  }


  static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond) {
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }


  public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
    checkArgument(warmupPeriod >= 0, "warmupPeriod must not be negative: %s", warmupPeriod);
    return create(SleepingStopwatch.createFromSystemTimer(), permitsPerSecond, warmupPeriod, unit);
  }

  @VisibleForTesting
  static RateLimiter create(
      SleepingStopwatch stopwatch, double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
    RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }

  /**
   * The underlying timer; used both to measure elapsed time and sleep as necessary. A separate
   * object to facilitate testing.
   */
  private final SleepingStopwatch stopwatch;

  // Can't be initialized in the constructor because mocks don't call the constructor.
  private volatile Object mutexDoNotUseDirectly;

  private Object mutex() {
    Object mutex = mutexDoNotUseDirectly;
    if (mutex == null) {
      synchronized (this) {
        mutex = mutexDoNotUseDirectly;
        if (mutex == null) {
          mutexDoNotUseDirectly = mutex = new Object();
        }
      }
    }
    return mutex;
  }

  RateLimiter(SleepingStopwatch stopwatch) {
    this.stopwatch = checkNotNull(stopwatch);
  }

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

  abstract void doSetRate(double permitsPerSecond, long nowMicros);

  public final double getRate() {
    synchronized (mutex()) {
      return doGetRate();
    }
  }

  abstract double doGetRate();


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

  public double acquire(int permits) {
    long microsToWait = reserve(permits);
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
  }


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

  /**
   * Acquires a permit from this {@code RateLimiter} if it can be obtained
   * without exceeding the specified {@code timeout}, or returns {@code false}
   * immediately (without waiting) if the permit would not have been granted
   * before the timeout expired.
   *
   * <p>This method is equivalent to {@code tryAcquire(1, timeout, unit)}.
   *
   * @param timeout the maximum time to wait for the permit. Negative values are treated as zero.
   * @param unit the time unit of the timeout argument
   * @return {@code true} if the permit was acquired, {@code false} otherwise
   * @throws IllegalArgumentException if the requested number of permits is negative or zero
   */
  public boolean tryAcquire(long timeout, TimeUnit unit) {
    return tryAcquire(1, timeout, unit);
  }

 
  public boolean tryAcquire(int permits) {
    return tryAcquire(permits, 0, MICROSECONDS);
  }

  public boolean tryAcquire() {
    return tryAcquire(1, 0, MICROSECONDS);
  }


  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();
      if (!canAcquire(nowMicros, timeoutMicros)) {
        return false;
      } else {
        microsToWait = reserveAndGetWaitLength(permits, nowMicros);
      }
    }
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return true;
  }

  private boolean canAcquire(long nowMicros, long timeoutMicros) {
    return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
  }

  /**
   * Reserves next ticket and returns the wait time that the caller must wait for.
   *
   * @return the required wait time, never negative
   */
  final long reserveAndGetWaitLength(int permits, long nowMicros) {
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    return max(momentAvailable - nowMicros, 0);
  }

  /**
   * Returns the earliest time that permits are available (with one caveat).
   *
   * @return the time that permits are available, or, if permits are available immediately, an
   *     arbitrary past or present time
   */
  abstract long queryEarliestAvailable(long nowMicros);


  abstract long reserveEarliestAvailable(int permits, long nowMicros);

  @Override
  public String toString() {
    return String.format("RateLimiter[stableRate=%3.1fqps]", getRate());
  }

  @VisibleForTesting
  abstract static class SleepingStopwatch {

    abstract long readMicros();

    abstract void sleepMicrosUninterruptibly(long micros);

    static final SleepingStopwatch createFromSystemTimer() {
      return new SleepingStopwatch() {
        final Stopwatch stopwatch = Stopwatch.createStarted();

        @Override
        long readMicros() {
          return stopwatch.elapsed(MICROSECONDS);
        }

        @Override
        void sleepMicrosUninterruptibly(long micros) {
          if (micros > 0) {
            Uninterruptibles.sleepUninterruptibly(micros, MICROSECONDS);
          }
        }
      };
    }
  }

  private static int checkPermits(int permits) {
    checkArgument(permits > 0, "Requested permits (%s) must be positive", permits);
    return permits;
  }


  @Override
  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    resync(nowMicros);
    long returnValue = nextFreeTicketMicros;
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    double freshPermits = requiredPermits - storedPermitsToSpend;

    long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
        + (long) (freshPermits * stableIntervalMicros);

    this.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
  }

标签:降级,return,permits,long,干货,限流,stopwatch,分享
来源: https://blog.csdn.net/m0_37578675/article/details/119033278