HTB(Hierarchical Token Bucket,分层令牌桶) 是一种流量整形(traffic shaping)与带宽管理的算法,常用于 Linux tc(traffic control)以及路由器/防火墙系统中,用来精确限制、分配和借用带宽。基于 Linux 开发的 RouterOS  仍然基于 tc 来实现 Queue下的 Simple 和 Tree 的流控功能,但 PCQ 是 MikroTik 独立开发的流控算法。

在队列中有一个参数bucket-size,即Token Bucket(令牌桶),这里在官方文档的基础上重新按照自己理解的思路梳理了下关于Token Bucket(令牌桶),

Token Bucket(令牌桶) 算法

token bucket令牌桶算法基于一个桶作为类比,其中以byte字节表示的令牌以特定速率添加到桶中。bucket桶本身具有指定的容量大小。

如果桶已满,新到达的token将被禁止,放入Queue队列中排队。

bucket 容量= bucket-size (秒) × max-limit (bit/s)

注意:进入令牌桶的时packet数据包是byte字节,但上面的公式计算是用bit,bucket 容量需要做单位转换。

  • bucket-size (0..10,默认值:0.1)

在让任何数据包进入 queue 队列之前,会检查token bucket令牌桶,看看此时是否已有足够的令牌,如果有令牌,则绕过queue队列,并删除相应数量的token。否则数据包将进入queue队列,直到有足够的令牌可用为止。

在HTB队列结构中,子队列中使用的令牌也会“记入”其父队列的“账簿“。换句话说,子队列会从其父队列“借用”令牌。

从上图,可以理解HTB 的速率发生在“发送之前”,queue 限制是兜底。如果 bucket 为 0,则没有令牌可用,直接接入 Queue。

bucket 桶大小的实际应用

设置一个简单的场景,其中所有进出某个 IP 地址192.168.88.101的流量都带有数据包标记,在/ip/firewall/mangle创建标记:

/ip firewall mangle
add chain=forward action=mark-connection connection-mark=no-mark src-address=192.168.88.101 new-connection-mark=pc1_conn 
add chain=forward action=mark-connection connection-mark=no-mark dst-address=192.168.88.101 new-connection-mark=pc1_conn 
add chain=forward action=mark-packet connection-mark=pc1_conn new-packet-mark=pc1_traffic

默认bucket桶

/queue tree 
add name=download parent=Local packet-mark=PC1-traffic max-limit=10M 
add name=upload parent=Public packet-mark=PC1-traffic max-limit=10M

在这种情况下,桶大小为 0.1,因此桶容量为 0.1 x 10M = 1M。

如果 bucket 桶已满(即客户端一段时间内未使用队列的全部容量),则接下来的 1Mb 流量不经过queue队列发送。

更大的bucket桶

/queue tree 
add name=download parent=Local packet-mark=PC1-traffic max-limit=10M bucket-size=10 
add name=upload parent=Public packet-mark=PC1-traffic max-limit=10M bucket-size=10

尝试将同样的逻辑应用到桶大小达到最大值的情况中:桶大小为 10,因此 bucket 桶容量为 10 x 10M = 100M。

如果客户端未使用桶里的容量,即桶里的 token 是满的情况下,则接下来的 100Mb 流量可以不经过 queue 队列直接发送。可以使用桶容量的时间:

  • 20Mbps 传输速度,bucket可以持续 10 秒
  • 60Mbps 传输突发,bucket可以持续 2 秒
  • 1Gbps 传输突发,bucket可以持续约 100 毫秒

这个时间的计算公式是:bucket容量 ÷ (实际速率 − max-limit)

因此,可以看出,桶允许流量在队列中呈现某种“突发性”。这种行为类似于普通的突发功能,但缺少突发流量的上限(Queue burst 有 burst-limit,明确的最高 Mbps)。如果在HTB队列结构中合理利用bucket-size,就可以避免这种缺陷。可以理解为bucket-size 允许突发,但不限制突发“跑多快”,如果 bucket-size 用在单一 queue 上,可能导致独占链路,把 bucket-size 放进父/子 queue 结构中,就能用父 queue 约束这种突发。

在HTB结构中大子队列桶,小父队列桶

/queue tree 
add name=download_parent parent=Local max-limit=20M 
add name=download parent=download_parent packet-mark=PC1-traffic max-limit=10M bucket-size=10 
add name=upload_parent parent=Public max-limit=20M 
add name=upload parent=upload_parent packet-mark=PC1-traffic max-limit=10M bucket-size=10

在这种情况下:

  • 父队列桶大小=0.1,桶容量=0.1 x 20M = 2M
  • 子队列桶大小=10,桶容量=10 x 10M = 100M

父队列的令牌消耗速度远快于子队列,而子队列总是从父队列借用令牌,因此整个系统的令牌速率被限制在父队列的令牌速率范围内——在本例中为 max-limit=20M。此速率将持续到子队列的令牌耗尽为止,届时系统将限制在 10Mbps 的令牌速率范围内。

这样,我们就可以在长达 10 秒的时间内获得 20Mbps 的峰值速率。