4. 漏桶算法实现:基于Python的漏桶实现、与令牌桶的对比、适用场景分析
流量控制里,漏桶算法和令牌桶算法是两大经典。很多人觉得它们差不多,其实差别挺大。今天咱们就聊聊漏桶,我还会拿它和令牌桶做个对比。
先说说我个人的理解。漏桶算法,说白了就是一个固定容量的桶,底下有个小洞,水以恒定速率流出去。不管上面倒水多猛,下面出水始终匀速。这个特性决定了它的核心价值——削峰填谷。
4.1 漏桶算法的核心原理
漏桶有三个关键参数:
- 桶容量(capacity):最多能存多少请求。超过这个量,直接丢弃。
- 漏水速率(leak_rate):每秒能处理多少个请求。这是恒定值。
- 当前水量(water):桶里积压的请求数。
每次新请求进来,先看看桶满了没。满了就拒绝,没满就放进去。然后系统以固定速率从桶里取请求处理。嗯,这里要注意:漏水速率决定了系统的最大吞吐能力,桶容量决定了能容忍的突发程度。
核心公式:当前水量 = max(0, 上次水量 - (当前时间 - 上次时间) × 漏水速率)
如果当前水量 + 1 > 桶容量,则拒绝请求;否则放行,水量+1。
4.2 Python实现:一个生产级的漏桶
我曾经在某个API网关项目里写过漏桶,踩过不少坑。下面这个版本是我觉得比较实用的:
import time
import threading
class LeakyBucket:
def __init__(self, capacity, leak_rate):
self.capacity = capacity # 桶容量
self.leak_rate = leak_rate # 漏水速率(请求/秒)
self.water = 0 # 当前水量
self.last_time = time.time() # 上次漏水时间
self.lock = threading.Lock() # 线程安全
def _leak(self):
"""计算漏水,更新水量"""
now = time.time()
elapsed = now - self.last_time
# 漏掉的水量 = 时间差 × 速率
leaked = elapsed * self.leak_rate
self.water = max(0, self.water - leaked)
self.last_time = now
def allow_request(self):
"""判断是否允许请求通过"""
with self.lock:
self._leak()
if self.water < self.capacity:
self.water += 1
return True
return False
# 使用示例
bucket = LeakyBucket(capacity=10, leak_rate=2) # 桶容量10,每秒处理2个
for i in range(20):
if bucket.allow_request():
print(f"请求 {i}: 通过")
else:
print(f"请求 {i}: 被限流")
time.sleep(0.1)
避坑指南:我曾经在实现时忘了加锁,结果高并发下水量计算全乱套了。多线程环境一定要用锁保护共享状态。另外,_leak()方法每次请求都调用,如果时间精度要求高,可以考虑用定时器异步漏水。
4.3 漏桶 vs 令牌桶:到底差在哪?
很多人问我:漏桶和令牌桶,到底选哪个?我一般会反问:你的业务允许突发吗?
先看个对比表格:
| 对比维度 | 漏桶算法 | 令牌桶算法 |
|---|---|---|
| 输出速率 | 严格恒定 | 允许突发到峰值 |
| 突发处理 | 不处理突发,超出即丢弃 | 允许一定程度的突发 |
| 实现复杂度 | 简单,只需维护水量和时间 | 稍复杂,需维护令牌数和时间 |
| 适用场景 | 对输出速率有严格要求的场景 | 允许短时突发的场景 |
| 内存开销 | 低,仅需几个变量 | 低,类似 |
说白了,漏桶是“硬限速”,令牌桶是“软限速”。漏桶把流量磨平了再输出,令牌桶允许你攒一波令牌然后爆发出去。
举个例子:你想想看,如果是一个数据库连接池,你希望每秒最多建立10个连接。用漏桶就很合适,因为数据库受不了突发连接。但如果是CDN缓存,用户突然刷一下页面,你希望他能快速拿到数据,这时候令牌桶就更友好。
4.4 适用场景分析
根据我的项目经验,漏桶算法最适合以下场景:
- 下游系统脆弱:比如老旧的数据库、单机服务,它们扛不住突发流量。漏桶能保证输出平稳。
- 网络带宽控制:我记得有个视频转码服务,输出带宽有限。用漏桶控制每秒发送的数据量,避免网络拥塞。
- 任务队列调度:消费者处理能力固定,用漏桶把生产者塞进来的任务平滑化。
- API限流:对外暴露的API,要求调用方不能超过某个固定QPS。漏桶是最直观的方案。
注意:漏桶不适合需要应对突发流量的场景。比如电商秒杀、热点新闻推送,用户瞬间涌入,你希望尽快响应,而不是把请求都卡在桶里排队。这时候令牌桶更合适。
4.5 漏桶算法的变体
实际项目中,我见过几种漏桶的变体:
- 带优先级的漏桶:桶里分多个队列,高优先级请求可以插队。我曾在日志系统中用过,错误日志优先处理。
- 动态速率漏桶:漏水速率根据系统负载动态调整。比如CPU空闲时加快处理,繁忙时减慢。
- 分布式漏桶:用Redis实现全局漏桶,多个服务实例共享一个桶。这个在微服务架构里很常见。
下面这张图展示了漏桶算法的完整流程:
4.6 选型建议
最后给点实在的建议。如果你还在纠结用漏桶还是令牌桶,可以问自己三个问题:
- 下游能扛突发吗? 不能 → 漏桶;能 → 令牌桶
- 业务允许请求排队吗? 允许 → 漏桶;不允许 → 令牌桶(直接拒绝)
- 需要平滑输出吗? 需要 → 漏桶;不需要 → 令牌桶
我个人习惯是:保护下游用漏桶,保护上游用令牌桶。这个原则在我做过的十几个限流项目里,基本没出过问题。
小技巧:如果实在拿不准,可以两个都实现,然后压测对比。我一般会在代码里做个策略模式,运行时动态切换。这样上线后还能根据实际表现调整。