从Brutal算法的实现引申出的随想
前篇 关于 TCP 拥塞的控制算法,入门知识可以从 《TCP拥塞控制算法BBR的原理和改进实践》开始过一遍 Brutal 算法的核心代码剖析 static void brutal_update_rate(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); struct brutal *brutal = inet_csk_ca(sk); u64 sec = tcp_sock_get_sec(tp); u64 min_sec = sec - PKT_INFO_SLOTS; u32 acked = 0, losses = 0; u32 ack_rate; // Scaled by 100 (100=1.00) as kernel doesn't support float u64 rate = brutal->rate; u32 cwnd; u32 mss = tp->mss_cache; u32 rtt_ms = (tp->srtt_us >> 3) / USEC_PER_MSEC; if (!rtt_ms) rtt_ms = 1; for (int i = 0; i < PKT_INFO_SLOTS; i++) { if (brutal->slots[i].sec >= min_sec) { acked += brutal->slots[i].acked; losses += brutal->slots[i].losses; } } if (acked + losses < MIN_PKT_INFO_SAMPLES) ack_rate = 100; else { ack_rate = acked * 100 / (acked + losses); if (ack_rate < MIN_ACK_RATE_PERCENT) ack_rate = MIN_ACK_RATE_PERCENT; } rate *= 100; rate /= ack_rate; // The order here is chosen carefully to avoid overflow as much as possible cwnd = rate / MSEC_PER_SEC; cwnd *= rtt_ms; cwnd /= mss; cwnd *= brutal->cwnd_gain; cwnd /= 10; cwnd = max_t(u32, cwnd, MIN_CWND); brutal_tcp_snd_cwnd_set(tp, min(cwnd, tp->snd_cwnd_clamp)); WRITE_ONCE(sk->sk_pacing_rate, min_t(u64, rate, READ_ONCE(sk->sk_max_pacing_rate))); } 剖析 从 struct tcp_sock 检索当前时间戳,转换成秒 计算出一个最小的时间戳,该时间戳是当前时间戳减去一个固定大小的窗口(PKT_INFO_SLOTS)。这个窗口用来限定考虑的数据包信息的时间范围 遍历存储数据包信息的数组 brutal->slots,累加在时间窗口内的确认(acked)和丢失(losses)的数据包数量 如果在时间窗口内的样本数量小于定义的最小样本数量 MIN_PKT_INFO_SAMPLES,则将确认率 ack_rate 设为 100%,表示没有丢包 如果样本数量足够,则计算确认率 ack_rate,即在考虑的时间窗口内被确认的数据包与总数据包的比例,并乘以 100 转换为百分比格式 如果确认率低于定义的最小确认率 MIN_ACK_RATE_PERCENT,则使用这个最小值 然后根据确认率调整发送速率 rate。这里使用了一些数学操作来避免使用浮点数,因为内核通常不使用浮点运算 接下来,根据新的发送速率、往返时间(RTT),以及最大分段大小(MSS)计算新的拥塞窗口大小 cwnd 拥塞窗口大小还乘以了窗口增益 cwnd_gain(调整因子),然后除以10(因为增益是以十分之一为单位) 拥塞窗口大小被限制在一个最小值 MIN_CWND 和 tp->snd_cwnd_clamp(拥塞窗口的最大允许值)之间 使用 brutal_tcp_snd_cwnd_set 更新 tcp_sock 结构体中的 snd_cwnd 拥塞窗口大小 最后,更新套接字的发送速率 sk_pacing_rate,这也被限制在 sk_max_pacing_rate(最大发送速率)以下 其实我们能明显看出来,TCP 拥塞算法的核心思想,就是根据当前的网络拥塞情况(在这里依赖于判断丢包率)来计算 cwnd,这个显著指标作为核心值会判定直接影响到发送速率和数据包重传策略的决策,这种 AIMD 原则会在网络状况良好时逐渐增加发送窗口,以增大吞吐量,而在检测到网络拥塞(如丢包事件)时则大幅减小窗口大小,以减轻网络负担,理论上来说遵循这种原则的算法都可以算是一种 “君子算法”,但是 Brutal 算法不是的,在他们的文档中就说明了这一点: ...