绝对的说法都是错误的。

DO NOT Touch SO_RCVBUF

EDIT(2018/06/02): 还是自己太无知,书都白看了.. 见 https://github.com/golang/go/issues/25701 ,顺便在下面轻微的吐槽了下 Golang 的接口 (Dialer.Control & ListenConfig.Control).. 本文其它文字看看就好,就是个草稿

X Me

前文 做了个傻X事情,直接在代码里面调用了 TCPConn.SetWriteBuffer ,因为在同机房里情况下传输大的数据块数据指标有明显的好转,顺便也把 RCVBUF 设置了 … 其实我是想直接改系统配置的,但是我就不事后诸葛亮了 ..

没有经过充分的测试,一拍脑袋就直接发上了生产环境,过了很久大家都觉得不对劲,跨机房的情况下数据非常不好看,也一时摸不着头脑.. 前两天写程序测试了各种场景(专业测试O(∩_∩)O~),发现是数据的接收端设置了 TCPConn.SetReadBuffer 导致的,而且 TCPConn.SetWriteBuffer 看起来并没有什么卵用,TCP/IP 协议栈本来就做了很多优化,我们这个优化看起来就是自作聪明.. 把这个所谓的“优化”干掉后,latency 明显降低,大数据块(5M)从 800ms 降低到了 100ms 左右..

X Me Hard

tcpdump 之后发现,发送端频繁的在发送一些包之后就停止了,等待对方 ack 之后才会继续发送,自定义 buffer 的情况对方的 win 最大只能到 356 左右,乘以 2^wscale 之后在 200K 左右,而不设置的情况基本上没有停顿 win 会涨到最大值 16384,也就是 8M(16384*(1«9)),是 sysctl_net_ipv4_tcp_rmem[2] 的一半,为什么是一半,见 net.ipv4.tcp_adv_win_scale,详情可自行 Google .. 然后 perf_event + FlameGraph 发现 sk_stream_wait_memory 频率很高,猜测是发送端 SNDBUF 太小导致的,然后增加之后测试发现火焰图上的 sk_stream_wait_memory 和没有设置 buffer 的情况差不多,但是效果并没有改善..

再次测试之后发现,更改系统配置和直接使用 TCPConn.SetReadBuffer 的效果完全不一样,大概原因应该是用户自定义的 buffer 禁用了内核的某些优化,尝试翻了下 linux(v3.10) 内核的代码.. 发现用户设置 RCVBUF 会加上一个 SOCK_RCVBUF_LOCK 的 flag,然后 tcp_clamp_windowtcp_rcv_space_adjust 发现这个这 flag 都会跳过某些不为人知的操作,有时间得把 TCP/IP 协议栈的代码给仔细研究下 (/ω╲) ..

既然都到这里,就想着能不能再优化一点,建立连接之后会有一个“慢”启动的过程,第一次发送 10*mss (linux 2.6.x? 之后调整了 #define TCP_INIT_CWND 10)的数据之后会等待对方 ack 之后再继续发送,之后也会有几次停顿,再往后就很流畅了,每次停顿 20ms 左右(网络延时),于是就想着增大初始窗口的大小,使用 ip route 调整发送端和接收端的 cwnd 和 rwnd 之后没有显著的提升就没再尝试了,期间命令敲错一次直接让一台生产环境的机器被网络隔离..

EDIT(TODO): 今天偶然想起来,回过头来梳理了下,按照理论,单个连接的最大速率应该是 min(cwnd, rwnd) / rtt,发送端的 cwnd 的最大值为接收端的 rwnd,而 rwnd 为 net.ipv4.tcp_rmem 最大值的一半 8M(net.ipv4.tcp_adv_win_scale),最大速率为 8MiB/0.026s ~= 307MiB/s,但是实际速率只有 50MiB/s,而接收端的 rwnd 已经最大了,那么说明 cwnd 因为某种原因没涨上来;那么再按照理论计算,假设 cwnd 需要涨到 8M,那么就是 ~5746 个 segment,按照最好的情况,从 10 指数增加到 5746,需要的时间是 log2(1 + 5746/10) * 26ms ~= 238.4ms,根据测试情况连接早已经预热好了,而且是万兆网卡加上几十个 G 的专线,不能解释为毛 cwnd 没涨上来的原因,除非网络很不稳定。。那就得尝试其它的拥塞控制算法,遗憾的是以前那个机房下线,没有以前的环境了,找个时间再回头看看

So Much to Learn

问题是解决了,过程很暴力,只有数据支撑没有理论支撑还是挺让人难受的,得找时间仔细研究下 TCP/IP 协议栈..

千万别自作聪明

当然如果某位大佬能一棒子把小弟敲醒小弟感激不尽

Refs