算法
流量限制
计数器
- 统计单位时间内请求数。超过直接拒绝。
- 特点:
- 实现简单。
- 单位时间一开始就消耗完,剩余时间都会拒绝,即突刺消耗。
滑动窗口
- 计数器的精细化(微分+积分),按照时间窗口计数和拒绝。
- 特点:
- 减弱突刺流量的影响:
- 消耗更多内存CPU统计并计算限流。
漏桶
- 缓冲区缓存,系统恒定速率处理请求。
- 特点:
- 消费匀速。
- 请求可能有一定的等待延迟。
- 突发流量会等待较久或者丢弃。
令牌桶
- 改进漏桶。某种策略生成令牌,系统消耗令牌处理请求
- 特点:
并发度限制
开源限流组件
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);
}
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加载。
- 入口:SentinelResourceAspect
- 核心节点:
- StatisticSlot:流量统计。
- FlowSlot:流控校验。
流量统计
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;
}
}
相关资料