限流的几种实现

算法

流量限制

计数器
  • 统计单位时间内请求数。超过直接拒绝。
  • 特点:
    • 实现简单。
    • 单位时间一开始就消耗完,剩余时间都会拒绝,即突刺消耗。
滑动窗口
  • 计数器的精细化(微分+积分),按照时间窗口计数和拒绝。
  • 特点:
    • 减弱突刺流量的影响:
      • 切换计数器时的双倍峰值。
      • 减弱突刺消耗的影响范围。
    • 消耗更多内存CPU统计并计算限流。
漏桶
  • 缓冲区缓存,系统恒定速率处理请求。
    210125.leak.png
  • 特点:
    • 消费匀速。
    • 请求可能有一定的等待延迟。
    • 突发流量会等待较久或者丢弃。
令牌桶
  • 改进漏桶。某种策略生成令牌,系统消耗令牌处理请求210125.token.png
  • 特点:
    • 控制生产速率。
    • 可以消费一定的突发流量。

并发度限制

  • 限制线程数、连接数等。
  • 特点:
    • 限制边界更加严格,隔离度高。
    • 并发阈值调优困难。

开源限流组件

RateLimiter

  • 模式:
    • SmoothBursty模式:每秒钟发放N个令牌,也允许预先借用一定数量的令牌。
    • SmoothWarmingUp模式:在系统刚刚启动的时候,发放令牌逐渐增加到设定的最高阈值。
  • 令牌桶算法:允许突发流量。
  • 仅单机限流。
核心变量
  • storedPermits:当前存储令牌数
  • stableIntervalMicros:添加令牌的时间间隔。预热模式启动时逐渐减小到阈值。
  • nextFreeTicketMicros:下一次可以获取令牌的时刻。
流程
  • 根据请求的令牌数计算sleep时间,sleep线程。
  public double acquire(int permits) {
    long microsToWait = reserve(permits);
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
  }
  • reserve双重加锁串行计算sleep时间。
  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);
    return max(momentAvailable - nowMicros, 0);
  }
  • 如果nextFreeTicketMicros为过去,刷新令牌桶:
    • storedPermits,不超过最大值。
    • nextFreeTicketMicros到当前。
 void resync(long nowMicros) {
    if (nowMicros > nextFreeTicketMicros) {
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
  }

  • 计算等待时间、消耗令牌:
    • 本次请求等待到 nextFreeTicketMicros
    • 如果令牌不够则预支,累加到 nextFreeTicketMicros
    • 消耗桶中的 storedPermits
  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 = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
  }

Sentinel

  • 模式:
    • 排队等待。类似ratelimiter的SmoothBursty模式。
    • 预热模式。类似ratelimiter的SmoothWarmingUp模式。
    • 快速失败。环形滑动窗口统计qps,超出则拒绝。
流程
  • 职责链模式,spi加载。
    210202.sentinel.png
  • 入口:SentinelResourceAspect
  • 核心节点:
    • StatisticSlot:流量统计。
    • FlowSlot:流控校验。
流量统计
  • ArrayMetric:滑动窗口
    • 秒级窗口:1s跨度的2个区间。
public class StatisticNode implements Node {
    private transient volatile Metric rollingCounterInSecond = 
		new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL);
    private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
}
public class ArrayMetric implements Metric {
    private final LeapArray<MetricBucket> data;
    public void addPass(int count) {
        WindowWrap<MetricBucket> wrap = data.currentWindow();
        wrap.value().addPass(count);
    }
}
  • LeapArray,通过当前时间计算当前所属分桶开始时间,路由到分桶。
    • 分桶不存在则创建并CAS。
    • 当前时间等于桶的开始时间,该桶为所属。
    • 当前时间大于桶的开始时间,加锁并重置桶。
    • 当前时间小于桶的开始时间,理论上不会。
    public LeapArray(int sampleCount, int intervalInMs) {
        this.windowLengthInMs = intervalInMs / sampleCount;
        this.intervalInMs = intervalInMs;
        this.intervalInSecond = intervalInMs / 1000.0;
        this.sampleCount = sampleCount;

        this.array = new AtomicReferenceArray<>(sampleCount);
    }
    public WindowWrap<T> currentWindow(long timeMillis) {
        if (timeMillis < 0) {
            return null;
        }
        int idx = calculateTimeIdx(timeMillis);
        long windowStart = calculateWindowStart(timeMillis);
        while (true) {
            WindowWrap<T> old = array.get(idx);
            if (old == null) {
                WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
                if (array.compareAndSet(idx, null, window)) {
                    return window;
                } else {
                    Thread.yield();
                }
            } else if (windowStart == old.windowStart()) {
                return old;
            } else if (windowStart > old.windowStart()) {
                if (updateLock.tryLock()) {
                    try {
                        return resetWindowTo(old, windowStart);
                    } finally {
                        updateLock.unlock();
                    }
                } else {
                    Thread.yield();
                }
            } else if (windowStart < old.windowStart()) {
                return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            }
        }
    }
流控校验
  • 3种流控方式:DefaultController(快速失败)、RateLimiterController(排队等待)、WarmUpController(预热)
  • DefaultController比较时间跨度内所有窗口的累加和设定值,超过则限流。
public class DefaultController{
    public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        int curCount = avgUsedTokens(node);
        if (curCount + acquireCount > count) {
            return false;
        }
        return true;
    }
    private int avgUsedTokens(Node node) {
        if (node == null) {
            return DEFAULT_AVG_USED_TOKENS;
        }
        return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
    }
}
public class StatisticNode implements Node {
  private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
        IntervalProperty.INTERVAL);
  public double passQps() {
        return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec();
    }
}
public class ArrayMetric{
    public long pass() {
        data.currentWindow();
        long pass = 0;
        List<MetricBucket> list = data.values();

        for (MetricBucket window : list) {
            pass += window.pass();
        }
        return pass;
    }
}

相关资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值