admin管理员组

文章数量:1547514

一、背景

我们在系统运维过程中,尤其业务负载高或复杂的场景中,可能出现系统瓶颈影响业务运行的情况,甚至造成系统宕机等风险,这是我们必要情况需要对系统参数进行优化处理来缓解这种压力和风险,本文即对日常运维过程常见的维护经验总结汇总,以供后续工作参考。

二、优化处理

2.1、关于内核参数的优化

1)swap优化

如果服务器上有运行数据库服务或消息中间件服务,请关闭交换分区

echo "vm.swappiness = 0" >> /etc/sysctl.conf
sysctl -p
2)OOM 误Killer优化

什么是OOM? Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉。Linux中,每个程序申请的物理内存都是共享的;例如物理内存只有1g,启动2个程序各申请1g是可以的,linux通过这种过度分配的方式来达到内存的充分利用,当程序实际使用内存超出物理内存时,会被系统按照优先级,杀掉一部分程序以确保其它程序的正常运行;为了避免核心服务触发(OOM)机制而被杀死,可以将进程文件设置为最高优先级(参考参看)。

echo -17 > /proc/$pid/oom_score_adj #数值越小越不容易被杀,-17表临时关闭linux内核的OOM机制,程序永远不被kill
echo -17 > /proc/$PID/oom_adj #默认值为0,新版linux已经使用oom_score_adj来代替旧版的oom_score

A much more powerful interface, /proc/<pid>/oom_score_adj, was
introduced with the oom killer rewrite that allows users to increase or
decrease the badness() score linearly.  This interface will replace
/proc/<pid>/oom_adj.

sysctl -w vm.panic_on_oom=1 #彻底关闭;当panic_on_oom为1时,直接panic;当panic_on_oom为0时内核将通过oom killer杀掉部分进程(默认是为0的)。
或在sysctl.conf后追加vm.panic_on_oom = 1 //1表示关闭,默认为0表示开启OOM

sysctl -p
或:
echo "vm.panic_on_oom=1" >> /etc/sysctl.conf
sysctl -w kernel.panic=10
echo "kernel.panic=10" >> /etc/sysctl.conf  //kernel panic 10秒后自动重启系统

sysctl -w vm.overcommit_memory=2   // 即不超分内存vm.overcommit_memory 表示内核在分配内存时候做检查的方式。这个变量可以取到0,1,2三个值。
echo "vm.overcommit_memory=2" >> /etc/sysctl.conf //不超分配内存就不会返回错误,永远也不能达到内存被耗尽状态,OOM也就不会有影响了。

```bash
	0:当用户空间请求更多的的内存时,内核尝试估算出剩余可用的内存。此时宏为 OVERCOMMIT_GUESS,内核计算:NR_FILE_PAGES 总量+SWAP总量+slab中可以释放的内存总量,如果申请空间超过此数值,则将此数值与空闲内存总量减掉 totalreserve_pages(?) 的总量相加。如果申请空间依然超过此数值,则分配失败。
    1:当这个参数值为1时,宏为 OVERCOMMIT_ALWAYS,内核允许超量使用内存直到用完为止,主要用于科学计算。
    2:当这个参数值为2时,此时宏为 OVERCOMMIT_NEVER,内核会使用一个决不过量使用内存的算法,即系统整个内存地址空间不能超过swap+50%的RAM值,50%参数的设定是在overcommit_ratio中设定,内核计算:内存总量×vm.overcommit_ratio/100+SWAP 的总量,如果申请空间超过此数值,则分配失败。vm.overcommit_ratio 的默认值为50。
   以上为粗略描述,在实际计算时,如果非root进程,则在计算时候会保留3%的空间,而root进程则没有该限制。

典型案例: 一台机器突然ssh远程登录不了,但能ping通,说明不是网络的故障,原因是sshd进程被OOM killer杀掉了(多次遇到这样的假死状况)。重启机器后查看系统日志/var/log/messages会发现Out of Memory: Kill process 1865(sshd)类似的错误信息。

为防止重要的系统进程触发(OOM)机制而被杀死,内核会通过特定的算法给每个进程计算一个分数来决定杀哪个进程,每个进程的oom分数可以在/proc/PID/oom_score中找到。每个进程都有一个oom_score的属性,oom killer会杀死oom_score较大的进程,当oom_score为0时禁止内核杀死该进程。在用户空间可以通过操作每个进程的 oom_adj 内核参数来调整进程的分数,这个分数也可以通过 oom_score 这个内核参数看到。

注:oom_adj的可调值为15到-16,其中15最大-16最小,-17为禁止使用OOM。oom_score为2的n次方计算出来的,其中n就是进程的oom_adj值,所以n越小,oom_score的分数越小,高的就越会被内核优先杀掉。

检测脚本:将下述脚本可用来打印当前系统上 oom_score 分数最高(最容易被 OOM Killer 杀掉)的进程:

#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
printf "%2d %5d %s\n" \
"$(cat $proc/oom_score)" \
"$(basename $proc)" \
"$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10

测试验证:测试触发OOM的方法,可以把某个进程的oom_adj设置到15(最大值),执行:
echo f > /proc/sysrq-trigger //-f调用 oom_kill 来终止内存占用进程,这样15的那个进程就会报出OOM错误,需要注意的是这个测试,只是模拟OOM,不会真正杀掉进程;如下所示:

总结: 子进程会继承父进程的oom_adj。OOM不适合于解决内存泄漏(Memory leak)的问题。有时free查看还有充足的内存,但还是会触发OOM,是因为该进程可能占用了特殊的内存地址空间(更多详情参看)。

扩展: Java中,常见有java.lang.OutOfMemoryError;关于这类OOM的原因多为以下两点:

1)本身程序分配的少:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
2)应用用的太多,并且用完没释放,导致后续进程无法使用。此时就会造成内存泄露或者内存溢出。

内存泄露:申请使用完的内存没有释放,导致jvm虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被jvm虚拟机分配给别人用。大量的内存泄露最终会导致内存溢出。

内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

Java程序最常见的OOM情况有以下三种:(更多单击参考引用)

  • A、java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。

  • B、java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。

  • C、java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。

更多参见 OOM 常见原因及解决;2;

3)TCP参数优化

标志位说明:

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

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

ACK表示响应,

PSH表示有 DATA数据传输,

RST表示连接重置

net.ipv4.tcp_syn_retries //默认值为6,参考值为2。主机作为客户端,对外发起TCP连接时,即三次握手的第一步,内核发送SYN报文的重试次数,超过这个次数后放弃连接。内网环境通信良好,因此可以适度降低此值

net.ipv4.tcp_synack_retries //默认值为5,参考值为2。主机作为服务端,接受TCP连接时,在三次握手的第二步,向客户端发送SYN+ACK报文的重试次数,超过这个次数后放弃连接。内网环境中可适度降低此值

net.ipv4.tcp_timestamps //是否开启时间戳,开启后可以更精确地计算RTT,一些其他特性也依赖时间戳字段。

net.ipv4.tcp_tw_reuse //默认值为0,建议值为1。是否允许将处于TIME_WAIT状态的socket重新用于新的TCP连接。这对于降低TIME_WAIT数量很有效。该参数只有在开启tcp_timestamps的情况下才会生效。

net.ipv4.tcp_tw_recycle //是否开启TIME_WAIT套接字的快速回收,这是比tcp_tw_reuse更激进的一种方式,它同样依赖tcp_timestamps选项。强烈建议不要开启tcp_tw_recycle,原因有两点,一是TIME_WAIT是十分必要的状态,避免关闭中的连接与新建连接之间的数据混淆,二是tcp_tw_recycle选项在 NAT环境下会导致一些新建连接被拒绝,因为NAT下每个主机存在时差,这体现在套接字中的时间戳字段,服务端会发现某个IP上的本应递增的时间戳出现降低的情况,时间戳相对降低的报文将被丢弃

net.core.somaxconn //默认值为128,参考值为2048。定义了系统中每一个端口最大的监听队列的长度。当服务端监听了某个端口时,操作系统内部完成对客户端连接请求的三次握手。这些已建立的连接存储在一个队列中,等待accept调用取走。本选项就是定义这个队列的长度。调大该值,可降低高并发场景下服务端的reject次数。

net.ipv4.tcp_max_syn_backlog //客户端的请求在服务端由两个队列进行管理,一种是与客户端完成连接建立后,等待accept的放到一个队列,这个队列的长度由somaxconn参数控制;另一种是正在建立但未完成的连接单独存放一个队列,这个队列的长度由tcp_max_syn_backlog控制;默认128,调到至8192.

net.ipv4.tcp_max_tw_buckets //默认值为4096,参考值为100000。定义系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数,则TIME_WAIT套接字将立刻被清除并打印警告信息。如果系统被TIME_WAIT过多问题困扰,则可以调节tcp_max_tw_buckets、tcp_tw_reuse、tcp_timestamps三个选项来缓解。TIME_WAIT状态产生在TCP会话关闭时主动关闭的一端,如果想从根本上解决问题,则让客户端主动关闭连接,而非服务端。

扩展:

1)为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

2)为什么不能用两次握手进行连接?

答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在再发出的数据分组超时后,重复发送同样的分组。这样就形成了死锁。

3)如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会reset这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒就再发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接(更多参看)。

4)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 重传)

5)TCP 状态变迁图

4)page cache优化

page cache即系统脏页,是系统的io缓存,当数据写入磁盘前会先写入page cache中,然后异步刷入磁盘;写缓存可以提升IO的访问速度,但同时也会增加丢失数据的风险。

从page cache刷到磁盘有以下三种时机:

可用物理内存低于特定阈值时,为了给系统腾出空闲内存;
脏页驻留时间超过特定阈值时,为了避免脏页无限期驻留内存;
被用户的sync()或fsync()触发。

由系统执行的刷盘有两种写入策略:

异步执行刷盘,不阻塞用户I/O;
同步执行刷盘,用户I/O被阻塞,直到脏页低于某个阈值。
在一般情况下,系统先执行第一种策略,当脏页数据量过大,异步执行来不及完成刷盘时,切换到同步方式。

我们可以通过内核参数调整脏数据的刷盘阈值

vm.dirty_background_ratio //默认值为10。该参数定义了一个百分比。当内存中的脏数据超过这个百分比后,系统使用异步方式刷盘。

vm.dirty_ratio //默认值为30。同样定义了一个百分比,当内存中的脏数据超过这个百分比后,系统使用同步方式刷盘,写请求被阻塞,直到脏数据低于dirty_ratio。如果还高于dirty_background_ratio,则切换到异步方式刷盘。因此 dirty_ratio 应高于dirty_background_ratio。除了通过百分比控制,还可以指定过期时间:vm.dirty_expire_centisecs,默认值为3000(30秒),单位为百分之1秒,超过这个时间后,脏数据被异步刷盘。

可以通过下面的命令查看系统当前的脏页数量:
cat /proc/vmstat |egrep "dirty|writeback" //输出如下所示

nr_dirty 951
nr_writeback 0
nr_writeback_temp 0

上述结果显示:输出显示有951个脏页等待写到磁盘。默认情况下每页大小为4KB。另外,也可以在/proc/meminfo文件中看到这些信息。

如果数据安全性要求没有那么高,想要多“cache”一些数据,让读取更容易命中cache,则可以增加脏数据占比和过期时间:

vm.dirty_background_ratio = 30
vm.dirty_ratio = 60
vm.dirty_expire_centisecs = 6000

如果不希望因为刷盘导致io被阻,可适当减少异步刷盘的数值,这样可以让io更加平滑:

vm.dirty_background_ratio = 5
vm.dirty_ratio = 60
5)其他内核参数

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

内核参数解释:

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 队列满时才会触发 (在队列恢复可用时此行为关闭)。

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

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

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

? 启用 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加载,这些算法各自适用于不同的环境。

? reno是最基本的拥塞控制算法,也是TCP协议的实验原型。

? bic适用于rtt较高但丢包极为罕见的情况,比如北美和欧洲之间的线路,这是2.6.8到2.6.18之间的Linux内核的默认算法。

? cubic是修改版的bic,适用环境比bic广泛一点,它是2.6.19之后的linux内核的默认算法。

? 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.coredev_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:

发包时丢弃的数据包

net.ipv4.conf.lo.arp_ignore =1
net.ipv4.conf.lo.arp_announce =2
net.ipv4.conf.all.arp_ignore =1
net.ipv4.conf.all.arp_announce =2

net.core.wmen_default = 8388608
net.core.rmen_default = 8388608
net.core.rmen_max = 16777216
net.core.wmen_max = 16777216

2.2、Mysql运行环境优化(Linux系统)

1)修改Linux默认的IO调度算法.

linux默认的IO调度算法为cfq,需要修改为dealine,如果是SSD或者PCIe-SSD设备,需要修改为noop,可以使用下面两种修改方式。

echo “deadline” > /sys/block/sda/queue/scheduler //在线动态修改,重启失效

修改/etc/grub.conf,永久生效。修改/etc/grub.conf配置文件,在kernel那行增加一个配置,这里主要关注elevator这个参数,设置内核的话需要重启系统才能生效,例如:

ernel /vmlinuz-2.6.32-279.el6.x86_64 ro root=UUID=e01d6bb4-bd74-404f-855a-0f700fad4de0 rd_NO_LUKS rd_NO_LVM LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun1
6 crashkernel=auto  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM elevator=deadline rhgb quiet
2)扩大文件描述符

ulimit -n 51200 //动态修改,重启失效,只能使用root,并且当前session有效
nprocess /etc/security/limits.conf //扩大可开启进程数

永久生效,在/etc/security/limits.conf配置文件中增加一行:

*        hard    nofile           51200

/etc/security/limits.conf配置文件中增加一行:

*        hard    nproc           51200

修改/etc/pam.d/login文件添加:
session required /lib64/security/pam_limits.so

重启系统以后使用 ulimit -a 命令查看是否生效。

3)禁用numa特性

新一代架构的NUMA不适合跑数据库,它本意是为了提高内存利用率,但是实际效果不好,反而可能导致一CPU的内存尚有剩余,但是另外一个不够用,发生swap的问题,因此建议关闭或者修改NUMA的调度机制。

1)修改/etc/grub.conf关闭NUMA,重启后生效。
kernel /vmlinuz-2.6.32-279.el6.x86_64 ro root=UUID=e01d6bb4-bd74-404f-855a-0f700fad4de0 rd_NO_LUKS rd_NO_LVM LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun1 6 crashkernel=auto KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM elevator=deadline numa=off rhgb quiet

2)修改/etc/init.d/mysql或者mysqld_safe脚本,设置启动mysqld进程时的NUMA调度机制,例如:

if true && test $numa_interleave -eq 1
then
# Locate numactl, ensure it exists.
if ! my_which numactl > /dev/null 2>&1
then
log_error “numactl command not found, required for –numa-interleave”
exit 1
# Attempt to run a command, ensure it works.
elif ! numactl –interleave=all true
then
log_error “numactl failed, check if numactl is properly installed”
fi  # Launch mysqld with numactl.
cmd=$cmd numactl –interleave=all”
elif test $numa_interleave -eq 1
then
log_error “–numa-interleave is not supported on this platform”
exit 1
fi
4)修改swappiness设置

swappiness是linux的一个内核参数,用来控制物理内存交换出去的策略.它允许一个百分比的值,最小的为0,最大的为100,改值默认是60.

#sysctl -a | grep swappiness
vm.swappiness = 60

说明:vm.swappiness设置为0表示尽量少使用swap,100表示尽量将inactive的内存页(inactive内存的意思是程序映射着,但是”长时间”不用的内存。)交换到swap里或者释放cache(cache类似于预读的文件)。可用如下命令查看inactive:vmstat -a 1

在Centos7之前,这个值建议设置为0,但是在新版本的内核里面,这样设置可能导致OOM(内存溢出),然后kernel会杀掉使用内存最多的mysqld进程。
所以现在这个值推荐设置为1,在/etc/sysctl.conf文件中增加一行:

vm.swappiness = 1
#sysctl -p

5)优化文件系统挂载参数

文件系统挂载参数是在/etc/fstab文件中修改,重启时候生效。
noatime表示不记录访问时间,nodiratime不记录目录的访问时间。
barrier=0,表示关闭barrier功能.barrier的主要目的是为了保证磁盘写数据的安全性,但是会降低性能。如果有BBU之类的电池备份电源保证控制卡不瞬间掉电,那么这个功能就可以放心大胆的关闭。

验证:cat /proc/mounts

2.3、 Linux安全优化

参看:Linux之安全最佳做法

1>系统安全:设定Tcp_Wrappers防火墙
Tcp_Wrappers是一个用来分析tcp/i封包的工具,默认已经安装,否者执行install Tcp_Wrappers;Linux本身有2层防火墙,IP过滤使用iptables实现第一层防护,它集成在系统内核中,执行效率非常高,它主要通过直观地监测系统运行情况来阻挡网络中的一些恶意攻击,通过iptables下一层就到了Tcp_Wrappers,它可实现对系统一些服务的开关,允许和禁止访问。Tcp_Wrappers是通过/etc/hosts.allow和deny两个文件来完成的,格式如下:service:host(s) [:action];其中,hosts可以是域名或ip,可配置多个;其中,ALL:ALL EXCEPT ip,ALL表所有服务/ip,ALL EXCEPT ip表除了指定的服务/ip;一般,LInux会首先判断hosts.allow;

2.4、Linux基础优化

1)系统分区

1>磁盘RAID
RAID中3个关键概念和技术:镜像(mirroring)、数据条带(Data Stripping)、数据校验(Data parity),实际应用中使用最多的是RAID0、RAID1、RAID5、RAID10,应综合考虑可用性,性能,成本来悬着合适的RAID级别。

2>Linux OS分区设置
原则:系统分区和数据分区分离;一般,/、/boot、/var、/usr这4个分区最好单独配置挂载独立磁盘,同时它们最好在一个屋里raid1上;另数据分区单独设置在一个单独raid5上;线上环境,因LVM自身限制,并不是最优推荐,因LVM的动态扩容对大硬盘效果大大减弱,相反LVM会带来磁盘读写性能下降且不便于后期的维护,且LVM达到185T左右时再扩容存在失败风险,LVM分区一旦发生故障,数据丢失风险很大。

Swap分区最好还是配置上,它可防止你猜不够导致的性能急剧下降,甚至oom-killer,导致服务宕机,我们可通过/proc/sys/vm/swappiness这个swap参数来调整使用swap的概率,值越小使用swap的概率越低;swap配置原则:物理内存的2倍,虚拟内存的1倍,一般8G左右;

3>YUM源
建议启用epel和repoforge,执行:yum install epel-release;和rpm rpmforge-**.rpm

4>重要文件加锁
对一些重要文件使用chattr +i命令加锁;对重要日志文件+a(append);

5>系统资源open files和max user process设置
编辑/etc/security/limits.conf或./linits.d/20-nproc.conf
6>配置history
默认保存在用户目录下的.bash_history文件里;参考配置命令如下:

本文标签: 系统优化经验Linux