在Linux上做网络应用的性能优化时,一般都会对TCP相关的内核参数进行调节,特别是和缓冲、队列有关的参数。网上搜到的文章会告诉你需要修改哪些参数,但我们经常是知其然而不知其所以然,每次照抄过来后,可能很快就忘记或混淆了它们的含义。本文尝试总结TCP队列缓冲相关的内核参数,从协议栈的角度梳理它们,希望可以更容易的理解和记忆。注意,本文内容均来源于参考文档,没有去读相关的内核源码做验证,不能保证内容严谨正确。作为Java程序员没读过内核源码是硬伤。

下面我以server端为视角,从 连接建立、 数据包接收 和 数据包发送 这3条路径对参数进行归类梳理。

一、连接建立

简单看下连接的建立过程,客户端向server发送SYN包,server回复SYN+ACK,同时将这个处于SYN_RECV状态的连接保存到半连接队列。客户端返回ACK包完成三次握手,server将ESTABLISHED状态的连接移入accept队列,等待应用调用accept()。
可以看到建立连接涉及两个队列:

  • 半连接队列,保存SYN_RECV状态的连接。队列长度由net.ipv4.tcp_max_syn_backlog设置
  • accept队列,保存ESTABLISHED状态的连接。队列长度为min(net.core.somaxconn, backlog)。其中backlog是我们创建ServerSocket(int port,int backlog)时指定的参数,最终会传递给listen方法:

    #include
    int listen(int sockfd, int backlog);

    如果我们设置的backlog大于net.core.somaxconn,accept队列的长度将被设置为net.core.somaxconn

另外,为了应对SYN flooding(即客户端只发送SYN包发起握手而不回应ACK完成连接建立,填满server端的半连接队列,让它无法处理正常的握手请求),Linux实现了一种称为SYN cookie的机制,通过net.ipv4.tcp_syncookies控制,设置为1表示开启。简单说SYN cookie就是将连接信息编码在ISN(initial sequence number)中返回给客户端,这时server不需要将半连接保存在队列中,而是利用客户端随后发来的ACK带回的ISN还原连接信息,以完成连接的建立,避免了半连接队列被攻击SYN包填满。对于一去不复返的客户端握手,不理它就是了。

二、数据包的接收

先看看接收数据包经过的路径:

数据包的接收,从下往上经过了三层:网卡驱动、系统内核空间,最后到用户态空间的应用。Linux内核使用sk_buff(socket kernel buffers)数据结构描述一个数据包。当一个新的数据包到达,NIC(network interface controller)调用DMA engine,通过Ring Buffer将数据包放置到内核内存区。Ring Buffer的大小固定,它不包含实际的数据包,而是包含了指向sk_buff的描述符。当Ring Buffer满的时候,新来的数据包将给丢弃。一旦数据包被成功接收,NIC发起中断,由内核的中断处理程序将数据包传递给IP层。经过IP层的处理,数据包被放入队列等待TCP层处理。每个数据包经过TCP层一系列复杂的步骤,更新TCP状态机,最终到达recv Buffer,等待被应用接收处理。有一点需要注意,数据包到达recv Buffer,TCP就会回ACK确认,既TCP的ACK表示数据包已经被操作系统内核收到,但并不确保应用层一定收到数据(例如这个时候系统crash),因此一般建议应用协议层也要设计自己的确认机制。

上面就是一个相当简化的数据包接收流程,让我们逐层看看队列缓冲有关的参数。

  1. 网卡Bonding模式
    当主机有1个以上的网卡时,Linux会将多个网卡绑定为一个虚拟的bonded网络接口,对TCP/IP而言只存在一个bonded网卡。多网卡绑定一方面能够提高网络吞吐量,另一方面也可以增强网络高可用。Linux支持7种Bonding模式:

    • Mode 0 (balance-rr) Round-robin策略,这个模式具备负载均衡和容错能力
    • Mode 1 (active-backup) 主备策略,在绑定中只有一个网卡被激活,其他处于备份状态
    • Mode 2 (balance-xor) XOR策略,通过源MAC地址与目的MAC地址做异或操作选择slave网卡
    • Mode 3 (broadcast) 广播,在所有的网卡上传送所有的报文
    • Mode 4 (802.3ad) IEEE 802.3ad 动态链路聚合。创建共享相同的速率和双工模式的聚合组
    • Mode 5 (balance-tlb) Adaptive transmit load balancing
    • Mode 6 (balance-alb) Adaptive load balancing

    详细的说明参考内核文档Linux Ethernet Bonding Driver HOWTO。我们可以通过cat /proc/net/bonding/bond0查看本机的Bonding模式:

    一般很少需要开发去设置网卡Bonding模式,自己实验的话可以参考这篇文档

  2. 网卡多队列及中断绑定
    随着网络的带宽的不断提升,单核CPU已经不能满足网卡的需求,这时通过多队列网卡驱动的支持,可以将每个队列通过中断绑定到不同的CPU核上,充分利用多核提升数据包的处理能力。
    首先查看网卡是否支持多队列,使用lspci -vvv命令,找到Ethernet controller项:

    如果有MSI-X, Enable+ 并且Count > 1,则该网卡是多队列网卡。
    然后查看是否打开了网卡多队列。使用命令cat /proc/interrupts,如果看到eth0-TxRx-0表明多队列支持已经打开:

    最后确认每个队列是否绑定到不同的CPU。cat /proc/interrupts查询到每个队列的中断号,对应的文件/proc/irq/${IRQ_NUM}/smp_affinity为中断号IRQ_NUM绑定的CPU核的情况。以十六进制表示,每一位代表一个CPU核:

    (00000001)代表CPU0
    (00000010)代表CPU1
    (00000011)代表CPU0和CPU1

    如果绑定的不均衡,可以手工设置,例如:

    echo "1" > /proc/irq/99/smp_affinity
    echo "2" > /proc/irq/100/smp_affinity
    echo "4" > /proc/irq/101/smp_affinity
    echo "8" > /proc/irq/102/smp_affinity
    echo "10" > /proc/irq/103/smp_affinity
    echo "20" > /proc/irq/104/smp_affinity
    echo "40" > /proc/irq/105/smp_affinity
    echo "80" > /proc/irq/106/smp_affinity
  3. Ring Buffer
    Ring Buffer位于NIC和IP层之间,是一个典型的FIFO(先进先出)环形队列Ring Buffer没有包含数据本身,而是包含了指向sk_buffsocket kernel buffers)的描述符。
    可以使用ethtool -g eth0查看当前Ring Buffer的设置:

    上面的例子接收队列为4096,传输队列为256。可以通过ifconfig观察接收和传输队列的运行状况:

    • RX errors:收包总的错误数
    • RX dropped: 表示数据包已经进入了Ring Buffer,但是由于内存不够等系统原因,导致在拷贝到内存的过程中被丢弃。
    • RX overruns: overruns意味着数据包没到Ring Buffer就被网卡物理层给丢弃了,而CPU无法及时的处理中断是造成Ring Buffer满的原因之一,例如中断分配的不均匀。
      当dropped数量持续增加,建议增大Ring Buffer,使用ethtool -G进行设置。
  4. Input Packet Queue(数据包接收队列)
    当接收数据包的速率大于内核TCP处理包的速率,数据包将会缓冲在TCP层之前的队列中。接收队列的长度由参数net.core.netdev_max_backlog设置。

  5. recv Buffer
    recv buffer是调节TCP性能的关键参数。BDP(Bandwidth-delay product,带宽延迟积) 是网络的带宽和与RTT(round trip time)的乘积,BDP的含义是任意时刻处于在途未确认的最大数据量。RTT使用ping命令可以很容易的得到。为了达到最大的吞吐量,recv Buffer的设置应该大于BDP,即recv Buffer >= bandwidth * RTT。假设带宽是100Mbps,RTT是100ms,那么BDP的计算如下:

    BDP = 100Mbps * 100ms = (100 / 8) * (100 / 1000) = 1.25MB

    Linux在2.6.17以后增加了recv Buffer自动调节机制,recv buffer的实际大小会自动在最小值和最大值之间浮动,以期找到性能和资源的平衡点,因此大多数情况下不建议将recv buffer手工设置成固定值。
    net.ipv4.tcp_moderate_rcvbuf设置为1时,自动调节机制生效,每个TCP连接的recv Buffer由下面的3元数组指定:

    net.ipv4.tcp_rmem =

    最初recv buffer被设置为,同时这个缺省值会覆盖net.core.rmem_default的设置。随后recv buffer根据实际情况在最大值和最小值之间动态调节。在缓冲的动态调优机制开启的情况下,我们将net.ipv4.tcp_rmem的最大值设置为BDP
    net.ipv4.tcp_moderate_rcvbuf被设置为0,或者设置了socket选项SO_RCVBUF,缓冲的动态调节机制被关闭。recv buffer的缺省值由net.core.rmem_default设置,但如果设置了net.ipv4.tcp_rmem,缺省值则被覆盖。可以通过系统调用setsockopt()设置recv buffer的最大值为net.core.rmem_max。在缓冲动态调节机制关闭的情况下,建议把缓冲的缺省值设置为BDP

    注意这里还有一个细节,缓冲除了保存接收的数据本身,还需要一部分空间保存socket数据结构等额外信息。因此上面讨论的recv buffer最佳值仅仅等于BDP是不够的,还需要考虑保存socket等额外信息的开销。Linux根据参数net.ipv4.tcp_adv_win_scale计算额外开销的大小:

    如果net.ipv4.tcp_adv_win_scale的值为1,则二分之一的缓冲空间用来做额外开销,如果为2的话,则四分之一缓冲空间用来做额外开销。因此recv buffer的最佳值应该设置为:

三、数据包的发送

发送数据包经过的路径:

和接收数据的路径相反,数据包的发送从上往下也经过了三层:用户态空间的应用、系统内核空间、最后到网卡驱动。应用先将数据写入TCP send buffer,TCP层将send buffer中的数据构建成数据包转交给IP层。IP层会将待发送的数据包放入队列QDisc(queueing discipline)。数据包成功放入QDisc后,指向数据包的描述符sk_buff被放入Ring Buffer输出队列,随后网卡驱动调用DMA engine将数据发送到网络链路上。

同样我们逐层来梳理队列缓冲有关的参数。

  1. send Buffer
    recv Buffer类似,和send Buffer有关的参数如下:

    net.ipv4.tcp_wmem =
    net.core.wmem_default
    net.core.wmem_max

    发送端缓冲的自动调节机制很早就已经实现,并且是无条件开启,没有参数去设置。如果指定了tcp_wmem,则net.core.wmem_defaulttcp_wmem的覆盖。send Buffertcp_wmem的最小值和最大值之间自动调节。如果调用setsockopt()设置了socket选项SO_SNDBUF,将关闭发送端缓冲的自动调节机制,tcp_wmem将被忽略,SO_SNDBUF的最大值由net.core.wmem_max限制。

  2. QDisc
    QDisc(queueing discipline )位于IP层和网卡的ring buffer之间。我们已经知道,ring buffer是一个简单的FIFO队列,这种设计使网卡的驱动层保持简单和快速。而QDisc实现了流量管理的高级功能,包括流量分类,优先级和流量整形(rate-shaping)。可以使用tc命令配置QDisc
    QDisc的队列长度由txqueuelen设置,和接收数据包的队列长度由内核参数net.core.netdev_max_backlog控制所不同,txqueuelen是和网卡关联,可以用ifconfig命令查看当前的大小:

    使用ifconfig调整txqueuelen的大小:

    ifconfig eth0 txqueuelen 2000
  3. Ring Buffer
    和数据包的接收一样,发送数据包也要经过Ring Buffer,使用ethtool -g eth0查看:

    其中TX项是Ring Buffer的传输队列,也就是发送队列的长度。设置也是使用命令ethtool -G

  4. TCP Segmentation和Checksum Offloading
    操作系统可以把一些TCP/IP的功能转交给网卡去完成,特别是Segmentation(分片)和checksum的计算,这样可以节省CPU资源,并且由硬件代替OS执行这些操作会带来性能的提升。
    一般以太网的MTU(Maximum Transmission Unit)为1500 bytes,假设应用要发送数据包的大小为7300bytes,MTU1500字节 - IP头部20字节 - TCP头部20字节=有效负载为1460字节,因此7300字节需要拆分成5个segment:

    Segmentation(分片)操作可以由操作系统移交给网卡完成,虽然最终线路上仍然是传输5个包,但这样节省了CPU资源并带来性能的提升:

    可以使用ethtool -k eth0查看网卡当前的offloading情况:

    上面这个例子checksum和tcp segmentation的offloading都是打开的。如果想设置网卡的offloading开关,可以使用ethtool -K(注意K是大写)命令,例如下面的命令关闭了tcp segmentation offload:

    sudo ethtool -K eth0 tso off
  5. 网卡多队列和网卡Bonding模式
    在数据包的接收过程中已经介绍过了。

至此,终于梳理完毕。整理TCP队列相关参数的起因是最近在排查一个网络超时问题,原因还没有找到,产生的“副作用”就是这篇文档。再想深入解决这个问题可能需要做TCP协议代码的profile,需要继续学习,希望不久的将来就可以再写文档和大家分享了。

参考文档
Queueing in the Linux Network Stack
TCP Implementation in Linux: A Brief Tutorial
Impact of Bandwidth Delay Product on TCP Throughput
Java程序员也应该知道的系统知识系列之网卡
说说网卡中断处理

Linux TCP队列相关参数的总结 转的更多相关文章

  1. Linux TCP队列相关参数的总结

    作者:阿里技术保障锋寒 原文:https://yq.aliyun.com/articles/4252 摘要: 本文尝试总结TCP队列缓冲相关的内核参数,从协议栈的角度梳理它们,希望可以更容易的理解和记 ...

  2. Linux - TCP编程相关配置2

    100万并发连接服务器笔记之处理端口数量受限问题 第二个遇到的问题:端口数量受限 一般来说,单独对外提供请求的服务不用考虑端口数量问题,监听某一个端口即可.但是向提供代理服务器,就不得不考虑端口数量受 ...

  3. Linux - TCP编程相关配置1

    100万并发连接服务器笔记之1M并发连接目标达成 第四个遇到的问题:tcp_mem 在服务端,连接达到一定数量,诸如50W时,有些隐藏很深的问题,就不断的抛出来. 通过查看dmesg命令查看,发现大量 ...

  4. linux消息队列相关操作

    /* 发送消息队列 */ #include <stdio.h>#include <stdlib.h>#include <string.h>#include < ...

  5. linux tcp相关参数

    /etc/sysctl.conf文件 /etc/sysctl.conf是一个允许你改变正在运行中的Linux系统的接口.它包含一些TCP/IP堆栈和虚拟内存系统的高级选项,可用来控制Linux网络配置 ...

  6. Linux系统调优——内核相关参数(五)

    修改内核参数有3种办法:一种临时修改,两种永久修改. 临时修改是使用sysctl [选项] [参数名=值]命令:永久修改是修改/etc/sysctl.conf文件或修改/proc/sys/目录下的对应 ...

  7. Linux 内核参数 和 Oracle相关参数调整

    Linux 内核参数 和 Oracle相关参数调整 分类: Oracle Basic Knowledge2009-10-14 12:23 9648人阅读 评论(0) 收藏 举报 oraclelinux ...

  8. Linux OOM killer 与相关参数详解

    一.前言 本文是描述Linux virtual memory运行参数的第二篇,主要是讲OOM相关的参数的.为了理解OOM参数,第二章简单的描述什么是OOM.如果这个名词对你毫无压力,你可以直接进入第三 ...

  9. 高并发情况下Linux系统及kernel参数优化

    众所周知在默认参数情况下Linux对高并发支持并不好,主要受限于单进程最大打开文件数限制.内核TCP参数方面和IO事件分配机制等.下面就从几方面来调整使Linux系统能够支持高并发环境. Iptabl ...

随机推荐

  1. Protocol Buffer详解

    1.Protocol Buffer 概念 Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 ...

  2. 手势冲突UIPanGestureRecognizer 和UIPinchGestureRecognizer

    当同时使用pan和pin手势时假如冲突,需要加入下面方法 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shou ...

  3. 检查 CPU 是否支持二级地址转换 - 摘自网络

    Windows 8 Consumer Preview 于2月正式发布,随后 Windows Server 8 Beta 也公布了下载.整体对比,Windows 8 在硬件方面的要求并不高,其最低硬件需 ...

  4. python oop __slots__方法

    动态语言python 可以在程序运行的情况下给class加上功能.具体为 #引入一个 from types import MethodType #方法 #然后 s.set_age = MethodTy ...

  5. 关于对javascript 提升概念 的总结与思考。

    最近在看一本新买的书叫<你不知道的javascript上卷>..买到这本书也算是个巧合 不过真是物有所值.它对js的几个高级概念都做了非常深刻的描述和通过一些通俗易懂的方式来让我们理解.这 ...

  6. php--opp--2.什么是类,什么是对象,类和对象这间的关系

    类的概念:类是具有相同属性和服务的一组对象的集合.它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和服务两个主要部分.在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属 ...

  7. show index 之Cardinality (mysql)

    show index  之Cardinality 官方文档的解释: Cardinality An estimate of the number of unique values in the inde ...

  8. Unity3D Script KeynoteII

    [Unity3D Script KeynoteII] 1.使用代码操作Particle. //粒子对象 GameObject particle = null; //粒子X轴方向速度 float vel ...

  9. Altium Designer 6三维元件库建模教程

    一.AD6.9 PCB编辑环境下如何使用STEP模型的方法. 在PCB 封装库中添加对应的3D模型,然后选择update pcb 即可 方法:在PCB library 页面,点击place -- 3d ...

  10. hdu 4289 Control(最小割 + 拆点)

    http://acm.hdu.edu.cn/showproblem.php?pid=4289 Control Time Limit: 2000/1000 MS (Java/Others)    Mem ...