在硬件资源有限的情况下,最大化服务器的性能,提高服务器的并发处理能力是很多技术人员思考的问题。除了优化Nginx/PHP-FPM/Mysql/Redis等服务软件的配置之外,还可以通过修改Linux内核相关的TCP参数来最大化服务器性能。在优化Linux内核的参数之前,我们需要了解一下TCP/IP协议,这是我们进行优化的理论基础。TCP/IP协议TCP/IP协议是一个非常复杂的协议,完全掌握它并不容易,但是作为基础知识,我们必须知道TCP的三次握手和四次握手的逻辑过程/IP协议。所谓三次握手,就是在建立一个TCP连接时,客户端和服务器一共需要发送三个数据包来确认连接的建立。在socket编程中,这个过程是由客户端执行connect触发的。Three-wayhandshakeflowchart:三次握手过程第一次握手:客户端设置标志位SYN为1,随机产生一个值seq=J,并向服务器发送数据包,客户端进入SYN_SENT状态,并等待服务器确认。第二次握手:服务器收到数据包后,标志位SYN=1,知道客户端请求建立连接。服务器将标志位SYN和ACK都设置为1,ack=J+1,并随机产生一个值seq=K,向客户端发送数据包确认连接请求,服务器进入SYN_RCVD状态。第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确,标志位ACK置1,ack=K+1,数据包发给服务器服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功。客户端和服务端进入ESTABLISHED状态,完成三次握手,然后客户端和服务端就可以开始传输数据了。挥手四次,挥手四次终止TCP连接,也就是说当一个TCP连接断开时,客户端和服务端一共需要发送4个数据包来确认连接断开。在套接字编程中,这个过程是由客户端或服务器执行关闭触发的。由于TCP连接是全双工的,每个方向都必须单独关闭。这个原理是当一方完成数据发送任务后,发送一个FIN终止这个方向的连接。收到一个FIN就意味着那么这个方向就没有数据流了,也就是不会再收到数据,但是这个TCP连接上仍然可以发送数据,直到这个方向也发送了FIN。先关闭的一方将执行主动关闭,而另一方将执行被动关闭。Flowchartofthefour-waywave:四路挥手流程中断连接的一端可以是客户端,也可以是服务端。第一波:client发送一个FIN=M,关闭client到server的数据传输,client进入FIN_WAIT_1状态。意思是“我的客户端没有数据要发送给你”,但是如果你的服务器还有数据没有发送,你不用急着关闭连接,可以继续发送数据。第二波:服务端收到FIN后,先发送ack=M+1,告诉客户端我已经收到你的请求了,但是我还没准备好,请继续等我的消息。此时客户端进入FIN_WAIT_2状态,继续等待服务器的FIN报文。第三次挥手:当服务端确定数据发送完毕后,向客户端发送FIN=N消息,告诉客户端,好了,我这边数据发送完毕,准备关闭连接。服务器进入LAST_ACK状态。第四次挥手:客户端收到FIN=N报文后,知道可以关闭连接,但还是不信任网络,怕服务器不知道关闭,所以发送ack=N+1并进入TIME_WAIT状态。如果服务器没有收到ACK,它可以重传。服务器收到ACK后,就知道可以断开连接了。如果客户端在等待2MSL后还没有收到回复,证明服务器已经正常关闭。好吧,我的客户端也可以关闭连接。最后完成一次四次握手。序列号和确认响应大家都知道TCP/IP协议是一种高可靠的通信协议。序列号和确认响应用于保证通信的高可靠性。有以下几个关键点:当发送端的数据到达接收主机时,接收主机会返回一个消息已经收到的通知。此消息称为确认(ACK)。发送方发送数据时,会等待对方的确认响应。如果有确认响应,则表示数据已成功到达对端。反之,数据丢失的可能性很大。如果在一定时间内没有确认响应,发送方可以认为数据已经丢失,重新发送。因此,即使发生丢包,仍然可以保证数据能够到达对端,实现可靠传输。未能收到确认并不意味着数据一定会丢失。也有可能是对方收到了数据,但是返回的确认回复在途中丢失了。这种情况也会导致发送方误认为数据没有到达目的地而重新发送数据。此外,可能还有其他一些原因导致确认响应延迟到达,在源主机重新发送数据后确认才到达的情况并不少见。此时,源主机只需要按照机制重新发送数据即可。目标主机不希望重复接收相同的数据。为了向上层应用程序提供可靠的传递,目标主机必须丢弃重复的数据包。为此,我们引入了序列号。序列号是按顺序为传输数据的每个字节(8位字节)分配一个编号的数字。接收端查询接收到的数据的TCP头中的序号和数据的长度,并返回下一步应该接收的序号作为确认响应。通过序号和确认响应号,TCP可以识别数据是否已经接收,也可以判断是否需要接收,从而实现可靠传输。重传超时是指在重传数据之前等待确认的特定时间间隔。如果在此时间内未收到确认响应,发送方将重新发送数据。最理想的是找到一个最小时间,可以保证“必须在这个时间内返回确认响应”。TCP要求无论网络环境如何都具有高性能通信,并且无论网络拥塞如何变化都必须保持这一特性。为此,它会在每次发送数据包时计算往返时间及其偏移量。加上往返时间和偏离时间,重传超时时间是一个比和略大的值。数据重发后,如果仍未收到确认响应,则重新发送。此时等待确认响应的时间会以2倍和4倍的指数函数延长。此外,数据不会无限重复发送。重传达到一定次数后,如果仍然没有返回确认响应,则判断网络或对端主机出现异常,强行关闭连接。并通知应用程序异常强制终止通信。了解了TCP/IP协议后,我们会发现几个问题:在三次握手中,如果客户端发起第一次握手后中断或不响应服务器发回的ACK=1包,那么服务器将不断重试发送数据包,直到超时。没错,这就是SYNFLOOD攻击的原理。4次挥手中,主动关闭连接的客户端处于TIME_WAIT状态,持续时间为2MSL。MSL是最大段生存期(maximumsegmentlifetime),是可以在Internet上存活的IP包。最长时间,超过这个时间就会消失在网络中(TIME_WAIT状态一般维持在1-4分钟)。2MSL时间长度用于保证旧的连接状态不会影响新的连接。处于TIME_WAIT状态的连接占用的资源是不会被内核释放的,所以作为服务器尽量不要主动断开连接,减少TIME_WAIT状态造成的资源浪费。如果我们的服务器是负载均衡服务器,上游服务器长时间没有作用,负载均衡服务器会主动关闭连接,在高并发场景下会导致TIME_WAIT状态的累积。在四波中,如果客户端收到FIN报文后没有返回ACK,服务器端也会继续尝试发送FIN报文,这样服务器端就会累积CLOSE_WAIT状态。SYNFlood攻击SynFlood攻击是目前网络上最常见的DDoS攻击,也是最经典的拒绝服务攻击。如果存在文件,则可能导致目标服务器中的半开连接队列已满,从而阻止其他合法用户访问。SynFlood攻击原理攻击者首先伪造地址向服务器发起SYN请求(CanIestablishaconnection?),服务器回应ACK+SYN(Yes+Pleaseconfirm)。而真正的IP会认为我没有发送请求,不会响应。如果服务器没有收到响应,它会重试3-5次并等待一个SYN时间(通常是30秒-2分钟),然后丢弃连接。如果攻击者发送大量这种伪造源地址的SYN请求,服务器会消耗大量资源来处理这种半连接。重试SYN+ACK的IP。TCP是一种可靠的协议。这时,消息将被重传。默认重试次数为5次。重试间隔从1s开始,每次加倍。它们分别是1s+2s+4s+8s+16s=31s。第五次发出后,过了32s才知道第五次也超时了,所以总共是31+32=63s。一个fakesyn报文会占用TCP准备队列63s,半连接队列默认为1024,在没有任何保护的情况下,每秒发送20个fakesyn包就足以把半连接队列打爆,从而使真正的连接无法建立,无法响应正常请求。最终结果是服务器无暇理会正常的连接请求——拒绝服务。内核TCP参数优化编辑文件/etc/sysctl.conf,添加如下内容:net.ipv4.tcp_fin_timeout=2net.ipv4.tcp_tw_reuse=1net.ipv4.tcp_tw_recycle=1net.ipv4.tcp_syncookies=1net.ipv4.tcp_keepalive_time=600net。ipv4.ip_local_port_range=400065000net.ipv4.tcp_max_syn_backlog=16384net.ipv4.tcp_max_tw_buckets=36000net.ipv4.route.gc_timeout=100net.ipv4.tcp_syn_retries=1net.ipv4.tcp_synack_retriesvnet.ipv4.tcp_synack_retriesvnet.core.so8conmax=1net.core.so8conmax16384net.ipv4.tcp_max_orphans=16384然后执行sysctl-p使参数生效。函数说明:net.ipv4.tcp_fin_timeout表示socket被本端关闭。该参数决定了它保持在FIN-WAIT-2状态的时间。默认值为60秒。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_fin_timeout60net.ipv4.tcp_tw_reuse表示启用复用。允许TIME-WAIT套接字被重新用于新的TCP连接,默认值为0,表示关闭。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_tw_reuse0net.ipv4.tcp_tw_recycle表示开启TCP连接中TIME-WAITsockets的快速回收。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_tw_recycle,默认为0,表示关闭。提示:设置参数reuse和recycle是为了防止生产环境中Web、Squid等业务服务器的time_wait网络状态过多。net.ipv4.tcp_syncookies表示开启SYNCookies功能。当SYN等待队列溢出时,启用Cookies进行处理,可以防止少量的SYN攻击。这个参数也可以省略。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_syncookies,默认值为1net.ipv4.tcp_keepalive_time表示启用keepalive时TCP发送keepalive报文的频率。默认为2小时,建议改为10分钟。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_keepalive_time,默认为7200秒。net.ipv4.ip_local_port_range该选项用于设置系统允许开放的端口范围,即对外连接的端口范围。该参数对应的系统路径为:/proc/sys/net/ipv4/ip_local_port_range3276861000net.ipv4.tcp_max_syn_backlog表示SYN队列的长度,默认为1024,建议将队列长度增加到8192或更多,使更多等待连接的网络连接数。服务器使用此参数记录未收到客户端确认的最大连接请求数。参数对象系统路径为:/proc/sys/net/ipv4/tcp_max_syn_backlognet.ipv4.tcp_max_tw_buckets表示系统同时维持TIME_WAITsockets的最大数量。如果超过这个值,TIME_WAIT套接字将被立即清除并打印警告信息。默认值为180000,对于Apache、Nginx等服务器,可以适当降低一点,比如5000~30000。对于不与业务通信的服务器也可以大一些,比如LVS、Squid。这个参数可以控制TIME_WAITsockets的最大数量,防止squid服务器被大量的TIME_WAITsockets拖死。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_max_tw_bucketsnet.ipv4.tcp_synack_retries该参数的值决定了内核放弃连接之前发送的SYN+ACK包的数量。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_synack_retries,默认值为5。net.ipv4.tcp_syn_retries表示内核放弃建立连接前发送的SYN包数。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_syn_retries5net.ipv4.tcp_max_orphans用于设置系统中不与任何用户文件句柄相关联的TCP套接字的最大数量。如果超过这个值,孤立的连接将被立即重置,并打印一条警告消息。此限制只是为了防止简单的DoS攻击。不要过分依赖这个限制,甚至不要考虑降低这个值,通常不增加这个值。该参数对应的系统路径为:/proc/sys/net/ipv4/tcp_max_orphans65536net.core.somaxconn该选项的默认值为128,该参数用于调整系统发起的TCP连接数同一时间。在高并发请求中,默认值可能会导致连接超时或重传,所以需要结合并发请求数调整该值。该参数对应的系统路径为:/proc/sys/net/core/somaxconn128net.core.netdev_max_backlog表示当每个网络接口接收数据包的速率快于内核处理这些数据包的速率时,允许发送到队列的数据最大包数。该参数对应的系统路径为:/proc/sys/net/core/netdev_max_backlog,默认值为1000
