2、令牌桶算法详解:令牌桶原理、突发流量处理、平均速率与峰值速率的关系
说到流控,令牌桶算法是绕不开的核心。我刚开始接触流量整形时,总觉得这玩意儿不就是个桶嘛,能有多深?后来在现网踩了几次坑才明白——桶的深浅、令牌投放速度、突发大小,每一个参数调不好,业务就得抖三抖。
咱们今天就把令牌桶彻底掰开揉碎。你理解了这个算法,后面讲漏桶、双速率三色标记,都会轻松很多。
2.1 令牌桶的核心原理
令牌桶的思路其实很朴素。想象一个桶,里面不断往里丢令牌。每个数据包要发出去,必须先拿走一个令牌。桶满了,令牌就溢出了,不再增加。
这个机制决定了两个关键行为:
- 平均速率:由令牌投放速率决定。你每秒往桶里放 1000 个令牌,那平均每秒最多就发 1000 个包。
- 突发能力:由桶的容量决定。桶里能攒下最多 N 个令牌,意味着你可以在瞬间一口气发 N 个包。
我习惯把令牌投放速率记作 CIR(Committed Information Rate),桶容量记作 CBS(Committed Burst Size)。这两个参数,就是令牌桶的灵魂。
核心公式:
当前令牌数 = min(桶容量, 上次令牌数 + 投放速率 × 时间间隔)
数据包通过条件:当前令牌数 ≥ 包长
通过后:当前令牌数 = 当前令牌数 - 包长
说白了,这就是一个积分加限幅的过程。桶容量是上限,投放速率是积分斜率,包长是消耗量。
2.2 突发流量是怎么处理的?
这是很多人容易搞混的地方。令牌桶允许突发,但突发不是无限的——它受桶容量限制。
举个例子。假设 CIR = 1 Mbps,CBS = 200 KB。如果链路空闲了 2 秒,桶里会攒满 200 KB 的令牌。这时候突然来了一波 200 KB 的流量,令牌桶会一次性放行,瞬时速率飙到很高。
但注意,这波突发放完后,桶空了。后续流量只能按 1 Mbps 的速率慢慢走,直到桶再次攒够令牌。
我在项目中遇到过一种情况:某视频平台做直播推流,码率波动很大。他们设了很大的 CBS,结果突发流量把上游交换机的缓存打爆了。嗯,这里要注意——突发不是越大越好。你给了业务太大的突发空间,下游设备不一定扛得住。
我的调优习惯:
- CBS 一般设为 CIR 的 1~2 秒数据量,作为初始值
- 如果业务对时延敏感(如语音),CBS 要偏小
- 如果业务有周期性突发(如视频关键帧),CBS 要适当放大
2.3 平均速率 vs 峰值速率
很多设备支持双令牌桶——一个管平均速率,一个管峰值速率。为什么要两个桶?
你想想看,如果只有一个桶,业务可以一直用突发把流量打满。比如 CIR = 10 Mbps,CBS = 10 Mb,那业务每 1 秒发 10 Mb 的突发,平均速率还是 10 Mbps,但瞬时峰值可能冲到 100 Mbps。这对网络很不友好。
所以我们需要第二个桶,管 PIR(Peak Information Rate)和 PBS(Peak Burst Size)。
| 参数 | 含义 | 典型关系 |
|---|---|---|
| CIR | 承诺平均速率 | 业务长期保证的带宽 |
| CBS | 承诺突发大小 | 通常 = CIR × 1~2 秒 |
| PIR | 峰值速率上限 | PIR ≥ CIR,通常 1.5~2 倍 |
| PBS | 峰值突发大小 | 通常 = PIR × 0.5~1 秒 |
双令牌桶的工作流程是这样的:
- 数据包先过 PIR 桶。如果 PIR 桶令牌不够,直接丢弃或标记。
- 过了 PIR 桶,再过 CIR 桶。如果 CIR 桶令牌够,标记为绿色(合规)。
- 如果 CIR 桶令牌不够但 PIR 桶够,标记为黄色(超标但可容忍)。
- 两个桶都不够,标记为红色(丢弃)。
我曾经帮一家 CDN 厂商调过这个逻辑。他们发现某些客户流量总是被标记为黄色,导致服务质量下降。查了半天,原来是 PIR 设得太紧——PIR 只比 CIR 高了 10%,业务稍微波动就超了。后来我把 PIR 放宽到 CIR 的 2 倍,黄色标记率从 30% 降到了 2% 以下。
避坑指南:
我曾经在配置交换机时,把 PIR 和 CIR 设成了相同的值。结果业务一有突发就丢包,排查了整整一个下午。后来才意识到——PIR 必须大于 CIR,否则双令牌桶退化为单令牌桶,突发能力完全丧失。
2.4 令牌桶的数学模型
如果你喜欢用公式理解,可以看这个离散时间模型。我一般在做流量仿真时会用到它。
// 伪代码:单令牌桶更新逻辑
// 每到达一个数据包,执行以下步骤:
// 1. 计算时间差
delta_t = 当前时间 - 上次更新时间
// 2. 补充令牌(上限为 CBS)
tokens = min(CBS, tokens + CIR * delta_t)
// 3. 判断是否放行
if tokens >= packet_size:
tokens -= packet_size
放行数据包
else:
丢弃或标记数据包
// 4. 更新时间戳
上次更新时间 = 当前时间
这个模型里有个细节容易被忽略:时间精度。如果 delta_t 太小(比如微秒级),CIR × delta_t 可能小于 1 个令牌,导致桶一直无法补充。实际实现中,很多设备会用累积小数的方式处理,或者把时间粒度放大到毫秒级。
2.5 知识体系总览
下面这张图把令牌桶的核心逻辑串起来了。你可以把它当作调试时的速查地图。
2.6 调试中的常见误区
最后总结几个我这些年见过的典型问题,你调试时可以直接对照排查。
- 误区一:CBS 设得越大越好。不对。CBS 过大,下游设备可能被突发冲垮。我见过有人把 CBS 设成 CIR 的 10 秒数据量,结果核心交换机端口缓存溢出,丢包率飙升到 5%。
- 误区二:PIR 和 CIR 设成一样。这等于废掉了峰值控制,双令牌桶退化为单桶。业务稍微波动就会被标记为黄色或红色。
- 误区三:忽略时间精度。有些软件实现的令牌桶,时间粒度太粗,导致令牌补充不均匀。我曾经在 DPDK 程序里踩过这个坑——毫秒级更新,流量一上来桶就空了,实际速率只有配置的一半。
- 误区四:只配单桶不配双桶。如果业务需要严格的峰值控制(比如金融交易流量),单桶根本不够用。必须上 PIR/PBS 双桶。
快速自查清单:
- CIR 是否匹配业务长期带宽需求?
- CBS 是否在 1~2 秒数据量范围内?
- PIR 是否 ≥ 1.5 倍 CIR?
- PBS 是否 ≤ 1 秒 PIR 数据量?
- 时间更新粒度是否 ≤ 1 毫秒?
令牌桶算法说难不难,说简单也不简单。你把 CIR、CBS、PIR、PBS 这四个参数吃透了,流控调试就入门了八成。剩下的两成,是在现网里一点点磨出来的经验——嗯,那部分谁也替不了你。