skaiuijing
引言
在tcp的拥塞控制算法中,reno和bbr是两种不同的算法,它们代表的两种不同的设计哲学。
拥塞控制不同的设计之争已经持续了很多很多年,但是,终究是有一方看起来胜出了。
现代协议从老旧的ack驱动模型转化为了更加强大的时钟驱动。
结构体
reno结构体
在Linux6.6的tcp.h的tcp_sock结构体里面,有以下字段:
发送窗口(流量控制与拥塞控制)
snd_wnd — 对端通告窗口
max_window — 历史最大窗口
snd_cwnd — 拥塞窗口
snd_ssthresh — 慢启动阈值
snd_cwnd_cnt — cwnd 线性增长计数
snd_cwnd_clamp — cwnd 上限
snd_cwnd_used
snd_cwnd_stamp
prior_cwnd — 进入恢复前的 cwnd
RENO理论
reno代表的是ack驱动的设计哲学。
网络是一个“带时延 + 丢包 + 随机扰动”的动态系统。
把网络抽象成系统,就是:
y(t)=G(u(t−τ))+w(t)
其中:
- u(t):发送端发出的数据
- y(t):接收端返回的 ACK
- τ:网络传播时延(固定 + 抖动)
- w(t):随机扰动(丢包、排队、ACK 压缩、抖动)
在网络世界中,ack来了就是接收到了,ack很久没来就是丢包了,丢包就意味着网络可能发生了拥塞。
在一些老旧的TCP实现中,发送端根据 ACK 调整发送行为:u(t)=f(y(t))
这是一个单闭环反馈系统:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ┌──────────────┐ │ Sender │ │ u(t)=f(ACK) │ └───────┬──────┘ │ u(t) ▼ ┌────────────────┐ │ 网络系统 G │ │ (τ + 扰动 w) │ └───────┬────────┘ │ y(t) ▼ ACK反馈
|
我们使用pacing,会发生什么呢?
pacing 的本质是:upacing(t)=P(t)
它依赖:本地时钟、估计速率、历史 RTT
但不依赖 ACK。
于是发送行为变成:u(t)=f(y(t))+P(t)
这就变成了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| pacing(前馈) │ ▼ ┌──────────────┐ │ Sender │ │ u = f(ACK)+P │ └───────┬──────┘ │ ▼ ┌────────────────┐ │ 网络系统 G │ └───────┬────────┘ │ ▼ ACK反馈
|
pacing会破坏 ACK 闭环的稳定性。
RENO实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
__bpf_kfunc void tcp_reno_cong_avoid(struct sock *sk, u32 ack, u32 acked) { struct tcp_sock *tp = tcp_sk(sk);
if (!tcp_is_cwnd_limited(sk)) return;
if (tcp_in_slow_start(tp)) { acked = tcp_slow_start(tp, acked); if (!acked) return; }
tcp_cong_avoid_ai(tp, tcp_snd_cwnd(tp), acked); } EXPORT_SYMBOL_GPL(tcp_reno_cong_avoid);
__bpf_kfunc u32 tcp_reno_ssthresh(struct sock *sk) { const struct tcp_sock *tp = tcp_sk(sk);
return max(tcp_snd_cwnd(tp) >> 1U, 2U); } EXPORT_SYMBOL_GPL(tcp_reno_ssthresh);
__bpf_kfunc u32 tcp_reno_undo_cwnd(struct sock *sk) { const struct tcp_sock *tp = tcp_sk(sk);
return max(tcp_snd_cwnd(tp), tp->prior_cwnd); } EXPORT_SYMBOL_GPL(tcp_reno_undo_cwnd);
struct tcp_congestion_ops tcp_reno = { .flags = TCP_CONG_NON_RESTRICTED, .name = "reno", .owner = THIS_MODULE, .ssthresh = tcp_reno_ssthresh, .cong_avoid = tcp_reno_cong_avoid, .undo_cwnd = tcp_reno_undo_cwnd, };
|
参考
“Congestion Avoidance and Control”
— Van Jacobson & Michael J. Karels, ACM Computer Communication Review, 1988
— https://dl.acm.org/doi/10.1145/52325.52356