概要
请求限流的目的是为了保证后台服务器的稳定性,确保在高负载情况下仍能保持稳定运行,此次总结了四种常用的限流算法,用于学习讨论。
1.固定窗口限流算法
算法思想:在固定窗口内限制请求的数量。该算法把时间分为固定的窗口,请求按照时间顺序放入时间窗口内,如果某次请求打进来的时候,窗口内的请求总数超过了阈值,就会拒绝该请求。
假设窗口大小是1s,限流阈值是5,则在1s内的请求限制在5次内,多出的请求都会被拒绝。等1s结束,计数器清零。
伪代码实现:
public static Integer counter = 0; // 统计请求次数
public static long lastAcquireTime = 0L; // 上一次请求时间
public static final Long windowUnit = 1000L; // 窗口大小 1000ms
public static final Integer threshold = 5; // 阈值
public synchronized boolean fixedWindowsTryAcquire() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastAcquireTime > windowUnit) {
lastAcquireTime = currentTime; // 新的窗口
counter = 0; // 清空计数器
}
if (counter < threshold) {
counter++; // 计数器+1
return true;
}
return false;
}
算法优点:实现简单
算法缺点:临界问题,假设窗口大小还是1,阈值还为5,假设第一个请求是0.1刻打来的,lastAcquireTime=0.1,后四个请求是0.5-1.0之间打来的,1s内5和请求没有什么问题。然后1.1刻又打来一个请求,此时窗口更新,lastAcquireTime=1.1,之后在1.1s-1.5s内又打入4个请求,在该窗口内仍然是没什么问题的,但是在0.5s-1.5s这个窗口内请求次数为9次,超过了定义的阈值5。
2.滑动窗口限流算法
算法思想:是固定窗口限流算法的一种优化,解决了固定窗口临界问题,我们知道,固定窗口限流算法只要lastAcquireTime
一旦确定,窗口就确定了,并且更新窗口的时候counter
会清零,所以会出现临界问题。滑动窗口是跟着时间滑动的,每次滑动一个小窗口大小,并且请求次数不会清零。
对比固定窗口限流算法
窗口大小为1s,阈值为5
- 固定窗口限流算法:新的5个请求(紫色格子)到来的时候会更新lastAcquireTime,并清空counter,导致5个请求正常接收,造成临界问题。
- 滑动窗口限流算法:新的5个请求到来之后,窗口右移一格,请求总数并不会清空,所以紫色格子的请求全部都会被拒绝。
伪代码实现:
public static Integer counter = 0; // 统计请求次数
public static long lastAcquireTime = 0L; // 上一次请求时间
public static final Long windowUnit = 1000L; // 窗口大小 1000ms
public static final Integer threshold = 5; // 阈值
public synchronized boolean fixedWindowsTryAcquire() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastAcquireTime > windowUnit) {
lastAcquireTime = currentTime; // 新的窗口
counter = 0; // 清空计数器
}
if (counter < threshold) {
counter++; // 计数器+1
return true;
}
return false;
}
算法优点:
- 简单
- 精准度高(没有临界问题)
算法缺点:
- 超过阈值的请求全部都会被丢弃,对于用户/产品来说不友好。
3.令牌桶限流算法
算法思想:用于限定一定时间内的请求数量,具体做法是维护一个令牌桶,每秒会向令牌桶中放入一定数量的令牌,请求到来的时候先从桶中拿令牌,拿到令牌之后才能被处理,否则请求被拒绝。
伪代码实现:
public class TokenBucket {
private final int capacity; // 令牌桶容量
private final int rate; // 令牌生成速率,单位:令牌/秒
private int tokens; // 当前令牌数量
private long lastRefillTimestamp; // 上次令牌生成时间戳
/**
* 构造函数中传入令牌桶的容量和令牌生成速率。
*/
public TokenBucket(int capacity, int rate) {
this.capacity = capacity;
this.rate = rate;
this.tokens = capacity;
this.lastRefillTimestamp = System.currentTimeMillis();
}
/**
* 表示一个请求是否允许通过,该方法使用 synchronized 关键字进行同步,以保证线程安全。
*/
public synchronized boolean allowRequest() {
refill();
if (tokens > 0) {
tokens--;
return true;
} else {
return false;
}
}
/**
* 生成令牌,其中计算令牌数量的逻辑是按照令牌生成速率每秒钟生成一定数量的令牌,
*/
private void refill() {
long now = System.currentTimeMillis();
if (now > lastRefillTimestamp) {
int generatedTokens = (int) ((now - lastRefillTimestamp) / 1000 * rate);
tokens = Math.min(tokens + generatedTokens, capacity);
lastRefillTimestamp = now;
}
}
}
算法优点:
- 比较稳定,通过控制令牌生成的速度来控制处理请求的速度
- 精度高
- 弹性好,可以处理突发流量,短时间内提供更多处理能力
算法缺点:
- 实现复杂
- 时间精度要求高
4.漏斗限流算法
算法思想:就像一个漏斗,不论有多少水,水流出的速度都是不变的,把请求比作水,那么每次请求打来的时候首先流出一部分水(请求),之后把本次请求加进去,水加入之后多于容量的会溢出(拒绝)。
伪代码实现:
public class LeakyBucket {
private final long capacity; // 桶的容量
private final long rate; // 漏桶出水速率
private long water; // 当前桶中的水量
private long lastLeakTimestamp; // 上次漏水时间戳
public LeakyBucket(long capacity, long rate) {
this.capacity = capacity;
this.rate = rate;
this.water = 0;
this.lastLeakTimestamp = System.currentTimeMillis();
}
/**
* 尝试向桶中放入一定量的水,如果桶中还有足够的空间,则返回 true,否则返回 false。
*/
public synchronized boolean tryConsume(long waterRequested) {
leak(); // 水先流出
if (water + waterRequested <= capacity) {
water += waterRequested;
return true;
} else {
return false;
}
}
/**
* 根据当前时间和上次漏水时间戳计算出应该漏出的水量,然后更新桶中的水量和漏水时间戳等状态。
*/
private void leak() {
long now = System.currentTimeMillis();
long elapsedTime = now - lastLeakTimestamp;
long leakedWater = elapsedTime * rate / 1000;
if (leakedWater > 0) {
water = Math.max(0, water - leakedWater);
lastLeakTimestamp = now;
}
}
}
算法优点:
- 比较灵活,可以根据不同场景调整桶大小和速率
- 可以平滑控制处理请求的速度,避免系统过载
算法缺点:
- 需要对请求进行缓存,增加了服务器内存消耗
参考:
https://juejin.cn/post/7408859165433364490?searchId=20241001001453FFD2E0010431F26C0403
https://www.seven97.top/microservices/protocol/requestflowlimitingalgorithm.html