0%

网络性能调优(TCP/UDP)

检查协议栈buffer

检查buffer大小,修改之后重启网络服务:

sysctl -a |grep net.core
net.core.rmem_default = 212992
net.core.rmem_max = 212992

检查网卡收包情况:

2015 git:(master)✗ > netstat -su
IcmpMsg:
    InType3: 366
    OutType3: 325
Udp:
    88110 packets received
    325 packets to unknown port received.
    0 packet receive errors
    6219 packets sent
    IgnoredMulti: 2208
UdpLite:
IpExt:
    InMcastPkts: 1945
    OutMcastPkts: 35
    InBcastPkts: 42730
    OutBcastPkts: 261
    InOctets: 192296247659
    OutOctets: 192200794856
    InMcastOctets: 220327
    OutMcastOctets: 5370
    InBcastOctets: 4953400
    OutBcastOctets: 30722
    InNoECTPkts: 6131438
    InECT0Pkts: 127

检查socket收发缓冲区

TCP 的性能取决于几个方面的因素。两个最重要的因素是:

  1. 链接带宽(link bandwidth)(报文在网络上传输的速率)
  2. 往返时间(round-trip time) 或 RTT(发送报文与接收到另一端的响应之间的延时)

这两个值确定了称为 Bandwidth Delay Product(BDP)的内容
给定链接带宽和 RTT 之后,您就可以计算出 BDP 的值了,
BDP 给出了一种简单的方法来计算理论上最优的 TCP socket 缓冲区大小(其中保存了排队等待传输和等待应用程序接收的数据)。

如果缓冲区太小,那么 TCP 窗口就不能完全打开,这会对性能造成限制。
如果缓冲区太大,那么宝贵的内存资源就会造成浪费。
如果您设置的缓冲区大小正好合适,那么就可以完全利用可用的带宽。下面我们来看一个例子:

如果应用程序是通过一个 100Mbps 的局域网进行通信,其 RRT 为 50 ms,那么 BDP 就是:

BDP = link_bandwidth * RTT
100MBps * 0.050 sec / 8 = 0.625MB = 625KB

注意:此处除以 8 是将位转换成通信使用的字节。

因此,我们可以将 TCP 窗口设置为 BDP 或 1.25MB。
但是在 Linux 2.6 上默认的 TCP 窗口大小是 110KB,这会将连接的带宽限制为 2.2MBps,计算方法如下:

throughput = window_size / RTT
110KB / 0.050 = 2.2MBps

如果使用上面计算的窗口大小,我们得到的带宽就是 12.5MBps,计算方法如下:

625KB / 0.050 = 12.5MBps

差别的确很大,并且可以为 socket 提供更大的吞吐量。
Sockets API 提供了几个 socket 选项,其中两个可以用于修改 socket 的发送和接收缓冲区的大小。
使用 SO_SNDBUF 和 SO_RCVBUF 选项来调整发送和接收缓冲区的大小
注意:尽管 socket 缓冲区的大小确定了通告 TCP 窗口的大小,但是 TCP 还在通告窗口内维护了一个拥塞窗口。
因此,由于这个拥塞窗口的存在,给定的 socket 可能永远都不会利用最大的通告窗口。

修改默认socket收发缓冲区大小:

int ret, sock, sock_buf_size;
sock = socket( AF_INET, SOCK_STREAM, 0  );
sock_buf_size = 64*1024;
ret = setsockopt( sock, SOL_SOCKET, SO_SNDBUF,
        (char *)&sock_buf_size, sizeof(sock_buf_size) );
ret = setsockopt( sock, SOL_SOCKET, SO_RCVBUF,
        (char *)&sock_buf_size, sizeof(sock_buf_size) );

禁用 Nagle 算法

在通过 TCP socket 进行通信时,数据都拆分成了数据块,
这样它们就可以封装到给定连接的 TCP payload(指 TCP 数据包中的有效负荷)中了。
TCP payload 的大小取决于几个因素(例如最大报文长度和路径),
但是这些因素在连接发起时都是已知的。
为了达到最好的性能,我们的目标是使用尽可能多的可用数据来填充每个报文。
当没有足够的数据来填充 payload 时(也称为最大报文段长度(maximum segment size) 或 MSS),
TCP 就会采用 Nagle 算法自动将一些小的缓冲区连接到一个报文段中。

这样可以通过最小化所发送的报文的数量来提高应用程序的效率,并减轻整体的网络拥塞问题。

尽管 John Nagle 的算法可以通过将这些数据连接成更大的报文来最小化所发送的报文的数量,
但是有时您可能希望只发送一些较小的报文。
一个简单的例子是 telnet 程序,它让用户可以与远程系统进行交互,这通常都是通过一个 shell 来进行的。
如果用户被要求用发送报文之前输入的字符来填充某个报文段,那么这种方法就绝对不能满足我们的需要。
另外一个例子是 HTTP 协议。通常,客户机浏览器会产生一个小请求(一条 HTTP 请求消息),
然后 Web 服务器就会返回一个更大的响应(Web 页面)。

您应该考虑的第一件事情是 Nagle 算法满足一种需求。
由于这种算法对数据进行合并,试图构成一个完整的 TCP 报文段,因此它会引入一些延时。
但是这种算法可以最小化在线路上发送的报文的数量,因此可以最小化网络拥塞的问题。
但是在需要最小化传输延时的情况中,Sockets API 可以提供一种解决方案。
禁用 Nagle 算法,您可以设置 TCP_NODELAY socket 选项。

为TCP socket 禁用 Nagle 算法

int sock, flag, ret;
/* Create new stream socket */
sock = socket( AF_INET, SOCK_STREAM, 0  );
/* Disable the Nagle (TCP No Delay) algorithm */
flag = 1;
ret = setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)  );
if (ret == -1) {
printf("Couldn't setsockopt(TCP_NODELAY)\n");
    exit(-1);
}

使用 Samba 的实验表明,在从 Microsoft® Windows® 服务器上的 Samba 驱动器上读取数据时,禁用 Nagle 算法几乎可以加倍提高读性能。

tcp

我们知道TCP链接是有很多开销的,一个是会占用文件描述符,另一个是会开缓存
一般来说一个系统可以支持的TCP链接数是有限的,我们需要清楚地认识到TCP链接对系统的开销是很大的
正是因为TCP是耗资源的,所以,很多攻击都是让你系统上出现大量的TCP链接,把你的系统资源耗尽。比如著名的SYNC Flood攻击。

所以,我们要注意配置KeepAlive参数,这个参数的意思是定义一个时间,
如果链接上没有数据传输,系统会在这个时间发一个包,如果没有收到回应,
那么TCP就认为链接断了,然后就会把链接关闭,这样可以回收系统资源开销。
(注:HTTP层上也有KeepAlive参数)对于像HTTP这样的短链接,设置一个1-2分钟的keepalive非常重要。
这可以在一定程度上防止DoS攻击。有下面几个参数(下面这些参数的值仅供参考):

net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_fin_timeout = 30

对于TCP的TIME_WAIT这个状态,主动关闭的一方进入TIME_WAIT状态,
TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),默认为4分钟,TIME_WAIT状态下的资源不能回收。
有大量的TIME_WAIT链接的情况一般是在HTTP服务器上。对此,有两个参数需要注意,

net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_recycle=1

前者表示重用TIME_WAIT,后者表示回收TIME_WAIT的资源。

TCP还有一个重要的概念叫RWIN(TCP Receive Window Size),
这个东西的意思是,我一个TCP链接在没有向Sender发出ack时可以接收到的最大的数据包。
为什么这个很重要?因为如果Sender没有收到Receiver发过来ack,
Sender就会停止发送数据并会等一段时间,如果超时,那么就会重传。
这就是为什么TCP链接是可靠链接的原因。
重传还不是最严重的,如果有丢包发生的话,TCP的带宽使用率会马上受到影响(会盲目减半),
再丢包,再减半,然后如果不丢包了,就逐步恢复。
相关参数如下:

net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

一般来说,理论上的RWIN应该设置成:吞吐量 * 回路时间
Sender端的buffer应该和RWIN有一样的大小
因为Sender端发送完数据后要等Receiver端确认,如果网络延时很大,
buffer过小了,确认的次数就会多,于是性能就不高,对网络的利用率也就不高了。
也就是说,对于延迟大的网络,我们需要大的buffer,这样可以少一点ack,多一些数据,对于响应快一点的网络,可以少一些buffer
因为,如果有丢包(没有收到ack),buffer过大可能会有问题,因为这会让TCP重传所有的数据,反而影响网络性能。
(当然,网络差的情况下,就别玩什么高性能了) 所以,
高性能的网络重要的是要让网络丢包率非常非常地小(基本上是用在LAN里),
如果网络基本是可信的,这样用大一点的buffer会有更好的网络传输性能(来来回回太多太影响性能了)。

udp

说到UDP的调优,有一些事我想重点说一样,那就是MTU——最大传输单元(其实这对TCP也一样,因为这是链路层上的东西)
对于一个UDP的包,我们要尽量地让他大到MTU的最大尺寸再往网络上传,这样可以最大化带宽利用率。
对于这个MTU,以太网是1500字节,光纤是4352字节,802.11无线网是7981。
但是,当我们用TCP/UDP发包的时候,
我们的有效负载Payload要低于这个值,因为IP协议会加上20个字节,UDP会加上8个字节(TCP加的更多)
所以,一般来说,你的一个UDP包的最大应该是1500-8-20=1472,这是你的数据的大小。
当然,如果你用光纤的话, 这个值就可以更大一些。

再多说一下,使用Socket编程的时候,你可以使用setsockopt() 设置 SO_SNDBUF/SO_RCVBUF 的大小,TTL和KeepAlive这些关键的设置

多路复用

关于多路复用技术,也就是用一个线程来管理所有的TCP链接,有三个系统调用要重点注意:

  1. 一个是select,这个系统调用只支持上限1024个链接
  2. 第二个是poll,其可以突破1024的限制,但是select和poll本质上是使用的轮询机制,
    轮询机制在链接多的时候性能很差,因主是O(n)的算法
  3. 所以,epoll出现了,epoll是操作系统内核支持的,仅当在链接活跃时,操作系统才会callback,
    这是由操作系统通知触发的,但其只有Linux Kernel 2.6以后才支持(准确说是2.5.44中引入的),
    当然,如果所有的链接都是活跃的,过多的使用epoll_ctl可能会比轮询的方式还影响性能,不过影响的不大。

另外,关于一些和DNS Lookup的系统调用要小心,比如:gethostbyaddr/gethostbyname,
这个函数可能会相当的费时,因为其要到网络上去找域名,因为DNS的递归查询,会导致严重超时,
而又不能通过设置什么参数来设置time out

对此你可以通过配置hosts文件来加快速度,或是自己在内存中管理对应表,在程序启动时查好,
而不要在运行时每次都查。

另外,在多线程下面,gethostbyname会一个更严重的问题,
就是如果有一个线程的gethostbyname发生阻塞,
其它线程都会在gethostbyname处发生阻塞
,这个比较变态,要小心。
(你可以试试GNU的gethostbyname_r(),这个的性能要好一些) 这种到网上找信息的东西很多,
比如,如果你的Linux使用了NIS,或是NFS,某些用户或文件相关的系统调用就很慢,所以要小心。

检查防火墙

iptables -L

如果iptables工作,需要关闭:

service iptables stop

Linux内核参数优化

  1. /proc/sys/net/core/rmem_max — 最大的TCP数据接收缓冲。
  2. /proc/sys/net/core/wmem_max — 最大的TCP数据发送缓冲。
  3. /proc/sys/net/ipv4/tcp_timestamps — 时间戳在(请参考RFC 1323)TCP的包头增加12个字节。
  4. /proc/sys/net/ipv4/tcp_sack — 有选择的应答。
  5. /proc/sys/net/ipv4/tcp_window_scaling — 支持更大的TCP窗口. 如果TCP窗口最大超过65535(64KB), 必须设置该数值为1。
  6. rmem_default — 默认的接收窗口大小。
  7. rmem_max — 接收窗口的最大大小。
  8. wmem_default — 默认的发送窗口大小。
  9. wmem_max — 发送窗口的最大大小。

/proc目录下的所有内容都是临时性的, 所以重启动系统后任何修改都会丢失。
建议在系统启动时自动修改TCP/IP参数,两种方式:

  1. 修改/etc/rc.local
  2. 修改/etc/sysctl.conf

GUN/Linux网络工具

GNU/Linux 提供了几个工具 —— 有些是 GNU/Linux 自己提供的,
有些是开放源码软件 —— 用于调试网络应用程序,测量带宽/吞吐量,以及检查链接的使用情况。

任何 GNU/Linux 发行版中都可以找到的工具:

  1. ping这是用于检查主机的可用性的最常用的工具,但是也可以用于识别带宽延时产品计算的 RTT。
  2. traceroute打印某个连接到网络主机所经过的包括一系列路由器和网关的路径(路由),从而确定每个 hop 之间的延时。
  3. netstat确定有关网络子系统、协议和连接的各种统计信息。
  4. tcpdump显示一个或多个连接的协议级的报文跟踪信息;其中还包括时间信息,您可以使用这些信息来研究不同协议服务的报文时间。

GNU/Linux 发行版中没有提供的有用性能工具:

  1. netlog为应用程序提供一些有关网络性能方面的信息。
  2. nettimer为瓶颈链接带宽生成一个度量标准;可以用于协议的自动优化。
  3. Ethereal以一个易于使用的图形化界面提供了 tcpump(报文跟踪)的特性。
  4. iperf测量 TCP 和 UDP 的网络性能;测量最大带宽,并汇报延时和数据报的丢失情况。

Ref

  1. 提高 Linux 上 socket 性能
  2. 浅谈linux性能调优之十四:调节socket缓冲区
  3. 性能调优攻略