关于linux:TCP性能和发送接收窗口Buffer的关系

8次阅读

共计 13045 个字符,预计需要花费 33 分钟才能阅读完成。

阐明:

转载 https://plantegg.github.io/20…

外围

linux 2.4 会主动调整 tcp recv send buffer 大小, 无需手动设置;

  • https://man7.org/linux/man-pa…

前言
本文心愿解析分明,当咱们在代码中写下 socket.setSendBufferSize 和 sysctl 看到的 rmem/wmem 零碎参数以及最终咱们在 TCP 经常谈到的接管发送窗口的关系,以及他们怎么影响 TCP 传输的性能,同时如何通过图形来展现哪里是传输瓶颈。

拥塞窗口相干文章比拟多,他们跟带宽严密相干,所以大家比拟好判断,反而是接管、发送窗口一旦呈现瓶颈,就没这么好判断了。

先明确一下:文章题目中所说的 Buffer 指的是 sysctl 中的 rmem 或者 wmem,如果是代码中指定的话对应着 SO_SNDBUF 或者 SO_RCVBUF,从 TCP 的概念来看对应着发送窗口或者接管窗口

最初补充各种场景下的传输案例,一站式将影响传输速度的各种起因都拿下,值得珍藏。

本文次要剖析 rt、buffer 如何影响 TCP 的传输性能,更多其余因素影响 TCP 性能的案例见:TCP 传输速度案例剖析

TCP 性能和发送接管 Buffer 的关系
先从碰到的一个理论问题看起:

利用通过专线跨网络拜访云上的服务,专线 100M,时延 20ms,一个 SQL 查问了 22M 数据,后果花了大略 25 秒,这太慢了,不失常。

如果通过云上 client 拜访云上服务那么 1 - 2 秒就返回了(不跨网络服务是失常的,阐明服务自身没有问题)。

如果通过 http 或者 scp 从云下向云上传输这 22M 的数据大略两秒钟也传送结束了(阐明网络带宽不是瓶颈),

所以这里问题的起因基本上是咱们的服务在这种网络条件下有性能问题,须要找出为什么。

抓包剖析 tcpdump+wireshark
抓包剖析这 22M 的数据传输,如下图(wireshark 时序图),横轴是工夫,纵轴是 sequence number:

image.png

粗一看没啥问题,因为工夫太长覆盖了问题。把这个图形放大,只看两头 50ms 内的传输状况(横轴是工夫,纵轴是 sequence number,一个点代表一个包)

image.png

能够看到传输过程总有一个 20ms 的期待平台,这 20ms 没有发送任何包,换个角度,看看窗口尺寸图形:

image.png

从 bytes in flight 也大抵能算进去总的传输速度 16K*1000/20=800Kb/ 秒

咱们的利用代码中会默认设置 socketSendBuffer 为 16K:

socket.setSendBufferSize(16*1024) //16K send buffer

原理解析
如果 tcp 发送 buffer 也就是 SO_SNDBUF 只有 16K 的话,这些包很快都收回去了,然而这 16K 的 buffer 不能立刻释放出来填新的内容进去,因为 tcp 要保障牢靠,万一两头丢包了呢。只有等到这 16K 中的某些包 ack 了,才会填充一些新包进来而后持续收回去。因为这里 rt 根本是 20ms,也就是 16K 发送结束后,等了 20ms 才收到一些 ack,这 20ms 利用、内核什么都不能做,所以就是如后面第二个图中的大略 20ms 的期待平台。这块请参考这篇文章

比方下图,wmem 大小是 8,收回 1 - 8 后,buffer 不能开释,等到收到 ack1- 4 后,开释 1 -4,buffer 也就是开释了一半,这一半能够填充新的发送数据进来了。下面的问题在于 ack 花了很久,导致 buffer 始终不能开释。

image.png

sendbuffer 相当于发送仓库的大小,仓库的货物都发走后,不能立刻腾出来发新的货物,而是要等对方确认收到了 (ack) 能力腾出来发新的货物。传输速度取决于发送仓库(sendbuffer)、接管仓库(recvbuffer)、路宽(带宽)的大小,如果发送仓库(sendbuffer)足够大了之后接下来的瓶颈就会是高速公路了(带宽、拥塞窗口)。而实际上这个案例中带宽够、接管仓库也够,然而发送仓库太小了,导致发送过程断断续续,所以十分慢。

如果是 UDP,就没有牢靠的概念,有数据通通收回去,基本不关怀对方是否收到,也就不须要 ack 和这个发送 buffer 了。

几个发送 buffer 相干的内核参数
$sudo sysctl -a | egrep “rmem|wmem|tcp_mem|adv_win|moderate”
net.core.rmem_default = 212992
net.core.rmem_max = 212992
net.core.wmem_default = 212992 //core 是给所有的协定应用的,
net.core.wmem_max = 212992
net.ipv4.tcp_adv_win_scale = 1 //
net.ipv4.tcp_moderate_rcvbuf = 1
net.ipv4.tcp_rmem = 4096 87380 6291456 // 最小值 默认值 最大值】
net.ipv4.tcp_wmem = 4096 16384 4194304 //tcp 这种就本人的专用选项就不必 core 外面的值了
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
vm.lowmem_reserve_ratio = 256 256 32
net.ipv4.tcp_mem = 88560 118080 177120
vm.lowmem_reserve_ratio = 256 256 32
net.ipv4.tcp_wmem 默认就是 16K,而且内核是可能动静调整的,只不过咱们代码中这块的参数是很多年前从 Cobra 中继承过去的,初始指定了 sendbuffer 的大小。代码中设置了这个参数后就敞开了内核的动静调整性能,这就是为什么 http 或者 scp 都很快,因为他们的 send buffer 是动静调整的。

接管 buffer 是有开关能够动态控制的,发送 buffer 没有开关默认就是开启,敞开只能在代码层面来管制

net.ipv4.tcp_moderate_rcvbuf

解决方案
调整 socketSendBuffer 到 256K,查问工夫从 25 秒降落到了 4 秒多,然而比实践带宽所须要的工夫略高

持续查看零碎 net.core.wmem_max 参数默认最大是 130K,所以即便咱们代码中设置 256K 理论应用的也是 130K,持续调大这个零碎参数后整个网络传输工夫大略 2 秒(跟 100M 带宽匹配了,scp 传输 22M 数据也要 2 秒),整体查问工夫 2.8 秒。测试用的 mysql client 短连贯,如果代码中的是长连贯的话会块 300-400ms(消掉了握手和慢启动阶段),这基本上是实践上最快速度了

image.png

如果调用 setsockopt()设置了 socket 选项 SO_SNDBUF,将敞开发送端缓冲的主动调节机制,tcp_wmem 将被疏忽,SO_SNDBUF 的最大值由 net.core.wmem_max 限度。

这个案例对于 wmem 的论断
默认状况下 Linux 零碎会主动调整这个 buffer(net.ipv4.tcp_wmem), 也就是不举荐程序中被动去设置 SO_SNDBUF,除非明确晓得设置的值是最优的。

从这里咱们能够看到,有些实践知识点尽管咱们晓得,然而在实践中很难分割起来,也就是常说的无奈学以致用,最开始看到抓包后果的时候比拟狐疑发送、接管窗口之类的,没有间接想到 send buffer 上,实践跟实际没分割上。

BDP 带宽时延积
BDP=rtt*(带宽 /8)

这个 buffer 调到 1M 测试没有帮忙,从实践计算 BDP(带宽时延积)0.02 秒(100MB/8)=250Kb 所以 SO_SNDBUF 为 256Kb 的时候根本能跑满带宽了,再大也没有什么实际意义了。也就是后面所说的仓库足够后瓶颈在带宽上了。

因为这里依据带宽、rtt 计算失去的 BDP 是 250K,BDP 跑满后拥塞窗口(带宽、接管窗口和 rt 决定的)行将成为新的瓶颈,所以调大 buffer 没意义了。

接下来看看接管 buffer(rmem)和接管窗口的关系
用这样一个案例下来验证接管窗口的作用:

有一个 batch insert 语句,整个一次要插入 5532 条记录,所有记录大小总共是 376K,也就是这个 sql 语句自身是 376K。

SO_RCVBUF 很小的时候并且 rtt 很大对性能的影响
如果 rtt 是 40ms,总共须要 5 - 6 秒钟:

image.png

根本能够看到 server 一旦空进去点窗口,client 马上就发送数据,因为这点窗口太小,rtt 是 40ms,也就是一个 rtt 能力传 3456 字节的数据,整个带宽才用到 80-90K,齐全没跑满。

image.png

比拟显著距离 40ms 一个期待台阶,台阶之间两个包大略 3K 数据,总的传输效率如下:

image.png

斜线越陡示意速度越快,从上图看整体 SQL 上传花了 5.5 秒,执行 0.5 秒。

此时对应的窗口尺寸:

image.png

窗口由最开始 28K(20 个 1448)很快降到了不到 4K 的样子,而后根本游走在行将满的边缘,尽管读取慢,幸好 rtt 也大,导致最终也没有满。(这个是 3.1 的 Linux,利用 SO_RCVBUF 设置的是 8K,用一半来做接管窗口)

SO_RCVBUF 很小的时候并且 rtt 很小对性能的影响
如果同样的语句在 rtt 是 0.1ms 的话

image.png

尽管显著看到接管窗口常常跑满,然而因为 rtt 很小,一旦窗口空进去很快就告诉到对方了,所以整个过小的接管窗口也没怎么影响到整体性能

image.png

如上图 11.4 秒整个 SQL 开始,到 11.41 秒 SQL 上传完毕,11.89 秒执行结束(执行花了 0.5 秒),上传只花了 0.01 秒

接管窗口状况:

image.png

如图,接管窗口由最开始的 28K 降下来,而后始终在 5880 和满了之间跳动

image.png

从这里能够得出结论,接管窗口的大小对性能的影响,rtt 越大影响越显著,当然这里还须要应用程序配合,如果应用程序始终不读走数据即便接管窗口再大也会堆满的。

SO_RCVBUF 和 tcp window full 的坏 case
image.png

上图中红色平台局部,进展了大略 6 秒钟没有发任何有内容的数据包,这 6 秒钟具体在做什么如下图所示,能够看到这个时候接管方的 TCP Window Full,同时也能看到接管方(3306 端口)的 TCP Window Size 是 8192(8K),发送方(27545 端口)是 20480.

image.png

这个情况跟后面形容的 recv buffer 太小不一样,8K 是很小,然而因为 rtt 也很小,所以 server 总是能很快就 ack 收到了,接管窗口也始终不容易达到 full 状态,然而一旦接管窗口达到了 full 状态,竟然须要惊人的 6 秒钟能力复原,这期待的工夫有点太长了。这里应该是利用读取数据太慢导致了耗时 6 秒才复原,所以最终这个申请执行会十分十分慢(工夫次要耗在了上传 SQL 而不是执行 SQL).

理论起因不晓得,从读取 TCP 数据的逻辑来看这里没有显著的 block,可能的起因:

request 的 SQL 太大,Server(3306 端口上的服务)从 TCP 读取 SQL 须要放到一块调配好的内存,内存不够的时候须要扩容,扩容有可能触发 fgc,从图形来看,第一次满就卡顿了,而且每次满都卡顿,不像是这个起因
request 申请一次发过来的是多个 SQL,利用读取 SQL 后,将 SQL 分成多个,而后先执行第一个,第一个执行完后返回 response,再读取第二个。图形中卡顿前没有 response 返回,所以也不是这个起因
……其它未知起因
接管方不读取数据导致的接管窗口满同时有丢包产生
服务端返回数据到 client 端,TCP 协定栈 ack 这些包,然而应用层没读走包,这个时候 SO_RCVBUF 沉积满,client 的 TCP 协定栈发送 ZeroWindow 标记给服务端。也就是接收端的 buffer 堆满了(然而服务端这个时候看到的 bytes in fly 是 0,因为都 ack 了),这时服务端不能持续发数据,要等 ZeroWindow 复原。

那么接收端下层利用不读走包可能的起因:

利用代码卡顿、GC 等等
利用代码逻辑上在做其它事件(比方 Server 将 SQL 分片到多个 DB 上,Server 先读取第一个分片,如果第一个分片数据很大很大,解决也慢,那么即便第二个分片数据都返回到了 TCP 的 recv buffer,利用也没去读取其它分片的后果集,直到第一个分片读取结束。如果 SQL 带排序,那么 Server 会轮询读取多个分片,造成这种卡顿的概率小了很多)
image.png

上图这个流因为应用层不读取 TCP 数据,导致 TCP 接管 Buffer 满,进而接管窗口为 0,server 端不能再发送数据而卡住,然而 ZeroWindow 的探测包,client 都有失常回复,所以 1903 秒之后接管方窗口不为 0 后(window update)传输复原。

image.png

这个截图和前一个相似,是在 Server 上 (3003 端口) 抓到的包,不同的是接管窗口为 0 后,server 端屡次探测(Server 上抓包能看到),然而 client 端没有回复 ZeroWindow(也有可能是回复了,然而中间环节把 ack 包丢了, 或者这个探测包 client 没收到),造成 server 端认为 client 死了、不可达之类,进而重复重传,重传超过 15 次之后,server 端认为这个连贯死了,粗犷单方面断开(没有 reset 和 fin, 因为没必要,server 认为网络连通性出了问题)。

等到 1800 秒后,client 的接管窗口复原了,发个 window update 给 server,这个时候 server 认为这个连贯曾经断开了,只能回复 reset

网络不通,重传超过肯定的工夫(tcp_retries2)而后断开这个连贯是失常的,这里的问题是:

为什么这种场景下丢包了,而且是针对某个 stream 始终丢包
可能是因为这种场景下触发了中间环节的流量管控,成心丢包了(比方 proxy、slb、交换机都有可能做这种选择性的丢包)

这里 server 认为连贯断开,没有发 reset 和 fin, 因为没必要,server 认为网络连通性出了问题。client 还不晓得 server 上这个连贯清理掉了,等 client 回复了一个 window update,server 早就认为这个连贯早断了,忽然收到一个 update,莫名其妙,只能 reset

接管窗口和 SO_RCVBUF 的关系
ss 查看 socket buffer 大小
初始接管窗口个别是 mss 乘以初始 cwnd(为了和慢启动逻辑兼容,不想一下子冲击到网络),如果没有设置 SO_RCVBUF,那么会依据 net.ipv4.tcp_rmem 动态变化,如果设置了 SO_RCVBUF,那么接管窗口要向上面形容的值聚拢。

初始 cwnd 能够大抵通过查看到:

ss -itmpn dst “10.81.212.8”
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 10.xx.xx.xxx:22 10.yy.yy.yyy:12345 users:((“sshd”,pid=1442,fd=3))

     skmem:(r0,rb369280,t0,tb87040,f4096,w0,o0,bl0,d92)

Here we can see this socket has Receive Buffer 369280 bytes, and Transmit Buffer 87040 bytes.Keep in mind the kernel will double any socket buffer allocation for overhead.
So a process asks for 256 KiB buffer with setsockopt(SO_RCVBUF) then it will get 512 KiB buffer space. This is described on man 7 tcp.
初始窗口计算的代码逻辑,重点在 17 行:

/* TCP initial congestion window as per rfc6928 */
#define TCP_INIT_CWND           10
/* 3. Try to fixup all. It is made immediately after connection enters

   established state.
         */
        void tcp_init_buffer_space(struct sock *sk)
        {int tcp_app_win = sock_net(sk)->ipv4.sysctl_tcp_app_win;
      struct tcp_sock *tp = tcp_sk(sk);
      int maxwin;

    if (!(sk->sk_userlocks & SOCK_SNDBUF_LOCK))
            tcp_sndbuf_expand(sk);

    // 初始最大接管窗口计算过程
    tp->rcvq_space.space = min_t(u32, tp->rcv_wnd, TCP_INIT_CWND * tp->advmss);
    tcp_mstamp_refresh(tp);
    tp->rcvq_space.time = tp->tcp_mstamp;
    tp->rcvq_space.seq = tp->copied_seq;

    maxwin = tcp_full_space(sk);

    if (tp->window_clamp >= maxwin) {
            tp->window_clamp = maxwin;

            if (tcp_app_win && maxwin > 4 * tp->advmss)
                    tp->window_clamp = max(maxwin -
                                           (maxwin >> tcp_app_win),
                                           4 * tp->advmss);
    }

    /* Force reservation of one segment. */
    if (tcp_app_win &&
        tp->window_clamp > 2 * tp->advmss &&
        tp->window_clamp + tp->advmss > maxwin)
            tp->window_clamp = max(2 * tp->advmss, maxwin - tp->advmss);

    tp->rcv_ssthresh = min(tp->rcv_ssthresh, tp->window_clamp);
    tp->snd_cwnd_stamp = tcp_jiffies32;

}
传输过程中,最大接管窗口会动静调整,当指定了 SO_RCVBUF 后,理论 buffer 是两倍 SO_RCVBUF,然而要分出一部分(2^net.ipv4.tcp_adv_win_scale)来作为乱序报文缓存。

net.ipv4.tcp_adv_win_scale = 2 //2.6 内核,3.1 中这个值默认是 1
如果 SO_RCVBUF 是 8K,总共就是 16K,而后分出 2^2 分之一,也就是 4 分之一,还剩 12K 当做接管窗口;如果设置的 32K,那么接管窗口是 48K
static inline int tcp_win_from_space(const struct sock sk, int space)
{//space 传入的时候就曾经是 2SO_RCVBUF 了
int tcp_adv_win_scale = sock_net(sk)->ipv4.sysctl_tcp_adv_win_scale;

    return tcp_adv_win_scale <= 0 ?
            (space>>(-tcp_adv_win_scale)) :
            space - (space>>tcp_adv_win_scale); //sysctl 参数 tcp_adv_win_scale 

}
接管窗口有最大接管窗口和以后可用接管窗口。

一般来说一次中断根本都会将 buffer 中的包都取走。

image.png

绿线是最大接管窗口动静调整的过程,最开始是 146010,握手结束后稍微调整到 147210(可利用 body 减少了 12),随着数据的传输开始跳涨

image.png

上图是四个 batch insert 语句,能够看到绿色接管窗口随着数据的传输越来越大,图中蓝色竖直局部根本示意 SQL 上传,两个蓝色竖直条的距离代表这个 insert 在服务器上真正的执行工夫。这图十分平缓,示意上传没有任何瓶颈.

设置 SO_RCVBUF 后通过 wireshark 察看到的接管窗口根本
下图是设置了 SO_RCVBUF 为 8192 的理论状况:

image.png

从最开始的 14720,执行第一个 create table 语句后降到 14330,到真正执行 batch insert 就降到了 8192*1.5. 而后始终放弃在这个值

从 kernel 来看 buffer 相干信息
kernel 相干参数
sudo sysctl -a | egrep “rmem|wmem|tcp_mem|adv_win|moderate”
net.core.rmem_default = 212992
net.core.rmem_max = 212992
net.core.wmem_default = 212992 //core 是给所有的协定应用的,
net.core.wmem_max = 212992
net.ipv4.tcp_adv_win_scale = 1
net.ipv4.tcp_moderate_rcvbuf = 1
net.ipv4.tcp_rmem = 4096 87380 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304 //tcp 有本人的专用选项就不必 core 外面的值了
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
vm.lowmem_reserve_ratio = 256 256 32
net.ipv4.tcp_mem = 88560 118080 177120
发送 buffer 零碎比拟好主动调节,依附发送数据大小和 rt 延时大小,能够相应地进行调整;然而承受 buffer 就不肯定了,承受 buffer 的应用取决于收到的数据快慢和利用读走数据的速度,只能是 OS 依据零碎内存的压力来调整承受 buffer。零碎内存的压力取决于 net.ipv4.tcp_mem.

须要特地留神:tcp_wmem 和 tcp_rmem 的单位是字节,而 tcp_mem 的单位的页面

image.png

kernel 相干源码
从内核代码来看如果利用代码设置了 sndbuf(比方 java 代码中:socket.setOption(sndbuf, socketSendBuffer))那么理论会调配 socketSendBuffer* 2 的大小进去

image.png

比方利用代码有如下设置:

protected int socketRecvBuffer = 32 * 1024; // 接管 32K
protected int socketSendBuffer = 64 * 1024; // 发送 64K,理论会调配 128K

 // If bufs set 0, using '/etc/sysctl.conf' system settings on default
 // refer: net.ipv4.tcp_wmem / net.ipv4.tcp_rmem
 if (socketRecvBuffer > 0) {socket.setReceiveBufferSize(socketRecvBuffer);
 }
 if (socketSendBuffer > 0) {socket.setSendBufferSize(socketSendBuffer);
 }

理论会看到这样的:

tcp ESTAB 45 0 10.0.186.140:3306 10.0.186.70:26494 skmem:(r768,rb65536,t0,tb131072,f3328,w0,o0,bl0,d0)
tcp ESTAB 0 0 10.0.186.140:3306 10.0.186.70:26546 skmem:(r0,rb65536,t0,tb131072,f4096,w0,o0,bl0,d0)
为什么 kernel 要 double 接管和发送 buffer 能够参考 man7 中的 socket 帮忙信息

image.png

tcp 包发送流程
image.png

(图片来自)

用 tc 结构延时和带宽限度的模仿重现环境
sudo tc qdisc del dev eth0 root netem delay 20ms
sudo tc qdisc add dev eth0 root tbf rate 500kbit latency 50ms burst 15kb
内核观测 tcp_mem 是否有余
因 tcp_mem 达到限度而无奈发包或者产生抖动的问题,咱们也是能够观测到的。为了不便地观测这类问题,Linux 内核外面预置了动态观测点:sock_exceed_buf_limit(须要 4.16+ 的内核版本)。

$ echo 1 > /sys/kernel/debug/tracing/events/sock/sock_exceed_buf_limit/enable

而后去看是否有该事件产生:

$ cat /sys/kernel/debug/tracing/trace_pipe

如果有日志输入(即产生了该事件),就意味着你须要调大 tcp_mem 了,或者是须要断开一些 TCP 连贯了。

或者通过 systemtap 来察看
如下是 tcp_sendmsg 流程,sk_stream_wait_memory 就是 tcp_wmem 不够的时候触发期待:

image.png

如果 sendbuffer 不够就会卡在上图中的第一步 sk_stream_wait_memory, 通过 systemtap 脚本能够验证:

#!/usr/bin/stap

# Simple probe to detect when a process is waiting for more socket send
# buffer memory. Usually means the process is doing writes larger than the
# socket send buffer size or there is a slow receiver at the other side.
# Increasing the socket's send buffer size might help decrease application
# latencies, but it might also make it worse, so buyer beware.

probe kernel.function(“sk_stream_wait_memory”)
{

printf("%u: %s(%d) blocked on full send buffern",
    gettimeofday_us(), execname(), pid())

}

probe kernel.function(“sk_stream_wait_memory”).return
{

printf("%u: %s(%d) recovered from full send buffern",
    gettimeofday_us(), execname(), pid())

}

Typical output: timestamp in microseconds: procname(pid) event

1218230114875167: python(17631) blocked on full send buffer

1218230114876196: python(17631) recovered from full send buffer

1218230114876271: python(17631) blocked on full send buffer

1218230114876479: python(17631) recovered from full send buffer

其它案例剖析
从如下案例能够看到在时延 5ms 和 1ms 的时候,别离执行雷同的 SQL,SQL 查问后果 13M,耗时别离为 4.6 和 0.8 秒

$time mysql -h127.1 -e “select * from test;” >/tmp/result.txt
real 0m3.078s
user 0m0.273s
sys 0m0.028s

$ping -c 1 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=5.01 ms

— 127.0.0.1 ping statistics —
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 5.018/5.018/5.018/0.000 ms

$ls -lh /tmp/result.txt
-rw-rw-r– 1 admin admin 13M Mar 12 12:51 /tmp/result.txt

// 减小时延后持续测试
$ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=1.01 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=1.02 ms
^C
— 127.0.0.1 ping statistics —
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.016/1.019/1.022/0.003 ms

$time mysql -h127.1 -e “select * from test;” >/tmp/result.txt
real 0m0.838s
user 0m0.271s
sys 0m0.030s

// 通过 ss 能够看到这个连贯的 buffer 大小相干信息,3306 端口 socket 的 send buffer 为 32K;
//7226 为客户端,发送 buffer 为 128K,OS 默认参数
tcp ESTAB 0 0 127.0.0.1:7226 127.0.0.1:3306 skmem:(r0,rb131072,t2,tb2626560,f24576,w0,o0,bl0,d0)
tcp ESTAB 0 20480 127.0.0.1:3306 127.0.0.1:7226 skmem:(r0,rb16384,t0,tb32768,f1792,w26880,o0,bl0,d0)
在这个案例中 send buffer 为 32K(代码中设置的 16K,内核会再翻倍,所以是 32K),如果时延 5 毫秒时,一秒钟最多执行 200 次来回,也就是一秒钟能传输:200*32K=6.4M,总大小为 13M,也就是最快须要 2 秒钟能力传输行完,另外 MySQL innodb 执行耗时 0.5ms,也就是极限速度也就是 2.5 秒 + 了。

这个场景下想要快得缩小 rt 或者减少 send buffer,减少接收端的 buffer 没有意义,比方如下代码减少 client 的 –net-buffer-length=163840000 没有任何帮忙

time mysql –net-buffer-length=163840000 -h127.1 -e“select * from test;”>/tmp/result.txt

总结
一般来说相对不要在程序中手工设置 SO_SNDBUF 和 SO_RCVBUF,内核主动调整比你做的要好;
SO_SNDBUF 个别会比发送滑动窗口要大,因为发送进来并且 ack 了的能力从 SO_SNDBUF 中开释;
代码中设置的 SO_SNDBUF 和 SO_RCVBUF 在内核中会翻倍调配;
TCP 接管窗口跟 SO_RCVBUF 关系很简单;
SO_RCVBUF 太小并且 rtt 很大的时候会重大影响性能;
接管窗口比发送窗口简单多了;
发送窗口 /SO_SNDBUF–发送仓库,带宽 / 拥塞窗口–马路通顺水平,接管窗口 /SO_RCVBUF–接管仓库;
发送仓库、马路宽度、长度(rt)、接管仓库一起决定了传输速度–类比一下快递过程。
总之记住一句话:不要设置 socket 的 SO_SNDBUF 和 SO_RCVBUF

对于传输速度的总结:窗口要足够大,包含发送、接管、拥塞窗口等,天然就能将 BDP 跑满

相干和参考文章
用 stap 从内核角度来剖析 buffer、rt 和速度

经典的 nagle 和 dalay ack 对性能的影响 就是要你懂 TCP– 最经典的 TCP 性能问题

对于 TCP 半连贯队列和全连贯队列

MSS 和 MTU 导致的喜剧

双 11 通过网络优化晋升 10 倍性能

就是要你懂 TCP 的握手和挥手

正文完
 0