首页 > 代码库 > 网络调优
网络调优
关于网络调优,尤其是TCP Tuning(你可以以这两个关键词在网上找到很多文章),这里面有很多很多东西可以说。看看Linux下TCP/IP的那么多参数就知道了(顺便说一下,你也许不喜欢Linux,但是你不能否认Linux给我们了很多可以进行内核调优的权力)。强烈建议大家看看《TCP/IP详解卷1:协议》这本书。我在这里只讲一些概念上的东西。
A)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呢?你想过这个问题了吗?
B)UDP调优
说到UDP的调优,有一些事我想重点说一样,那就是MTU——最大传输单元(其实这对TCP也一样,因为这是链路层上的东西)。所谓最大传输单元,你可以想像成是公路上的公交车,假设一个公交车可以最多坐70人,带宽就像是公路的车道数一样,如果一条路上最多可以容下100辆公交车,那意味着我最多可以运送7000人,但是如果公交车坐不满,比如平均每辆车只有20人,那么我只运送了2000人,于是我公路资源(带宽资源)就被浪费了。所以,我们对于一个UDP的包,我们要尽量地让他大到MTU的最大尺寸再往网络上传,这样可以最大化带宽利用率。对于这个MTU,以太网是1500字节,光纤是4352字节,802.11无线网是7981。但是,当我们用TCP/UDP发包的时候,我们的有效负载Payload要低于这个值,因为IP协议会加上20个字节,UDP会加上8个字节(TCP加的更多),所以,一般来说,你的一个UDP包的最大应该是1500-8-20=1472,这是你的数据的大小。当然,如果你用光纤的话,这个值就可以更大一些。(顺便说一下,对于某些NB的千光以态网网卡来说,在网卡上,网卡硬件如果发现你的包的大小超过了MTU,其会帮你做fragment,到了目标端又会帮你做重组,这就不需要你在程序中处理了)
再多说一下,使用Socket编程的时候,你可以使用setsockopt() 设置SO_SNDBUF/SO_RCVBUF的大小,TTL和KeepAlive这些关键的设置,当然,还有很多,具体你可以查看一下Socket的手册。!!!
最后说一点,UDP还有一个最大的好处是multi-cast多播,这个技术对于你需要在内网里通知多台结点时非常方便和高效。而且,多播这种技术对于机会的水平扩展(需要增加机器来侦听多播信息)也很有利。
C)网卡调优
对于网卡,我们也是可以调优的,这对于千兆以及网网卡非常必要,在Linux下,我们可以用ifconfig查看网上的统计信息,如果我们看到overrun上有数据,我们就可能需要调整一下txqueuelen的尺寸(一般默认为1000),我们可以调大一些,如:ifconfig eth0 txqueuelen 5000。Linux下还有一个命令叫:ethtool可以用于设置网卡的缓冲区大小。在Windows下,我们可以在网卡适配器中的高级选项卡中调整相关的参数(如:Receive Buffers, Transmit Buffer等,不同的网卡有不同的参数)。把Buffer调大对于需要大数据量的网络传输非常有效。
D)其它网络性能
关于多路复用技术,也就是用一个线程来管理所有的TCP链接,有三个系统调用要重点注意:一个是select,这个系统调用只支持上限1024个链接,第二个是poll,其可以突破1024的限制,但是select和poll本质上是使用的轮询机制,轮询机制在链接多的时候性能很差,因主是O(n)的算法,所以,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,某些用户或文件相关的系统调用就很慢,所以要小心。
网络调优