问题是这样,我们有 2 组服务器,A 组和 B 组,一部分用户从 A 组下载文件,一部分从 B 组下载文件。分成 2 组的初衷是,B 组用户下载量通常比较大,并且是离线服务,意味着可以慢一些,但不能影响 A 组用户。现在的问题是,A 组 和 B 组最终下载的来源都是 S3,依然存在共享资源,会出现问题:在下载量比较大的时候,占用全部的 S3 上传带宽,导致 A 组用户收到影响。
我们希望限制 B 到 S3 的带宽,比如 10Gbps.
有几个限制:
我们无法在 Client – Server 这里限流,因为 Server 侧有 Cache,我们希望 Client – Server 这里依然可以使用非常大的带宽来下载;
S3 的服务器没有限流的功能,这些服务器我们也无法控制,我们只能配置 Server A 和 Server B;
为了区分出来 TTL,我们分多轮进行扫描,先对所有的 IP 发送 TTL=1 的包,接收程序如果收到了 ICMP reply 的回应,说明这些 IP 在 TTL=1 的时候就能 ping 通,由于我们要找的是 TTL 越长越好,所以这些 IP可以直接淘汰了。接下来我们把 TTL=1 不能 ping 通的包,用 TTL=2 再发送一轮,如果能收到 ICMP reply,那么也可以淘汰了…… 假设我们在 TTL=30 的时候有一些 IP 能 ping 通,但是在 TTL=31 以及之后的时候没有任何 IP 可以 ping 通,那就说明这些 TTL=30 的 IP 就是胜者。
这样需要多久呢?
DigitalOcean 页面解释:All other Droplets have a maximum network throughput limit of 2 Gbps3. 每一个 ICMP 包的大小是:Ethernet 14 + IP 20 + ICMP 8 + payload 32 = 74 bytes,所以,理论上我们可以跑到:
在收包程序上,直接看这个 IP 是不是一个合法的 ICMP reply,如果是,就记录 ping 通,如果不是就放通或丢弃。
但是 XDP 是运行在 kernel 的程序,如何把 ICMP reply 里面的 IP 信息记录到文件中呢?
第二个法宝:ring buffer
Ring buffer4 在网络领域是一个非常常用的数据结构,它本质上是一个 buffer,生产方可以往里面写,消费方从里面读,是两个指针。它天然适合网络的原因是,buffer 的 head 和 tail 是相接的,自然而然就可以实现「如果生产方生产的速度太快,丢弃(覆盖)最早到达并且还没有处理的包」。(对于 BPF ring buffer,如果用户态消费得不够快、buffer 没有剩余空间,新的记录会写入失败。)
使用 BPF ring buffer (BPF_MAP_TYPE_RINGBUF),作为 kernel space 和 user space 的桥梁——kernel 往这个 ring buffer 里面不断写入可以 ping 通的 IP,用户态读出来这个 IP 记录到文件中。