• socket与Linux TCP


    继续填满长肥管道,接上回:

    填满TCP长肥管道

    修正了TCP流控rwnd算法后,吞吐就与RTT和rcvbuff解耦了,它的值就是横截面:
    在这里插入图片描述
    buffer的目的是平滑到达和处理之间的gap而不再作为“要被填满的批量搜集站”,好处至少两点:

    • 解决了rcvbuffer的bufferbloat,就平滑了主机处理延时抖动。
    • 数据读取速率直接取决于CPU而不是rcvbuffer的配置。

    但关于这个buffer,又有另一个问题。这个问题可能就是损失那2Gbps(理论25Gbps,实际23Gbps)的祸首。

    之前说过,Linux TCP非全双工,socket读写不能(多CPU)并行,ACK和触发发送不能并行,除了这些,软中断Data收和进程socket收也不能并行。

    换句话说软中断和socket无法同时将数据放入receive queue和从中取走数据。

    无论TCP软中断处理还是socket收,都直接lock或own整个socket,造成软中断和socket进程的串行处理。具体参考Linux内核协议栈的backlog实现。

    lock或own整个socket显然粒度太粗,需要lock的只是TCP连接的元数据。

    Linux UDP之前也有这个问题,但UDP本身无状态,也就元数据需要保护,针对Linux UDP的优化一气呵成,可参见2019年的一篇文章:

    Linux内核UDP收包为什么效率低?能做什么优化?

    给出一个UDP双队列优化后的图示:
    在这里插入图片描述
    Linux TCP也需要类似的双队列buffer,两个队列原子转移,深入细致地管理这个双队列buffer,目的是软中断往buffer放数据的同时,socket可在另一个CPU上从buffer取数据。

    这才真正实现TCP协议栈到进程并行流式交接,buffer作为平滑适配器而不是批量搜集站,这是真正的排队系统,socket就像网络中继转发节点,将数据从TCP转交给进程。

    如昨天文中所说,进程不一定是最后一站,可能还要落盘,IO库,文件系统,磁盘buffer也依然要如是转交。
    话回正途,Linux TCP拆解这个socket lock范围很难,我尝试将receiveve queue独立出来,但socket读也要独立才行,最终失败。

    退而求其次,在socket读频率和每次读取的数量之间找一个平衡点。SO_LOWAT正好做这事,但iperf工具不支持该sockopt,可以用LD_PRELOAD来做,也可以用stap:

    #!/usr/local/bin/stap -g
    
    %{
    #include <net/tcp.h>
    %}
    
    function alter_lowat(skk:long)
    %{
    	struct sock *sk = (struct sock *)STAP_ARG_skk;
    
    	if (inet_sk(sk)->inet_num == 5001 && sk->sk_rcvlowat == 1) {
    		sk->sk_rcvlowat = 65500*2;
    		STAP_PRINTF("%d\n", sk->sk_rcvlowat);
    	}
    %}
    
    probe kernel.function("tcp_data_ready")
    {
    	alter_lowat($sk);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    意思是buffer里至少攒够了65500*2字节数据后才wakeup进程。保持相同的速率,CPU利用率下降20%。

    此外就是调节系统调度器了,我们追求的目标是软中断将n字节放入receive queue和socket从receive queue中读n字节这两件事交替进行,但这几乎做不到:

    • GRO/LRO必须开启,ACK过多会抑制发送。
    • GRO/LRO无法确定数据包长度。
    • Linux软中断无法精确控制pacing。
    • wakeup收包进程无法保证它立即运行。

    不过25Gbps尚未touch到CPU瓶颈,若100Gbps打进主机,恐怕上述问题就必须考虑了,由于Linux内核本身是一个复杂的控制面,跑100Gbps注定会吃力,任何wakeup模式因调度延时不确定而不能信任,busy poll模式就更加适合。不过这些都是后话。

    终于还是避不开TCP单机性能的损失,核心问题不是调度系统调优,如果真去那般做,可能路子本身就是错的,内核本是一个控制面,内核协议栈跑数据面尚且还好因为尚未touch到CPU瓶颈,但随着带宽持续演进,早晚会touch到CPU瓶颈。作一短文,备忘。

    浙江温州皮鞋湿,下雨进水不会胖。

  • 相关阅读:
    [ue5]建模场景学习笔记(1)——混合材质
    PostgreSQL+GeoHash地图点位聚合
    ABAP Debug 调试功能
    使用shuffle sharding增加容错性
    JAVA计算机毕业设计中小学图书馆管理Mybatis+源码+数据库+lw文档+系统+调试部署
    山东大学人工智能导论实验一 numpy的基本操作
    【JVM技术专题】让你完全攻克内存溢出(OOM)这一难题「案例篇」
    面试题—JAVA基础9.19/20
    数据结构 1.1 初学数据结构
    基于javaweb的电力设备监测管理系统(servlet+jsp)
  • 原文地址:https://blog.csdn.net/dog250/article/details/124992654