优化Linux内核中的TCP参数来提高网络处理能力
2011-01-19 14:08:13 阿炯

一台服务器CPU和内存资源额定有限的情况下,如何提高服务器的性能是作为系统运维的重要工作。要提高Linux系统下的负载能力,当网站发展起来之后,web连接数过多的问题就会日益明显。在节省成本的情况下,可以考虑修改Linux 的内核TCP/IP参数来部分实现;如果通过修改内核参数也无法解决的负载问题,也只能考虑升级服务器了。

Linux系统下,TCP/IP连接断开后,会以TIME_WAIT状态保留一定的时间,然后才会释放端口。当并发请求过多的时候,就会产生大量的TIME_WAIT状态的连接,无法及时断开的话,会占用大量的端口资源和服务器资源(因为关闭后进程才会退出)。这个时候我们可以考虑优化TCP/IP的内核参数,来及时将TIME_WAIT状态的端口清理掉。

本文介绍的方法只对拥有大量TIME_WAIT状态的连接导致系统资源消耗有效,不是这个原因的情况下,效果可能不明显。那么,到哪儿去查TIME_WAIT状态的连接呢?那就是使用netstat命令。我们可以输入一个复核命令,去查看当前TCP/IP连接的状态和对应的个数:
# netstat -an | awk '/^tcp/ {++s[$NF]} END {for(a in s) print a, s[a]}'

这个命令会显示出类似下面的结果:
TIME_WAIT 63648
FIN_WAIT1 3
FIN_WAIT2 4
ESTABLISHED 184
LISTEN 17

我们只用关心TIME_WAIT的个数,在这里可以看到,有6w多个TIME_WAIT,这样就占用了6w多个端口。要知道端口的数量只有65535个,占用一个少一个,会严重的影响到后继的新连接。这种情况下,我们就有必要调整下Linux的TCP/IP内核参数,让系统更快的释放 TIME_WAIT连接。

我们用vim打开配置文件:
# vim /etc/sysctl.conf

然后,在这个文件中,加入下面的几行内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 5

最后输入下面的命令,让内核参数生效:
# /sbin/sysctl -p

简单的说明下,上面的参数的含义:
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭;

net.ipv4.tcp_fin_timeout 修改系统默认的 TIMEOUT 时间。

在经过这样的调整之后,除了会进一步提升服务器的负载能力之外,还能够防御一定程度的DDoS、CC和SYN攻击,是个一举两得的做法。如果你的连接数本身就很多,我们可以再优化一下TCP/IP的可使用端口范围,进一步提升服务器的并发能力。依然是往上面的参数文件中,加入下面这些配置:
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.ip_local_port_range = 2000 65000
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 5000

这几个参数,建议只在流量非常大的服务器上开启,会有显著的效果。一般的流量小的服务器上,没有必要去设置这几个参数。这几个参数的含义如下:

net.ipv4.tcp_keepalive_time = 1200 表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。

net.ipv4.ip_local_port_range = 2000 65000 表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为2000到65000。(注意:这里不要将最低值设的太低,否则可能会占用掉正常的端口!)
net.ipv4.tcp_max_syn_backlog = 8192 表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。

net.ipv4.tcp_max_tw_buckets = 5000 表示系统同时保持TIME_WAIT的最大数量,如果超过这个数字,TIME_WAIT将立刻被清除并打印警告信息。默认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid来说效果却不大。此项参数可以控制TIME_WAIT的最大数量,避免Squid服务器被大量的TIME_WAIT拖垮。

经过这样的配置之后,你的服务器的TCP/IP并发能力又会上一个新台阶。在存在大量短连接的情况下,Linux的TCP栈一般都会生成大量的 TIME_WAIT 状态的socket:
# netstat -ant|grep -i time_wait |wc -l

可能会超过三四万。这个时候,我们需要修改 linux kernel 的 tcp time wait的时间,有个 sysctl 参数貌似可以使用,它是 /proc/sys/net/ipv4/tcp_fin_timeout,缺省值是 60,也就是60秒,很多网上的资料都说将这个数值设置低一些就可以减少netstat 里面的TIME_WAIT状态,但是这个说法不是很准确的。经过认真阅读Linux的内核源代码,我们发现这个数值其实是输出用的,修改之后并没有真正的读回内核中进行使用,而内核中真正管用的是一个宏定义,在 $KERNEL/include/net/tcp.h里面,有下面的行:
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */

而这个宏是真正控制 TCP TIME_WAIT 状态的超时时间的。如果我们希望减少 TIME_WAIT 状态的数目(从而节省一点点内核操作时间),那么可以把这个数值设置低一些,根据我们的测试,设置为 10 秒比较合适,也就是把上面的修改为:
#define TCP_TIMEWAIT_LEN (10*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */

然后重新编译内核,重启系统即可发现短连接造成的TIME_WAIT状态大大减少:
netstat -ant | grep -i time_wait |wc -l

一般情况都可以至少减少2/3,也能相应提高系统应对短连接的速度。下面来详细分析这些参数的用法。



Linux内核TCP/IP参数优化

TCP 相关部份

常用名词说明:
TCP 服务器 <---> 客户端通信状态
    SYN---------------->
        <--------------SYN,ACK
          ACK--------------->                                          建立连接

         Data1---------------->
                   <---------------Data1,ACK

         Data2---------------->

                   <---------------未回复

         Data2---------------->重传 [ 序列参数 tcp_sack, tcp_fack  ]

         [ 重传次数参数: tcp_retries1,tcp_retries2, tcp_orphan_retries ]

         数据传输

          FIN------------------>
                   <-----------------FIN,ACK(有时候FIN,ACK分两次)
          ACK----------------->                                         断开连接.主动关闭

          FIN------------------>
             <-----------------CLOSE_WAIT

SYN表示建立连接,RTT(Round-Trip Time): 往返时延

FIN表示关闭连接,RTO(Retransmission TimeOut)即重传超时时间

ACK表示响应,

PSH表示有 DATA数据传输,

RST表示连接重置


TCP 抓包常见错误

tcp out-of-order(tcp有问题)    #多数是网络拥塞引起的
tcp segment of a reassembled PDU    #TCP 分片标识
Tcp previous segment lost(tcp先前的分片丢失)
Tcp acked lost segment(tcp应答丢失)
Tcp window update(tcp窗口更新)
Tcp dup ack(tcp重复应答)
Tcp keep alive(tcp保持活动)
Tcp retransmission(tcp 重传)


内核参数解释

net.ipv4.tcp_timestamps = 1

说明:该参数控制RFC 1323 时间戳与窗口缩放选项。默认情况下,启用时间戳与窗口缩放,但是可以使用标志位进行控制。0位控制窗口缩放,1 位控制时间戳:
值为0(禁用 RFC 1323选项)
值为1(仅启用窗口缩放)
值为2(仅启用时间戳)
值为3(两个选项均启用)

net.ipv4.tcp_timestamps=0

说明:时间戳可以避免序列号的卷绕。一个1Gbps的链路肯定会遇到以前用过的序列号,时间戳能够让内核接受这种“异常”的数据包。这里需要将其关掉,相关的取值如下:
值为0(禁用时间戳)
值为1(启用时间戳)

只有客户端和服务端都开启时间戳的情况下,才会出现能ping通不能建立tcp三次握手的情况,所以做为提供服务的公司,不可能保证所有的用户都关闭时间戳,这个功能,所以我们必须关闭时间戳,这样才能给所用用户提供正常的服务。

net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_sack = 1

使用 Selective ACK﹐它可以用来查找特定的遗失的数据报— 因此有助于快速恢复状态。该文件表示是否启用有选择的应答(Selective Acknowledgment),这可以通过有选择地应答乱序接收到的报文来提高性能(这样可以让发送者只发送丢失的报文段)。(对于广域网通信来说这个选项应该启用,但是这会增加对 CPU 的占用。)

net.ipv4.tcp_fack = 1

打开FACK(Forward ACK) 拥塞避免和 快速重传功能。(注意,当tcp_sack设置为0的时候,这个值即使设置为1也无效)

net.ipv4.tcp_retrans_collapse = 1
net.ipv4.tcp_syn_retries = 5

对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。不应该大于255,默认值是5,对应于180秒左右时间。(对于大负载而物理通信良好的网络而言,这个值偏高,可修改为2;这个值仅仅是针对对外的连接,对进来的连接,是由tcp_retries1 决定的)

net.ipv4.tcp_synack_retries = 5

tcp_synack_retries显示或设定 Linux 核心在回应 SYN 要求时会尝试多少次重新发送初始 SYN,ACK 封包后才决定放弃。这是所谓的三段交握 (threeway handshake) 的第二个步骤。即是说系统会尝试多少次去建立由远端启始的 TCP 连线。tcp_synack_retries 的值必须为正整数,并不能超过 255。因为每一次重新发送封包都会耗费约 30 至 40 秒去等待才决定尝试下一次重新发送或决定放弃。tcp_synack_retries 的缺省值为 5,即每一个连线要在约 180 秒 (3 分钟) 后才确定逾时.

net.ipv4.tcp_max_orphans = 131072

系统所能处理不属于任何进程的TCP sockets最大数量。假如超过这个数量,那么不属于任何进程的连接会被立即reset,并同时显示警告信息。之所以要设定这个限制﹐纯粹为了抵御那些简单的 DoS 攻击﹐千万不要依赖这个或是人为的降低这个限制,更应该增加这个值(如果增加了内存之后)。每个孤儿套接字最多能够吃掉你64K不可交换的内存。

net.ipv4.tcp_max_tw_buckets = 5000

表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000。设为较小数值此项参数可以控制TIME_WAIT套接字的最大数量,避免服务器被大量的TIME_WAIT套接字拖死。

net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_intvl = 75

用实例进行说明上述三个参数:如果某个TCP连接在idle 2个小时后,内核才发起probe(探查).如果probe 9次(每次75秒既tcp_keepalive_intvl值)不成功,内核才彻底放弃,认为该连接已失效。

net.ipv4.tcp_retries1 = 3

放弃回应一个TCP 连接请求前﹐需要进行多少次重试。RFC 规定最低的数值是3﹐这也是默认值﹐根据RTO的值大约在3秒 - 8分钟之间。(注意:这个值同时还决定进入的syn连接)

(第二种解释:它表示的是TCP传输失败时不检测路由表的最大的重试次数,当超过了这个值,我们就需要检测路由表了)

net.ipv4.tcp_retries2 = 15

在丢弃激活(已建立通讯状况)的TCP连接之前﹐需要进行多少次重试。默认值为15,根据RTO的值来决定,相当于13-30分钟(RFC1122规定,必须大于100秒).(这个值根据目前的网络设置,可以适当地改小,我的网络内修改为了5)

(第二种解释:表示重试最大次数,只不过这个值一般要比上面的值大。和上面那个不同的是,当重试次数超过这个值,我们就必须放弃重试了)

net.ipv4.tcp_orphan_retries

主要是针对孤立的socket(也就是已经从进程上下文中删除了,可是还有一些清理工作没有完成).对于这种socket,我们重试的最大的次数就是它

net.ipv4.tcp_fin_timeout = 30

表示如果套接字由本端要求关闭,这个参数决定了它保持在 FIN-WAIT-2状态的时间

net.ipv4.tcp_tw_recycle = 1

表示开启TCP连接中TIME-WAITsockets的快速回收,默认为0,表示关闭

net.ipv4.tcp_stdurg = 0
net.ipv4.tcp_rfc1337 = 0
net.ipv4.tcp_max_syn_backlog = 8192

表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。

(第二种解释:端口最大backlog 内核限制。此参数限制服务端应用程序 可以设置的端口最大backlog 值 (对应于端口的 syn_backlog 和 backlog 队列长度)。动机是在内存有限的服务器上限制/避免应用程序配置超大 backlog 值而耗尽内核内存。如果应用程序设置 backlog 大于此值,操作系统将自动将之限制到此值。)

net.ipv4.tcp_abort_on_overflow = 0

当 tcp 建立连接的 3 路握手完成后,将连接置入ESTABLISHED 状态并交付给应用程序的 backlog 队列时,会检查 backlog 队列是否已满。若已满,通常行为是将连接还原至 SYN_ACK状态,以造成 3 路握手最后的 ACK 包意外丢失假象 —— 这样在客户端等待超时后可重发 ACK —— 以再次尝试进入ESTABLISHED 状态 —— 作为一种修复/重试机制。如果启用tcp_abort_on_overflow 则在检查到 backlog 队列已满时,直接发 RST 包给客户端终止此连接 —— 此时客户端程序会收到 104Connection reset by peer 错误。

警告:启用此选项可能导致高峰期用户访问体验到 104:Connection reset by peer 或白屏错误(视浏览器而定)。在考虑启用此选项前应先设法优化提高服务端应用程序的性能,使之能更快接管、处理连接。

net.ipv4.tcp_syncookies = 1

在 tcp 建立连接的 3 路握手过程中,当服务端收到最初的 SYN 请求时,会检查应用程序的 syn_backlog 队列是否已满。若已满,通常行为是丢弃此 SYN 包。若未满,会再检查应用程序的 backlog 队列是否已满。若已满并且系统根据历史记录判断该应用程序不会较快消耗连接时,则丢弃此 SYN 包。如果启用 tcp_syncookies 则在检查到 syn_backlog 队列已满时,不丢弃该 SYN 包,而改用 syncookie 技术进行 3 路握手。

警告:使用 syncookie 进行握手时,因为该技术挪用了 tcp_options 字段空间,会强制关闭 tcp 高级流控技术而退化成原始 tcp 模式。此模式会导致封包 丢失时 对端 要等待 MSL 时间来发现丢包事件并重试,以及关闭连接时 TIME_WAIT 状态保持 2MSL 时间。 该技术应该仅用于保护syn_flood 攻击。如果在正常服务器环境中服务器负载较重导致 syn_backlog 和 backlog 队列满时,应优化服务端应用程序的负载能力,加大应用程序 backlog 值。不过,所幸该参数是自动值,仅在 syn_backlog 队列满时才会触发 (在队列恢复可用时此行为关闭)。

1)、服务端应用程序设置端口backlog 值,内核理论上将允许该端口最大同时接收 2*backlog 个并发连接”请求”(不含已被应用程序接管的连接) ——分别存放在 syn_backlog 和 backlog 队列—— 每个队列的长度为backlog 值。syn_backlog 队列存储 SYN_ACK 状态的连接,backlog 则存储 ESTABLISHED 状态但尚未被应用程序接管的连接。

2)、syn_backlog 队列实际上是个 hash 表,并且 hash 表大小为 2 的次方。所以实际 syn_backlog 的队列长度要略大于应用程序设置的 backlog 值—— 取对应 2 的次方值。

3)、当 backlog 值较小,而高峰期并发连接请求超高时,tcp 建立连接的三路握手 网络时延将成为瓶颈 —— 并发连接超高时,syn_backlog 队列将被充满而导致 ` can’t connect` 错误。此时,再提高服务端应用程序的吞吐能力已不起作用,因为连接尚未建立,服务端应用程序并不能接管和处理这些连接—— 而是需要加大backlog 值 (syn_backlog 队列长度) 来缓解此问题。

4)、启用 syncookie 虽然也可以解决超高并发时的` can’t connect` 问题,但会导致 TIME_WAIT 状态 fallback 为保持 2MSL 时间,高峰期时会导致客户端无可复用连接而无法连接服务器 (tcp 连接复用是基于 四元组值必须不相同,就访问同一个目标服务器而言, 三元组值不变,所以此时可用的连接数限制为仅src_port 所允许数目,这里处于 TIME_WAIT 状态的相同 src_port 连接不可复用。Linux 系统甚至更严格,只使用了 三元组…)。故不建议依赖syncookie。

net.ipv4.tcp_orphan_retries = 0

本端试图关闭TCP连接之前重试多少次。缺省值是7,相当于50秒~16分钟(取决于RTO)。如果你的机器是一个重载的WEB服务器,你应该考虑减低这个值,因为这样的套接字会消耗很多重要的资源。参见tcp_max_orphans。

net.ipv4.tcp_sack = 1

SACK(SelectiveAcknowledgment,选择性确认)技术,使TCP只重新发送交互过程中丢失的包,不用发送后续所有的包,而且提供相应机制使接收方能告诉发送方哪些数据丢失,哪些数据重发了,哪些数据已经提前收到了。如此大大提高了客户端与服务器端数据交互的效率。

net.ipv4.tcp_reordering = 3
net.ipv4.tcp_ecn = 2
net.ipv4.tcp_dsack = 1

允许TCP发送“两个完全相同”的SACK。

net.ipv4.tcp_mem = 178368  237824     356736

同样有3个值,意思为:
net.ipv4.tcp_mem[0]: 低于此值,TCP没有内存压力.
net.ipv4.tcp_mem[1]: 在此值下,进入内存压力阶段.
net.ipv4.tcp_mem[2]: 高于此值,TCP拒绝分配socket.

net.ipv4.tcp_wmem = 4096   16384       4194304

TCP写buffer,可参考的优化值: 8192436600 873200

net.ipv4.tcp_rmem = 4096     87380       4194304

TCP读buffer,可参考的优化值:32768  436600  873200

net.ipv4.tcp_app_win = 31
net.ipv4.tcp_adv_win_scale = 2
net.ipv4.tcp_tw_reuse = 1

表示开启重用。允许将TIME-WAITsockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_frto = 2

开启F-RTO,一个针对TCP重传超时(RTOs)的增强的恢复算法。在无线环境下特别有益处,因为在这种环境下分组丢失典型地是因为随机无线电干扰而不是中间路由器组塞。参考RFC 4318了解更多的细节。这个文件拥有下列值之一:
0 禁用。
1 开启基本版本的F-RTO算法。
2 如果流使用SACK的话,开启SACK-增强的F-TRO算法。不过当使用SACK时是基本版本也是可以使用的,因为有这种场景存在,F-RTO和开启SACK的TCP流分组计数合作不好。

net.ipv4.tcp_frto_response = 0

当F-RTO侦测到TCP超时是伪的时(例如,通过设置了更长的超时值避免了超时),TCP有几个选项决定接下来如何去做。可能的值是:
1:基于速率减半;平滑保守的响应,导致一个RTT之后拥塞窗口(cwnd)和慢启动阀值(ssthresh)减半。
2:非常保守的响应;不推荐这样做,因为即时有效,它和TCP的其他部分交互不好;立即减半拥塞窗口(cwnd)和慢启动阀值(ssthresh)。
3:侵占性的响应;废弃现在已知不必要的拥塞控制措施(或略一个将引起TCP更加谨慎保守的丢失的重传);cwnd and ssthresh恢复到超时之前的值。

net.ipv4.tcp_slow_start_after_idle = 1

表示拥塞窗口在经过一段空闲时间后仍然有效而不必重新初始化。

net.ipv4.tcp_low_latency = 0

允许 TCP/IP 协议栈适应在高吞吐量情况下低延时的情况;这个选项应该禁用。

net.ipv4.tcp_no_metrics_save = 0

一个tcp连接关闭后,把这个连接曾经有的参数比如慢启动门限snd_sthresh,拥塞窗口snd_cwnd 还有srtt等信息保存到dst_entry中, 只要dst_entry 没有失效,下次新建立相同连接的时候就可以使用保存的参数来初始化这个连接.tcp_no_metrics_save 设置为1就是不保持这些参数(经验值),每次建立连接后都重新摸索一次. 个人觉得没什么好处. 所以系统默认把它设为0。

net.ipv4.tcp_moderate_rcvbuf = 1

打开了TCP内存自动调整功能(1为打开、0为禁止)

net.ipv4.tcp_tso_win_divisor = 3

单个TSO段可消耗拥塞窗口的比例,默认值为3。

net.ipv4.tcp_congestion_control = cubic
net.ipv4.tcp_available_congestion_control = cubic reno
net.ipv4.tcp_allowed_congestion_control = cubic reno

丢包使得TCP传输速度大幅下降的主要原因是丢包重传机制,控制这一机制的就是TCP拥塞控制算法。congestion(拥塞)

Linux内核中提供了若干套TCP拥塞控制算法,已加载进内核的可以通过内核参数net.ipv4.tcp_available_congestion_control看到:
没有加载进内核的一般是编译成了模块,可以用modprobe加载,这些算法各自适用于不同的环境。
1)、reno是最基本的拥塞控制算法,也是TCP协议的实验原型。
2)、bic适用于rtt较高但丢包极为罕见的情况,比如北美和欧洲之间的线路,这是2.6.8到2.6.18之间的Linux内核的默认算法。
3)、cubic是修改版的bic,适用环境比bic广泛一点,它是2.6.19之后的linux内核的默认算法。
4)、hybla适用于高延时、高丢包率的网络,比如卫星链路。

载入tcp_hybl模块 modprobe tcp_hybla

TCP拥塞控制算法对TCP传输速率的影响可很大。

net.ipv4.tcp_abc = 0
net.ipv4.tcp_mtu_probing = 0
net.ipv4.tcp_fastopen

GoogleTFO特性,kernel 3.6以上版本支持,具体实现方法参考本文档 Google TFO特性。

net.ipv4.tcp_base_mss = 512

分组层路径MTU发现(MTU探测)中使用的search_low的初始值。如果允许MTU探测,这个初始值就是连接使用的初始MSS值。

net.ipv4.route.min_adv_mss = 256

该文件表示最小的MSS(MaximumSegment Size)大小,取决于第一跳的路由器MTU。

net.ipv4.tcp_workaround_signed_windows = 0
net.ipv4.tcp_dma_copybreak= 4096

下限.以字节为单位.socket 的大小将卸载到一个 dma 复制引擎.如果存在一个在系统和内核配置为使用 config_net_dma 选项。

net.ipv4.tcp_max_ssthresh= 0

慢启动阶段,就是当前拥塞窗口值比慢启动阈值(snd_ssthresh)小的时候,所处的阶段就叫做慢启动阶段。

当我们收到一个新的ACK时,则会调用tcp_slow_start()这个函数,并且为拥塞窗口增加1.(Linux中拥塞窗口的值代表数据包的个数,而不是实际的发送字节数目。实际可以发送的字节数等于可以发送的数据包个数*MSS。)直到慢启动阶段出现数据包的丢失。

而引入了tcp_max_ssthresh 这个参数后,则可以控制在慢启动阶段拥塞窗口增加的频度。默认这个参数不打开,如果这个参数的值设置为1000,则当拥塞窗口值大于1000时,则没收到一个ACK,并不再增加拥塞窗口一个单位了,而是约收到2个ACK才增加一个窗口单位。收到2ACK并不是决定值!!需要根据当前的拥塞窗口值,tcp_max_ssthresh值进行判断。

net.ipv4.tcp_thin_linear_timeouts= 0

这个函数RTO超时的处理函数。如果是thin流,则不要新设RTO是原先的2倍。

net.ipv4.tcp_thin_dupack= 0

与tcp_thin_linear_timeouts同为快速重传算法参数

net.core.netdev_max_backlog=300

进入包的最大设备队列.默认是300,对重负载服务器而言该值太低,可调整到1000。

ip link set eth0mtu 1500

设置网卡mtu大小。

 
IP 相关部份

net.ipv4.ip_local_port_range = 1024 65000

表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。

net.ipv4.ip_conntrack_max = 655360

在内核内存中netfilter可以同时处理的“任务”(连接跟踪条目)another

# 避免放大攻击
net.ipv4.icmp_echo_ignore_broadcasts = 1

# 开启恶意icmp错误消息保护
net.ipv4.icmp_ignore_bogus_error_responses = 1

# 开启SYN洪水攻击保护
net.ipv4.tcp_syncookies = 1

# 开启并记录欺骗,源路由和重定向包
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

# 处理无源路由的包
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# 开启反向路径过滤
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# 确保无人能修改路由表
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0

# 不充当路由转发
net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# 开启execshild
kernel.exec-shield = 1
kernel.randomize_va_space = 1


网络相关部份(/sys)

sys/class/net/eth0/statistics.rx_packets:
收到的数据包数据

sys/class/net/eth0/statistics.tx_packets:
传输的数据包数量

sys/class/net/eth0/statistics.rx_bytes:
接收的字节数

sys/class/net/eth0/statistics.tx_bytes:
传输的字节数

sys/class/net/eth0/statistics.rx_dropped:
收包时丢弃的数据包

sys/class/net/eth0/statistics.tx_dropped:
发包时丢弃的数据包


TCP 协议简略图示


1、TCP 数据包的大小

以太网数据包packet的大小是固定的,最初是1518字节,后来增加到1522字节。其中, 1500 字节是负载payload,22字节是头信息head。
IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要20字节,所以 IP 数据包的负载最多为1480字节。


图片说明:IP 数据包在以太网数据包里面,TCP 数据包在 IP 数据包里面。
TCP 数据包在 IP 数据包的负载里面。它的头信息最少也需要20字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1460 字节。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为1400字节左右。

因此,一条1500字节的信息需要两个 TCP 数据包。HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度。


图片说明:以太网数据包的负载是1500字节,TCP 数据包的负载在1400字节左右。


2、TCP 数据包的编号SEQ

一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。
发送的时候,TCP 协议为每个包编号sequence number,简称 SEQ,以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。
第一个包的编号是一个随机数。为了便于理解,这里就把它称为1号包。假定这个包的负载长度是100字节,那么可以推算出下一个包的编号应该是101。这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。


图片说明:当前包的编号是45943,下一个数据包的编号是46183,由此可知,这个包的负载是240字节。


3、TCP 数据包的组装

收到 TCP 数据包以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 数据包。

对于应用程序来说,不用关心数据通信的细节。除非线路异常,收到的总是完整的数据。应用程序需要的数据放在 TCP 数据包里面,有自己的格式比如 HTTP 协议。

TCP 并没有提供任何机制,表示原始文件的大小,这由应用层的协议来规定。比如,HTTP 协议就有一个头信息Content-Length,表示信息体的大小。对于操作系统来说,就是持续地接收 TCP 数据包,将它们按照顺序组装好,一个包都不少。

操作系统不会去处理 TCP 数据包里面的数据。一旦组装好 TCP 数据包,就把它们转交给应用程序。TCP 数据包里面有一个端口port参数,就是用来指定转交给监听该端口的应用程序。


图片说明:系统根据 TCP 数据包里面的端口,将组装好的数据转交给相应的应用程序。上图中,21端口是 FTP 服务器,25端口是 SMTP 服务,80端口是 Web 服务器。
应用程序收到组装好的原始数据,以浏览器为例,就会根据 HTTP 协议的Content-Length字段正确读出一段段的数据。这也意味着,一次 TCP 通信可以包括多个 HTTP 通信。


4、慢启动和 ACK

服务器发送数据包,当然越快越好,最好一次性全发出去。但是,发得太快,就有可能丢包。带宽小、路由器过热、缓存溢出等许多因素都会导致丢包。线路不好的话,发得越快,丢得越多。

最理想的状态是,在线路允许的情况下,达到最高速率。但是我们怎么知道,对方线路的理想速率是多少呢?答案就是慢慢试。
TCP 协议为了做到效率与可靠性的统一,设计了一个慢启动slow start机制。开始的时候,发送得较慢,然后根据丢包的情况,调整速率:如果不丢包,就加快发送速度;如果丢包,就降低发送速度。

Linux 内核里面设定了常量TCP_INIT_CWND,刚开始通信的时候,发送方一次性发送10个数据包,即"发送窗口"的大小为10。然后停下来,等待接收方的确认,再继续发送。

默认情况下,接收方每收到两个 TCP 数据包,就要发送一个确认消息。"确认"的英语是 acknowledgement,所以这个确认消息就简称 ACK。ACK 携带两个信息:
期待要收到下一个数据包的编号
接收方的接收窗口的剩余容量


发送方有了这两个信息,再加上自己已经发出的数据包的最新编号,就会推测出接收方大概的接收速度,从而降低或增加发送速率。这被称为"发送窗口",这个窗口的大小是可变的。


图片说明:每个 ACK 都带有下一个数据包的编号,以及接收窗口的剩余容量。双方都会发送 ACK。

注意,由于 TCP 通信是双向的,所以双方都需要发送 ACK。两方的窗口大小,很可能是不一样的。而且 ACK 只是很简单的几个字段,通常与数据合并在一个数据包里面发送。


图片说明:上图一共4次通信。第一次通信,A 主机发给B 主机的数据包编号是1,长度是100字节,因此第二次通信 B 主机的 ACK 编号是 1 + 100 = 101,第三次通信 A 主机的数据包编号也是 101。同理,第二次通信 B 主机发给 A 主机的数据包编号是1,长度是200字节,因此第三次通信 A 主机的 ACK 是201,第四次通信 B 主机的数据包编号也是201。

即使对于带宽很大、线路很好的连接,TCP 也总是从10个数据包开始慢慢试,过了一段时间以后,才达到最高的传输速率。这就是 TCP 的慢启动。


5、数据包的遗失处理

TCP 协议可以保证数据通信的完整性,这是怎么做到的?

前面说过,每一个数据包都带有下一个数据包的编号。如果下一个数据包没有收到,那么 ACK 的编号就不会发生变化。举例来说,现在收到了4号包,但是没有收到5号包。ACK 就会记录,期待收到5号包。过了一段时间,5号包收到了,那么下一轮 ACK 会更新编号。如果5号包还是没收到,但是收到了6号包或7号包,那么 ACK 里面的编号不会变化,总是显示5号包。这会导致大量重复内容的 ACK。

如果发送方发现收到三个连续的重复 ACK,或者超时了还没有收到任何 ACK,就会确认丢包,即5号包遗失了,从而再次发送这个包。通过这种机制,TCP 保证了不会有数据包丢失。


图片说明:Host B 没有收到100号数据包,会连续发出相同的 ACK,触发 Host A 重发100号数据包。