随着企业网站访问量越来越大,服务器的压力也逐渐增加,主要体现在CPU使用率、内存、硬盘、网卡流量等方面资源占用情况很高。此时需对服务器性能进行调优,尽量在保持服务器的现有数量,然后对其各个环节参数进行优化。
本章向读者介绍Linux企业级性能服务器优化、TCP/IP报文、TCP三次握手及四次断开、Linux内核深入优化、Linux内核故障解决方案及对Linux性能进行评估等。
一、TCP/IP报文详解
TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地,而IP是给因特网的每台联网设备规定一个地址。TCP/IP 协议数据封装的过程包括:用户数据经过应用层协议封装后传递给传输层,传输层封装TCP头部,交给网络层,网络层封装IP头部后,再交给数据链路层,数据链路层封装Ethernet帧头和帧尾,交给物理层,物理层以比特流的形式将数据发送到物理线路上。
一般而言,不同的协议层对数据包有不同的称谓,数据包在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理,如图所示:
优化Linux服务器,需要了解TCP协议相关信息,例如TCP/IP数据报文的内容及如何传输的,如图所示为IP数据包报文详细结构图:
IP数据包详解如下:
q Source Port和Destination Port:分别占用16位,表示源端口号和目的端口号;用于区别主机中的不同进程,而IP地址是用来区分不同的主机的,源端口号和目的端口号配合上IP首部中的源IP地址和目的IP地址就能唯一的确定一个TCP连接; q Sequence Number:用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节在数据流中的序号;主要用来解决网络报乱序的问题; q Acknowledgment Number:32位确认序列号包含发送确认的一端所期望收到的下一个序号,因此,确认序号应当是上次已成功收到数据字节序号加1。不过,只有当标志位中的ACK标志(下面介绍)为1时该确认序列号的字段才有效。主要用来解决不丢包的问题; q Offset:给出首部中32 bit字的数目,需要这个值是因为任选字段的长度是可变的。这个字段占4bit(最多能表示15个32bit的的字,即4*15=60个字节的首部长度),因此TCP最多有60字节的首部。然而,没有任选字段,正常的长度是20字节; q TCP Flags:TCP首部中有6个标志比特,它们中的多个可同时被设置为1,主要是用于操控TCP的状态机的,依次为URG,ACK,PSH,RST,SYN,FIN。每个标志位的意思如下: ① URG:此标志表示TCP包的紧急指针域(后面马上就要说到)有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据; ② ACK:此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;有两个取值:0和1,为1的时候表示应答域有效,反之为0; ③ PSH:这个标志位表示Push操作。所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队; ④ RST:这个标志表示连接复位请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包; ⑤ SYN:表示同步序号,用来建立连接。SYN标志位和ACK标志位搭配使用,当连接请求的时候,SYN=1,ACK=0;连接被响应的时候,SYN=1,ACK=1;这个标志的数据包经常被用来进行端口扫描。扫描者发送一个只有SYN的数据包,如果对方主机响应了一个数据包回来,就表明这台主机存在这个端口;但是由于这种扫描方式只是进行TCP三次握手的第一次握手,因此这种扫描的成功表示被扫描的机器不很安全,一台安全的主机将会强制要求一个连接严格的进行TCP的三次握手; ⑥ FIN:表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。这个标志的数据包也经常被用于进行端口扫描。 q Window:窗口大小,也就是有名的滑动窗口,用来进行流量控制; |
二、TCP三次握手及四次断开
TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换TCP窗口大小信息。如图所示:
(1)TCP三次握手原理:
第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后客户端进入SYN_SENT状态,等待服务器的确认; 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态; 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
如图所示为基于tcpdump抓取TCP/IP三次握手及数据包传输过程:
(2)TCP四次挥手原理
q 第一次挥手:主机A(可以使客户端,可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机B发送一个FIN报文段;此时,主机A进入FIN_WAIT_1状态;这表示主机A没有数据要发送给主机B; q 第二次挥手:主机B收到了主机A发送的FIN报文段,向主机A回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机A进入FIN_WAIT_2状态;主机B告诉主机A,我“同意”你的关闭请求; q 第三次挥手:主机B向主机A发送FIN报文段,请求关闭连接,同时主机B进入LAST_ACK状态; q 第四次挥手:主机A收到主机B发送的FIN报文段,向主机B发送ACK报文段,然后主机A进入TIME_WAIT状态;主机B收到主机A的ACK报文段以后,就关闭连接;此时,主机A等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机A也可以关闭连接。 |
如图所示为基于tcpdump抓取TCP/IP四次挥手及数据包传输过程 :
三、优化Linux文件打开最大数
为了防止失控的进程破坏系统的性能,Unix和Linux会跟踪进程使用的大部分资源,并允许用户和系统管理员使用对进程的资源限制,例如控制某个进程打开的系统文件数、对某个用户打开系统进程数进行限制等,一般限制手段包括:软限制和硬限制。
-
软限制(soft limit)是内核实际执行的限制,任何进程都可以将软限制设置为任意小于等于对进程限制的硬限制的值,(noproc)最大线程数和(nofile)文件数;
-
硬限制(hard limit)是可以在任何时候任何进程中设置,但硬限制只能由超级用户修改。
Linux系统一切皆文件,对Linux进行各种操作,其实是对文件进行操作,文件可分为:普通文件、目录文件、链接文件和设备文件。而文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其值一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。
Linux系统默认已经打开的文件描述符包括:STDIN_FILENO 0表示标准输入、STDOUT_FILENO 1表示标准输出、STDERR_FILENO 2表示标准错误输出,默认打开一个新文件,它的文件描述符为3。
每个文件描述符与一个打开文件相对应,不同的文件描述符可以指向同一个文件。相同的文件可以被不同的进程打开,也可以在同一个进程中被多次打开。
Linux系统为每个进程维护了一个文件描述符表,该表的值都从0开始的,在不同的进程中你会看到相同的文件描述符,相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。Linux内核对文件操作,维护了3个数据结构概念如下:
-
进程级的文件描述符表;
-
系统级的打开文件描述符表;
-
文件系统的i-node表;
其中进程级的描述符表的每一个条目记录了单个文件描述符的相关信息,例如控制文件描述符操作的一组标志及对打开文件句柄的引用。Linux内核对所有打开的文件都维护了一个系统级的描述符表(open file description table)。将描述符表中的记录行称为打开文件句柄(open file handle),一个打开文件句柄存储了与一个打开文件相关的全部信息,详细信息如下:
-
当前文件偏移量;
-
打开文件时所使用的状态标识;
-
文件访问模式;
-
与信号驱动相关的设置;
-
对该文件i-node对象的引用;
-
文件类型和访问权限;
-
指针,指向该文件所持有的锁列表;
-
文件的各种属性。
默认Linux内核对每个用户设置了打开文件最大数为1024,对于高并发网站,是远远不够的,需要将默认值调整到更大,调整方法有两种:
Linux每个用户打开文件最大数临时设置方法,重启服务器该参数无效,命令行终端执行如下命令:
ulimit -n 65535
Linux每个用户打开文件最大数永久设置方法,将如下代码加入内核限制文件/etc/security/limits.conf的末尾:
* soft noproc 65535 * hard noproc 65535 * soft nofile 65535 * hard nofile 65535
如上设置为对每个用户分别设置nofile、noproc最大数,如果需要对Linux整个系统设置文件最大数限制,需要修改/proc/sys/fs/file-max中的值,该值为Linux总文件打开数,例如设置为:echo 3865161233 >/proc/sys/fs/file-max。
四、 内核参数的优化
Linux /proc/sys目录下存放着多数内核的参数,并且可以在系统运行时进行更改,一般重新启动机器就会失效。而/etc/sysctl.conf是一个允许改变正在运行中的Linux系统的接口,它包含一些TCP/IP堆栈和虚拟内存系统的高级选项,修改内核参数永久生效。
/proc/sys下内核文件与配置文件sysctl.conf中变量存在着对应关系,即修改sysct.conf配置文件,其实是修改/proc/sys相关参数,所以对Linux内核优化只需修改/etc/sysctl.conf文件即可。如下为BAT企业生产环境/etc/sysct.conf内核完整参数:
net.ipv4.ip_forward = 0 net.ipv4.conf.default.rp_filter = 1 net.ipv4.conf.default.accept_source_route = 0 kernel.sysrq = 0 kernel.core_uses_pid = 1 net.ipv4.tcp_syncookies = 1 kernel.msgmnb = 65536 kernel.msgmax = 65536 kernel.shmmax = 68719476736 kernel.shmall = 4294967296 net.ipv4.tcp_max_tw_buckets = 10000 net.ipv4.tcp_sack = 1 net.ipv4.tcp_window_scaling = 1 net.ipv4.tcp_rmem = 4096 87380 4194304 net.ipv4.tcp_wmem = 4096 16384 4194304 net.core.wmem_default = 8388608 net.core.rmem_default = 8388608 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.core.netdev_max_backlog = 262144 net.core.somaxconn = 262144 net.ipv4.tcp_max_orphans = 3276800 net.ipv4.tcp_max_syn_backlog = 262144 net.ipv4.tcp_timestamps = 0 net.ipv4.tcp_synack_retries = 1 net.ipv4.tcp_syn_retries = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_mem = 94500000 915000000 927000000 net.ipv4.tcp_fin_timeout = 1 net.ipv4.tcp_keepalive_time = 30 net.ipv4.ip_local_port_range = 1024 65535
Linux内核常见参数详解:
net.ipv4.tcp_timestamps = 1 该参数控制RFC 1323 时间戳与窗口缩放选项; net.ipv4.tcp_sack = 1 选择性应答(SACK)是 TCP 的一项可选特性,可以提高某些网络中所有可用带宽的使用效率; net.ipv4.tcp_fack = 1 打开FACK(Forward ACK) 拥塞避免和快速重传功能; net.ipv4.tcp_retrans_collapse = 1 打开重传重组包功能,为0的时候关闭重传重组包功能; net.ipv4.tcp_syn_retries = 5 对于一个新建连接,内核要发送多少个SYN 连接请求才决定放弃; net.ipv4.tcp_synack_retries = 5 tcp_synack_retries显示或设定Linux在回应SYN要求时尝试多少次重新发送初始SYN,ACK封包后才决定放弃; net.ipv4.tcp_max_orphans = 131072 系统所能处理不属于任何进程的TCP sockets最大数量; net.ipv4.tcp_max_tw_buckets = 5000 系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息; 默认为180000,设为较小数值此项参数可以控制TIME_WAIT套接字的最大数量,避免服务器被大量的TIME_WAIT套接字拖死; net.ipv4.tcp_keepalive_time = 30 net.ipv4.tcp_keepalive_probes = 3 net.ipv4.tcp_keepalive_intvl = 3 如果某个TCP连接在空闲30秒后,内核才发起probe(探查); 如果probe 3次(每次3秒既tcp_keepalive_intvl值)不成功,内核才彻底放弃,认为该连接已失效; net.ipv4.tcp_retries1 = 3 放弃回应一个TCP 连接请求前﹐需要进行多少次重试; net.ipv4.tcp_retries2 = 15 在丢弃激活(已建立通讯状况)的TCP连接之前﹐需要进行多少次重试; net.ipv4.tcp_fin_timeout = 30 表示如果套接字由本端要求关闭,这个参数决定了它保持在 FIN-WAIT-2状态的时间; net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭; net.ipv4.tcp_max_syn_backlog = 8192 表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数; net.ipv4.tcp_syncookies = 1 TCP建立连接的 3 次握手过程中,当服务端收到最初的 SYN 请求时,会检查应用程序的syn_backlog队列是否已满,启用syncookie,可以解决超高并发时的Can’t Connect` 问题。但是会导致 TIME_WAIT 状态fallback为保持2MSL时间,高峰期时会导致客户端无可复用连接而无法连接服务器; net.ipv4.tcp_orphan_retries = 0 关闭TCP连接之前重试多少次; net.ipv4.tcp_mem = 178368 237824 356736 net.ipv4.tcp_mem[0]: 低于此值,TCP没有内存压力; net.ipv4.tcp_mem[1]: 在此值下,进入内存压力阶段; net.ipv4.tcp_mem[2]: 高于此值,TCP拒绝分配socket; net.ipv4.tcp_tw_reuse = 1 表示开启重用,允许将TIME-WAIT sockets重新用于新的TCP连接; net.ipv4.ip_local_port_range = 1024 65000 表示用于向外连接的端口范围; net.ipv4.ip_conntrack_max = 655360 在内核内存中netfilter可以同时处理的“任务”(连接跟踪条目); net.ipv4.icmp_ignore_bogus_error_responses = 1 开启恶意icmp错误消息保护; net.ipv4.tcp_syncookies = 1 开启SYN洪水攻击保护。
五、内核报错剖析
企业生产环境Linux服务器正常运行,由于某种原因会导致内核报错或者抛出很多信息,根据系统SA可以快速定位Linux服务器故障,Linux内核日志一般存在messages日志中,可以通过命令tail -fn 100 /var/log/messages查看Linux内核日志,如下为Linux内核常见报错日志及生产环境解决报错的方案。
1、Linux内核抛出net.ipv4.tcp_max_tw_buckets错误
Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow |
根据TCP协议定义的3次握手及四次断开连接规定,发起socket主动关闭的一方Socket将进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime)。
如果该值设置过小导致,当系统Time wait数量超过默认设置的值,即会抛出如上的警告信息,需要增加net.ipv4.tcp_max_tw_buckets的值,警告信息消除。
当然也不能设置过大,对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的Socket,甚至比处于Established状态下的Socket多的多,严重影响服务器的处理能力,甚至耗尽可用的Socket而停止服务,TIME_WAIT是TCP协议用以保证被重新分配的Socket不会受到之前残留的延迟重发报文影响的机制,是TCP传输必要的逻辑保证。
2、Linux内核抛出Too many open files错误:
Benchmarking localhost (be patient) socket: Too many open files (24) socket: Too many open files (24) socket: Too many open files (24) socket: Too many open files (24) socket: Too many open files (24) |
每个文件描述符与一个打开文件相对应,不同的文件描述符可以指向同一个文件。相同的文件可以被不同的进程打开,也可以在同一个进程中被多次打开。Linux内核对应每个用户打开的文件最大数一般为1024,需要将该值调高满足大并发网站的访问。
Linux每个用户打开文件最大数永久设置方法,将如下代码加入内核限制文件/etc/security/limits.conf的末尾,Exit退出终端,重新登录即生效:
* soft noproc 65535 * hard noproc 65535 * soft nofile 65535 * hard nofile 65535
3、Linux内核抛出possible SYN flooding on port 80. Sending cookies错误:
May 31 14:20:14 localhost kernel: possible SYN flooding on port 80. Sending cookies. May 31 14:21:28 localhost kernel: possible SYN flooding on port 80. Sending cookies. May 31 14:22:44 localhost kernel: possible SYN flooding on port 80. Sending cookies. May 31 14:25:33 localhost kernel: possible SYN flooding on port 80. Sending cookies. May 31 14:27:06 localhost kernel: possible SYN flooding on port 80. Sending cookies. May 31 14:28:44 localhost kernel: possible SYN flooding on port 80. Sending cookies. May 31 14:28:51 localhost kernel: possible SYN flooding on port 80. Sending cookies. May 31 14:31:01 localhost kernel: possible SYN flooding on port 80. Sending cookies. |
此问题是由于SYN 队列已满,而触发SYN cookies,一般是由于大量的访问,或者恶意访问导致,也称之为SYN Flooding洪水攻击,与DDOS攻击类似。
完整的TCP连接的三次握手,假设一个用户A向服务器发送了SYN报文后突然死机或掉线,那么服务器在发出SYN+ACK应答报文后是无法收到客户端的ACK报文的(第三次握手无法完成),这种情况下服务器端一般会重试(再次发送SYN+ACK给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为SYN Timeout,一般来说这个时间是分钟的数量级(大约为30秒-2分钟)。
一个用户出现异常导致服务器的一个线程等待1分钟并不是什么很大的问题,但如果有一个恶意的攻击者大量模拟这种情况,服务器端将为了维护一个非常大的半连接列表而消耗非常多的资源,数以万计的半连接,即使是简单的保存并遍历也会消耗非常多的CPU时间和内存,何况还要不断对这个列表中的IP进行SYN+ACK的重试。
实际上如果服务器的TCP/IP栈不够强大,最后的结果往往是堆栈溢出崩溃,即使服务器端的系统足够强大,服务器端也将忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求(毕竟客户端的正常请求比率非常之小),此时从正常客户的角度看来,服务器失去响应,服务器拒绝提供服务,服务器受到了DDOS攻击,这里攻击的手段为DDOS中SYN Flood攻击(SYN洪水攻击)。
防护DDOS攻击有两种手段,一是基于硬件专业防火墙、二是基于Linux内核简单防护,如果攻击流量特别大,单纯配置内核参数是无法抵挡的,还得依靠专业级硬件防火墙,如下为Linux内核防护DDOS优化参数,加入如下代码即可:
net.ipv4.tcp_fin_timeout = 30 net.ipv4.tcp_keepalive_time = 1200 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.ip_local_port_range = 1024 65000 net.ipv4.tcp_max_syn_backlog = 8192 net.ipv4.tcp_max_tw_buckets = 8000 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_syn_retries = 2
4、Linux内核抛出ip_conntrack: table full, dropping packet.错误:
May 6 11:15:07 localhost kernel: nf_conntrack:table full, dropping packet. May 6 11:19:13 localhost kernel: nf_conntrack:table full, dropping packet. May 6 11:20:34 localhost kernel: nf_conntrack:table full, dropping packet. May 6 11:23:12 localhost kernel: nf_conntrack:table full, dropping packet. May 6 11:24:07 localhost kernel: nf_conntrack:table full, dropping packet. May 6 11:24:13 localhost kernel: nf_conntrack:table full, dropping packet. May 6 11:25:11 localhost kernel: nf_conntrack:table full, dropping packet. May 6 11:26:25 localhost kernel: nf_conntrack:table full, dropping packet. |
由于该服务器开启了iptables防火墙,WEB服务器收到了大量的连接,iptables会把所有的连接都做链接跟踪处理,这样iptables就会有一个链接跟踪表,当这个表满的时候,就会出现上面的错误。ip_conntrack是linux NAT的一个跟踪连接条目的模块,ip_conntrack模块会使用一个哈希表记录 tcp 通讯协议的established connection记录。
如果是CentOS6.x系统,需执行:modprobe nf_conntrack命令,然后在内核优化文件中加入如下代码,sysctl –p使其内核文件生效,即可解决该报错:
net.nf_conntrack_max = 655360 net.netfilter.nf_conntrack_tcp_timeout_established = 36000
如果是CentOS5.x系统,需执行:modprobe ip_conntrack命令,然后在内核优化文件中加入如下代码,sysctl –p使其内核文件生效,即可解决该报错:
net.ipv4.ip_conntrack_max = 655350 net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 10800
Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow Sep 23 04:45:55 localhost kernel: TCP: time wait bucket table overflow |