前面提到 Linux TCP rwnd 算法的问题: TCP滑动窗口辩证考 TCP rwnd算法挖坟
就是说 rwnd 受限于 sysctl_tcp_rmem[2]。长肥管道场景,若 sysctl_tcp_rmem[2] 不足,则无法填满管道。必须配置足够大的 sysctl_tcp_rmem[2] 以通告足够大的 rwnd。
现在换个视角重新评估这个问题。
小 rwnd 就一定意味着低吞吐吗?并不是!
吞吐是速率,而 rwnd 是数量,若要高吞吐,把时间因素考虑进去才正确。若 ACK 以非常密集的 pacing 背靠背持续到达,即便每个 ACK 仅通告1个 mss 的 rwnd,足够密集的 pacing 也能激发大吞吐。
rwnd 不应体现可发多少,应体现可发多快。显然 Linux TCP 将 rwnd 等价于“一个 RTT 内可发送的数据量”。一个 RTT 作为固化周期控制总量,这是古老的 rwnd 通告方式,现代方式应将“两次读数据间隔内可发送的数据量”作为 rwnd。
古老方式没有把“应用程序读”作为流控因素考虑进来,只简单地将“当前可用的 free space ”作为 rwnd 通告给发送端,至于是这些 free space 是如何 be free 的,who cares。
简单的 iperf 打流,若 sysctl_tcp_rmem[2] 足够大,接收端 CPU 利用率会很高,带宽会打满,若 sysctl_tcp_rmem[2] 很小,接收端 iperf 进程将长时间睡眠等待数据,CPU 得不到充分利用。
接收端虽有能力以更快的速度接收数据,但由于一个 RTT 只能送 rwnd 数据,而 rwnd 受限于 sysctl_tcp_rmem[2] ,它不够大时,任凭应用程序读能力再强,无数据可读也是巧妇难为无米之炊了。
古老方式将 rcvbuff 里的数据往应用程序里“推”,带宽受限于 rcvbuff 的大小,现代方式则是应用程序主动从发送端 “拉” 数据,带宽自适应接收应用程序的读能力。
不改变 rwnd 算法的前提下,我这里有一种现代方法。让“应用程序读”事件参与流控即可:
- 当应用程序有能力继续读时,接收端 TCP 主动向发送端通告当前 rwnd。
这意味着应用程序有能力读数据但 rcvbuff 里无数据可读时,可以主动将当前 free space 作为 rwnd 通告给发送端“刺激”发送端发数据。此法允许接收端应用程序采用 busy poll 的方式主动从发送端吸数据,每次(或每几次) poll 不到数据时就通告一下 rwnd。
Linux TCP 滑动窗口是数量窗口,没考虑时间因素,即数据被读取的速率,因此没看 rcvbuff 堆积动态,仅看绝对值。队列堆积动态情况甚至可以检测到丢包,窗口滑不动了,就堆积了,这样该多好啊。Linux rwnd 算法有性能问题,但我之前那篇文字改了算法,复杂了,没必要,别的系统我不知道怎么实现的,但大致也差不多。本文列举了一个简单的方法,没有改计算公式,也不需要 probe,只增加一个主动通告 rwnd 即可。
浙江温州皮鞋湿,下雨进水不会胖。
|