同字面意思一样,限流即为限制流量,已知服务并发上限,但是在某个时刻涌入了大量流量,服务器无法及时处理,则进入排队等待,从而导致服务超时或者响应时间过长,此时就需要我们的限流方案为这一现象进行一些处理, 目前主流的限流方案有以下几种,我们一起来实战演练一番
1.漏桶算法
原理及概念
漏桶限流算法是一种流量控制算法,它通过一个固定容量的桶来控制流出的速率。当流量到来时,先放入到漏桶中,如果漏桶已满则丢弃多余的流量,否则按照固定的速率流出。这样可以限制流量的突发性,平滑流量的输出。
漏桶限流算法的实现思路如下:
- 设定一个固定容量的漏桶,以及漏水的速率。
- 当有请求到来时,将请求放入漏桶中。
- 如果漏桶已满,则拒绝请求或者丢弃多余的请求。
- 按照固定速率处理漏桶中的请求。
在实际应用中,漏桶限流算法可以用于限制网络请求的速率,防止流量突发增大导致系统崩溃,也可以用于平滑流量的输出,保护系统资源。
如图所示

优缺点
优点:
- 平滑输出:漏桶算法可以平滑地处理流量,防止突发流量对系统造成冲击,有助于保护系统稳定性。
- 控制速率:漏桶算法可以限制输出的速率,确保系统资源不被过度占用,有助于进行流量控制和保护。
缺点:
- 丢弃请求:当漏桶已满时,漏桶算法会丢弃多余的请求,可能会导致部分请求被拒绝,对于某些应用场景可能不太友好。
- 无法应对突发流量:漏桶算法无法应对突发性的大流量,因为漏桶容量是固定的,超出容量的流量会被丢弃,无法灵活应对突发情况。
总的来说,漏桶算法适合用于平滑输出和控制速率,但在对于突发流量的处理上有一定局限性。在实际应用中需要根据具体场景和需求进行选择。
代码实现
java
/**
* 漏桶限流算法实现
*
* @author AnkerEvans
*/
public class LeakyBucketCurrentLimitHandler implements CurrentLimitHandler {
/**
* 漏桶容量
*/
private int capacity;
/**
* 漏水速率n/s, 每秒允许多少个请求
*/
private int rate;
/**
* 当前水量
*/
private int water;
/**
* 上次漏水时间
*/
private long lastLeakTime;
/**
* 构建handler
*
* @param capacity 桶容量
* @param rate 流出速率(n/s)
*/
public LeakyBucketCurrentLimitHandler(int capacity, int rate) {
this.capacity = capacity;
this.rate = rate;
this.water = 0;
}
/**
* 尝试访问
*
* @param tokens 需要获取令牌的数量
* @return 是否允许访问
*/
@Override
public synchronized boolean tryAccess(Integer tokens) {
// 当前服务器时间戳
long now = System.currentTimeMillis();
// 求当前数量
water = Math.max(0, (int) (water - (now - lastLeakTime) / 1000 * rate));
// 更新上次流出时间
lastLeakTime = now;
// 如果数量 + 需要获取令牌的数量 小于等于 桶容量大小, 则允许访问
if (water + tokens <= capacity) {
water += tokens;
// 漏桶未满,允许通过
return true;
}
// 漏桶已满,拒绝通过
return false;
}
}
2.令牌桶算法
原理及概念
令牌桶算法基于令牌桶的概念来实现流量控制。在令牌桶限流算法中,令牌桶以固定的速率往里面添加令牌,每个请求需要消耗一个令牌,如果令牌桶中的令牌数量不足,则请求将被限制或延迟处理。
令牌桶限流算法的基本原理如下:
- 令牌桶以固定的速率往里面添加令牌,直到达到最大容量为止。
- 每个请求需要消耗一个令牌,如果令牌桶中有足够的令牌,则请求可以被处理,同时从令牌桶中消耗一个令牌;如果令牌桶中没有足够的令牌,则请求被限制或延迟处理。
- 令牌桶中的令牌数量是有限的,当请求频率超过令牌桶的填充速率时,令牌桶将会耗尽,无法处理更多的请求。
令牌桶限流算法的优点包括:
- 平滑的流量控制:令牌桶算法可以以固定的速率向令牌桶中添加令牌,从而限制请求的处理速率,实现平滑的流量控制。
- 灵活性:令牌桶算法可以根据实际需求动态调整令牌的填充速率和容量,适应不同的场景。
然而,令牌桶限流算法也存在一些缺点:
- 突发流量处理:令牌桶算法对于突发流量的处理能力较差,因为令牌桶中的令牌数量是有限的,无法应对瞬时的大流量。
- 复杂性:相对于一些其他流量控制算法,令牌桶算法的实现可能相对复杂一些,需要考虑令牌的填充速率、容量等参数。
如图所示

优缺点
优点:
- 平滑的流量控制:令牌桶算法可以以固定的速率向令牌桶中添加令牌,从而限制请求的处理速率,实现平滑的流量控制。
- 灵活性:令牌桶算法可以根据实际需求动态调整令牌的填充速率和容量,适应不同的场景。
缺点:
- 突发流量处理:令牌桶算法对于突发流量的处理能力较差,因为令牌桶中的令牌数量是有限的,无法应对瞬时的大流量。
- 复杂性:相对于一些其他流量控制算法,令牌桶算法的实现可能相对复杂一些,需要考虑令牌的填充速率、容量等参数。
代码实现
java
/**
* 令牌桶限流算法实现
*
* @author AnkerEvans
*/
public class TokenBucketCurrentLimitHandler implements CurrentLimitHandler {
/**
* 令牌桶容量
*/
private int capacity;
/**
* 当前令牌数量
*/
private int tokens;
/**
* 上次填充令牌的时间
*/
private long lastRefillTime;
/**
* 令牌填充速率
*/
private int refillRate;
/**
* 构建令牌桶算法实现
*
* @param capacity 令牌容量
* @param refillRate 填充速率
*/
public TokenBucketCurrentLimitHandler(int capacity, int refillRate) {
this.capacity = capacity;
this.tokens = capacity;
this.refillRate = refillRate;
}
/**
* 补充令牌
*/
private void refill() {
long now = System.currentTimeMillis();
if (now > lastRefillTime) {
int timePassed = (int) (now - lastRefillTime);
/*
计算新token数量, 这段代码很好理解, 假设距离上次填充时间过去了 1000 ms, 填充速率每秒2个
那么需要填充的数量即为 1000 * 2 / 1000 = 2, 如果距离上次填充为500 ms, 则 500 * 2 / 1000 = 1, 间隔更小的话则不填充
通俗易懂的来讲就是, 按每秒生成令牌的速率填充令牌桶
*/
int newTokens = timePassed * refillRate / 1000;
// 令牌数量为 当前桶令牌数量 + 新生成的令牌数量, 与最大令牌容量取最小值即为当前令牌数量
this.tokens = Math.min(this.tokens + newTokens, capacity);
this.lastRefillTime = now;
}
}
@Override
public boolean tryAccess(Integer token) {
/*
相当于匀速填充令牌
*/
refill();
/*
如果令牌数量大于需要获取的令牌数量,则通过,并且令牌数量-${token}个
相当于获取令牌的操作
*/
if (this.tokens >= token) {
this.tokens -= token;
return true;
}
// 否则限制访问
return false;
}
}
小结一下:
了解完了漏桶限流和令牌桶限流算法,有没有发现他俩的区别
-
漏桶算法是限定单位时间内可以接受的最大请求次数.
想想这样的方式是不是有一些缺陷,例如
-
该接口允许1秒内最大接收10个请求, 但是我在第100ms时已经放行了10个请求, 那么后面的请求将被拒绝
-
该接口允许1秒内最大接收1000个请求,但是恰好10ms内涌入了1000个请求,这种场景其实没有有效的实现对服务器资源的保护
所以 漏桶限流算法存在很大的局限性, 读到这里,相信你也理解了漏桶算法的原理,希望在后续工作中,对于频繁访问或者流量不平滑的接口需要进行限流时,能对其一些核心参数进行优化, 避免出现上述的两种极端情况
-
-
令牌桶限流算法基于其实现原理,侧面的也解决了一些上文中漏桶算法的缺陷. 因为其相较于漏斗算法, 它对令牌的填充数量是匀速进行的
它们有一个共同的缺点: 无法应对流量激增这种情况,
我为两个限流算法各举一个简单的例子:
-
漏桶算法:
- 你开了一家早餐店, 每日早上限量100份供应,早上8:00开门9:00关门, 8:30的时候你早餐卖完然后关门走了, 慕名而来买早餐的人买不到早餐走了. 这种对应漏桶算法的缺陷a
- 你开了一家早餐店, 你每40分钟能做100份早餐, 你为了保险起见,预估了一下每小时卖100份, 结果刚开门2分钟就有100个人排队, 你拼老命10分钟做了80份然后累垮了, 这就是刚才提到的漏桶算法的缺陷b, 你的早餐店相当于服务器, 你40分钟做100份相当于你实际的处理速率, 然后那排队的100个人就是100个请求, 你累垮了则标识服务器宕机了
-
令牌桶限流算法:
- 你还是开了一家早餐店,你每10分钟只能做两份早餐, 来了十个人买早餐, 两个人买完早餐走了, 剩下的8个人等下一个10分钟的两份早餐,或者走人
以上两个贴近生活的小例子应该能帮助你对两种限流算法的理解了(文笔不太好,勿Cue) 感兴趣的小伙伴可以关注下我的博客,将在下周更新
[服务或接口限流算法1/3]-固定时间窗口及滑动时间窗口算法解析