🔥 本文是限流算法系列的下篇,将带你深入理解漏桶和令牌桶限流算法。从原理到代码实现,从单机到分布式,全方位掌握这两种高级限流技术。
📚 博主匠心之作,强推专栏:
文章目录
一、漏桶算法详解
1.1 算法原理
还记得小时候去农村,看到房顶用水桶接雨水的场景吗?漏桶算法的思想其实和这个很像。
1. 漏桶模型
想象一个水桶:
- 上面有个口子不断接收水(请求)
- 下面有个小孔以固定速率漏水(处理)
- 桶有容量限制,装不下就溢出(拒绝)
拿我们系统的消息推送来举例:
- 上游服务疯狂发消息(可能一秒几千条)
- 我们的推送接口限制固定速率(每秒100条)
- 超出的消息先放桶里(缓冲区)
- 桶满了就拒绝新消息(限流)
2. 水流速率
这个是漏桶最关键的参数。还是拿消息推送举例:
- 我们发现每秒处理100条是比较合适的
- 再快,服务器扛不住
- 再慢,消息延迟太大
- 所以设定漏桶速率就是100/秒
实际使用中要注意:
- 速率要根据系统承载能力来定
- 要留点余量,不能踩着极限值
- 最好能动态调整,应对流量变化
3. 桶容量设计
这个要根据实际场景权衡。我们系统最开始设置了1000:
- 优点是可以应对短时高峰
- 缺点是消息延迟可能变大
- 后来改成了500,在延迟和承载间取了个平衡
几个实践建议:
- 容量太大,延迟会变高
- 容量太小,缓冲作用就没了
- 建议设置告警阈值(比如80%)
- 监控桶的使用率,方便调优
漏桶算法最大的特点就是"恒定速率"。这在很多场景下是把双刃剑:
- 好处是保护系统,流量非常平稳
- 坏处是不能应对突发流量,全都得排队
所以,如果你的系统需要处理突发流量(比如秒杀),可能令牌桶算法更合适。这个我们在后面会详细讲。
1.2 漏桶算法实现
下面是一个生产级别的漏桶限流器实现:
/**
* 漏桶限流器
* 以固定速率处理请求,多余的请求放入桶中排队
*/
public class LeakyBucket {
// 桶的容量(最大排队数)
private final int capacity;
// 漏水速率(每秒处理数)
private final double rate;
// 上次漏水时间
private long lastLeakTime;
// 当前桶中水量(待处理请求数)
private double water;
private final ReentrantLock lock = new ReentrantLock();
/**
* 创建漏桶限流器
*
* @param capacity 桶容量
* @param rate 漏水速率(每秒)
*/
public LeakyBucket(int capacity, double rate) {
this.capacity = capacity;
this.rate = rate;
this.lastLeakTime = System.currentTimeMillis();
this.water = 0;
}
/**
* 尝试加入请求到漏桶
*
* @return true:请求被接受 false:请求被拒绝
*/
public boolean tryAcquire() {
lock.lock();
try {
// 计算当前水量
long now = System.currentTimeMillis();
// 距离上次漏水的时间(秒)
double timeElapsed = (now - lastLeakTime) / 1000.0;
// 计算漏掉的水量
double leakedWater = timeElapsed * rate;
// 更新当前水量
water = Math.max(0, water - leakedWater);
lastLeakTime = now;
// 如果桶未满,接受请求
if (water < capacity) {
water++;
return true;
}
return false;
} finally {
lock.unlock();
}
}
/**
* 尝试加入请求,如果桶满则等待
*
* @param timeout 最大等待时间(毫秒)
* @return true:请求被接受 false:等待超时
*/
public boolean tryAcquireWithTimeout(long timeout) {
long endTime = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < endTime) {
if (tryAcquire()) {
return true;
}
// 等待一小段时间再试
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 获取当前桶中水量
*/
public double getCurrentWater() {
lock.lock();