3、漏桶算法详解:漏桶原理、与令牌桶的区别、适用场景分析。
聊完令牌桶,咱们来看看它的“老对手”——漏桶算法。这两个算法经常被放在一起比较,很多人觉得它们差不多,其实不然。我个人习惯把漏桶看作一个“强制限流器”,它不关心你流量怎么来,只关心你怎么走。
3.1 漏桶的核心原理
漏桶这个名字很形象。你想象一个桶,底部有个小洞。不管你是往里面倒一杯水,还是泼一盆水,水从洞口流出的速度是恒定的。如果水来得太快,桶满了,多余的水就会溢出(被丢弃)。
在计算机网络里,这个“桶”就是一个缓冲区(队列),底部的“洞”就是固定的处理速率。流量进来先入队,然后以恒定的速率被处理。如果队列满了,新来的数据包直接丢掉。
核心思想:输出速率绝对平滑,但允许输入有突发。突发超过队列容量时,直接丢弃。
我给大家画个图,一看就明白:
3.2 漏桶 vs 令牌桶:到底差在哪?
很多新手容易把这两个搞混。我刚开始做流控的时候也犯过这个错。说白了,它们解决问题的角度完全不同。
令牌桶是“能不能放行?”——有令牌就放,没令牌就等或丢。它允许短时间内的突发流量,只要你有足够的令牌积累。
漏桶是“能不能进来?”——进来先排队,出去必须匀速。它强行把流量“捋直”了。
我整理了一个对比表,大家一看就清楚:
| 对比维度 | 令牌桶算法 | 漏桶算法 |
|---|---|---|
| 核心思想 | 有令牌才能通过 | 有空间才能进入,出去必须匀速 |
| 输出速率 | 允许突发,最大速率 = 桶大小 + 填充速率 | 绝对恒定,等于漏出速率 |
| 突发处理 | 允许短时突发(消耗积累令牌) | 允许短时突发入队,但输出不变 |
| 队列机制 | 无队列,令牌桶本身是计数器 | 有队列,数据包排队等待 |
| 丢包策略 | 令牌不足时丢弃或延迟 | 队列满时丢弃 |
| 适用场景 | 允许突发、对延迟不敏感的业务 | 要求输出绝对平滑的场景 |
我的经验:如果你需要“削峰填谷”,让流量变得平滑,用漏桶。如果你需要“控制平均速率但允许短时爆发”,用令牌桶。我曾经在一个视频直播项目中,推流端用了令牌桶保证用户体验,转码端用了漏桶防止后端被冲垮——两个算法配合着用,效果很好。
3.3 适用场景分析
漏桶算法不是万能的,它有自己的“舒适区”。我给大家拆解一下:
3.3.1 最适合的场景
- 流量整形(Traffic Shaping):比如运营商的带宽限制,你买了10Mbps,不管你怎么突发,出口速率就是10Mbps。漏桶天然适合。
- 下游保护:你的后端服务处理能力有限(比如数据库每秒只能写1000条),用漏桶把前端请求“捋顺”了再发过去,防止被打死。
- 音视频编码/转码:编码器需要恒定码率的输入流,漏桶可以保证数据到达的节奏稳定。
3.3.2 不太适合的场景
- 交互式API限流:用户请求有突发性,漏桶会强制排队,导致延迟增加。令牌桶更合适。
- 对延迟极度敏感的业务:比如高频交易、实时游戏。漏桶的排队机制会引入不确定的等待时间。
我曾经踩过的坑:有一次给一个API网关做限流,我用了漏桶。结果用户反馈接口响应时间忽高忽低。排查后发现,漏桶的队列把请求都堵住了,突发流量一来,后面的请求排队时间暴增。后来换成令牌桶,问题解决了。所以,漏桶不适合对延迟敏感的场景,这一点一定要记住。
3.4 漏桶算法的实现要点
实现漏桶其实不复杂,但有几个细节要注意。我习惯用“水位”的方式来模拟:
class LeakyBucket:
def __init__(self, capacity, leak_rate):
self.capacity = capacity # 桶容量
self.leak_rate = leak_rate # 每秒漏出速率
self.water = 0 # 当前水位
self.last_time = time.time() # 上次漏水时间
def allow_request(self, packet_size=1):
now = time.time()
# 先漏水:根据时间差计算漏掉了多少
elapsed = now - self.last_time
leaked = elapsed * self.leak_rate
self.water = max(0, self.water - leaked)
self.last_time = now
# 检查是否有空间
if self.water + packet_size <= self.capacity:
self.water += packet_size
return True # 放行
else:
return False # 丢弃
嗯,这里要注意:漏水计算一定要基于时间差,不能每次请求都固定漏一个单位。否则在高并发下,漏水速率会不准。
3.5 漏桶的变种:分层漏桶
实际项目中,单层漏桶有时候不够用。比如你要同时限制总带宽和单连接带宽。我做过一个方案:
- 第一层:全局漏桶,限制总出口带宽
- 第二层:每个连接一个漏桶,限制单连接带宽
- 第三层:优先级队列,高优先级流量可以“插队”
这种分层设计在运营商和企业网关里很常见。你想想看,如果不做分层,一个用户开多线程下载就能把整个带宽占满,其他用户就卡死了。
总结一下:漏桶算法的精髓在于“强制平滑”。它牺牲了突发能力,换来了输出的绝对稳定。选不选它,就看你的业务能不能接受排队和延迟。