Contents

Linux内核调优

Contents
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
查看所有可读变量:
# sysctl -a 

读一个指定的变量,例如 kern.maxproc:
# sysctl kern.maxproc kern.maxproc: 1044 

要设置一个指定的变量,直接用 variable=value 这样的语法:
# sysctl kern.maxfiles=5000
kern.maxfiles: 2088 -> 5000 
您可以使用sysctl修改系统变量,也可以通过编辑sysctl.conf文件来修改系统变量。sysctl.conf 看起来很像 rc.conf。它用 variable=value 的形式来设定值。指定的值在系统进入多用户模式之后被设定。并不是所有的变量都可以在这个模式下设定。
sysctl 变量的设置通常是字符串、数字或者布尔型。 (布尔型用 1 来表示'yes',用 0 来表示'no')。例如
#sysctl -w kernel.sysrq=0 

下面是对/etc/sysctl.conf配置文件的一些中文注释。在对服务器性能进行调整的时候可能会用得上!
#最大的待发送TCP数据缓冲区空间 
net.inet.tcp.sendspace=65536 
#最大的接受TCP缓冲区空间 
net.inet.tcp.recvspace=65536 
#最大的接受UDP缓冲区大小 
net.inet.udp.sendspace=65535 
#最大的发送UDP数据缓冲区大小 
net.inet.udp.maxdgram=65535 
#本地套接字连接的数据发送空间 
net.local.stream.sendspace=65535 

#加快网络性能的协议 
net.inet.tcp.rfc1323=1 
net.inet.tcp.rfc1644=1 
net.inet.tcp.rfc3042=1 
net.inet.tcp.rfc3390=1 

#最大的套接字缓冲区 
kern.ipc.maxsockbuf=2097152 

#系统中允许的最多文件数量 
kern.maxfiles=65536 

#每个进程能够同时打开的最大文件数量 
kern.maxfilesperproc=32768 

#当一台计算机发起TCP连接请求时,系统会回应ACK应答数据包。该选项设置是否延迟ACK应答数据包,把它和包含数据的数据包一起发送,在高速网络和低负载的情况下会略微提高性能,但在网络连接较差的时候,对方计算机得不到应答会持续发起连接请求,反而会降低性能。 
net.inet.tcp.delayed_ack=0 

#屏蔽ICMP重定向功能 
net.inet.icmp.drop_redirect=1 
net.inet.icmp.log_redirect=1 
net.inet.ip.redirect=0 
net.inet6.ip6.redirect=0 

#防止ICMP广播风暴 
net.inet.icmp.bmcastecho=0 
net.inet.icmp.maskrepl=0 

#限制系统发送ICMP速率 
net.inet.icmp.icmplim=100 

#安全参数,编译内核的时候加了options TCP_DROP_SYNFIN才可以用 
net.inet.icmp.icmplim_output=0 
net.inet.tcp.drop_synfin=1 

#设置为1会帮助系统清除没有正常断开的TCP连接,这增加了一些网络带宽的使用,但是一些死掉的连接最终能被识别并清除。死的TCP连接是被拨号用户存取的系统的一个特别的问题,因为用户经常断开modem而不正确的关闭活动的连接 
net.inet.tcp.always_keepalive=1 

#若看到net.inet.ip.intr_queue_drops这个在增加,就要调大net.inet.ip.intr_queue_maxlen,为0最好 
net.inet.ip.intr_queue_maxlen=1000 

#防止DOS攻击,默认为30000 
net.inet.tcp.msl=7500 

#接收到一个已经关闭的端口发来的所有包,直接drop,如果设置为1则是只针对TCP包 
net.inet.tcp.blackhole=2 

#接收到一个已经关闭的端口发来的所有UDP包直接drop 
net.inet.udp.blackhole=1 

#为网络数据连接时提供缓冲 
net.inet.tcp.inflight.enable=1 

#如果打开的话每个目标地址一次转发成功以后它的数据都将被记录进路由表和arp数据表,节约路由的计算时间,但会需要大量的内核内存空间来保存路由表 
net.inet.ip.fastforwarding=0 

#kernel编译打开options POLLING功能,高负载情况下使用低负载不推荐SMP不能和polling一起用 
#kern.polling.enable=1 

#并发连接数,默认为128,推荐在1024-4096之间,数字越大占用内存也越大 
kern.ipc.somaxconn=32768 

#禁止用户查看其他用户的进程 
security.bsd.see_other_uids=0 

#设置kernel安全级别 
kern.securelevel=0 

#记录下任何TCP连接 
net.inet.tcp.log_in_vain=1 

#记录下任何UDP连接 
net.inet.udp.log_in_vain=1 

#防止不正确的udp包的攻击 
net.inet.udp.checksum=1 

#防止DOS攻击 
net.inet.tcp.syncookies=1 

#仅为线程提供物理内存支持,需要256兆以上内存 
kern.ipc.shm_use_phys=1 

# 线程可使用的最大共享内存 
kern.ipc.shmmax=67108864 

# 最大线程数量 
kern.ipc.shmall=32768 

# 程序崩溃时不记录 
kern.coredump=0 

# lo本地数据流接收和发送空间 
net.local.stream.recvspace=65536 
net.local.dgram.maxdgram=16384 
net.local.dgram.recvspace=65536 

# 数据包数据段大小,ADSL为1452。 
net.inet.tcp.mssdflt=1460 

# 为网络数据连接时提供缓冲 
net.inet.tcp.inflight_enable=1 

# 数据包数据段最小值,ADSL为1452 
net.inet.tcp.minmss=1460 

# 本地数据最大数量 
net.inet.raw.maxdgram=65536 

# 本地数据流接收空间 
net.inet.raw.recvspace=65536 

#ipfw防火墙动态规则数量,默认为4096,增大该值可以防止某些病毒发送大量TCP连接,导致不能建立正常连接 
net.inet.ip.fw.dyn_max=65535 

#设置ipf防火墙TCP连接空闲保留时间,默认8640000(120小时) 
net.inet.ipf.fr_tcpidletimeout=864000

#默认的接收TCP窗口大小
net.core.rmem_default = 256960

#最大的TCP数据接收缓冲 
net.core.rmem_max = 256960

#默认的发送TCP窗口大小 
net.core.wmem_default = 256960

#最大的TCP数据发送缓冲
net.core.wmem_max = 256960

#以一种比重发超时更精确的方法(请参阅 RFC 1323)来启用对 RTT 的计算;为了实现更好的性能应该启用这个选项 
net.ipv4.tcp_timestamps = 0

#启用有选择的应答(Selective Acknowledgment),这可以通过有选择地应答乱序接收到的报文来提高性能(这样可以让发送者只发送丢失的报文段);(对于广域网通信来说)这个选项应该启用,但是这会增加对 CPU 的占用。 
net.ipv4.tcp_sack =1

#启用转发应答(Forward Acknowledgment),这可以进行有选择应答(SACK)从而减少拥塞情况的发生;这个选项也应该启用。

net.ipv4.tcp_fack =1

#支持更大的TCP窗口. 如果TCP窗口最大超过65535(64K), 必须设置该数值为1 
net.ipv4.tcp_window_scaling = 1

#确定 TCP 栈应该如何反映内存使用;每个值的单位都是内存页(通常是 4KB)。第一个值是内存使用的下限。第二个值是内存压力模式开始对缓冲区使用应用压力的上限。第三个值是内存上限。在这个层次上可以将报文丢弃,从而减少对内存的使用。
net.ipv4.tcp_mem= 24576 32768 49152

#为自动调优定义每个 socket 使用的内存。第一个值是为socket 的发送缓冲区分配的最少字节数。第二个值是默认值(该值会被 wmem_default 覆盖),缓冲区在系统负载不重的情况下可以增长到这个值。第三个值是发送缓冲区空间的最大字节数(该值会被 wmem_max 覆盖)。
net.ipv4.tcp_wmem= 4096 16384 131072

#与 tcp_wmem 类似,不过它表示的是为自动调优所使用的接收缓冲区的值
net.ipv4.tcp_rmem= 4096 87380 174760

#允许 TCP/IP 栈适应在高吞吐量情况下低延时的情况;这个选项应该禁用。
net.ipv4.tcp_low_latency= 0

#启用发送者端的拥塞控制算法,它可以维护对吞吐量的评估,并试图对带宽的整体利用情况进行优化;对于 WAN 通信来说应该启用这个选项
net.ipv4.tcp_westwood= 0

#为快速长距离网络启用 Binary Increase Congestion;这样可以更好地利用以 GB 速度进行操作的链接;对于 WAN 通信应该启用这个选项。
net.ipv4.tcp_bic= 1

#通过源路由,攻击者可以尝试到达内部IP地址 –包括RFC1918中的地址,所以不接受源路由信息包可以防止你的内部网络被探测。
net.inet.ip.sourceroute=0
net.inet.ip.accept_sourceroute=0

#vnode 是对文件或目录的一种内部表达。 因此, 增加可以被操作系统利用的 vnode 数量将降低磁盘的 I/O。一般而言, 这是由操作系统自行完成的,也不需要加以修改。但在某些时候磁盘 I/O 会成为瓶颈,而系统的 vnode 不足, 则这一配置应被增加。此时需要考虑是非活跃和空闲内存的数量。要查看当前在用的 vnode 数量:
# sysctl vfs.numvnodes
vfs.numvnodes: 91349

要查看最大可用的 vnode 数量:
# sysctl kern.maxvnodes
kern.maxvnodes: 100000
如果当前的 vnode 用量接近最大值,则将 kern.maxvnodes 值增大 1,000 可能是个好主意。
您应继续查看 vfs.numvnodes 的数值, 如果它再次攀升到接近最大值的程度,
仍需继续提高 kern.maxvnodes。 在 top(1) 中显示的内存用量应有显著变化,
kern.maxvnodes=8446

#最大的进程数
kern.maxproc= 964

#每个用户id允许的最大进程数
kern.maxprocperuid=867
注意,maxprocperuid至少要比maxproc少1,因为init(8) 这个系统程序绝对要保持在运作状态。

#系统中支持最多同时开启的文件数量,如果你在运行数据库或大的很吃描述符的进程,那么应该设置在20000以上
kern.maxfiles=2221928

转:Linux上TCP的几个内核参数调优

Linux作为一个强大的操作系统,提供了一系列内核参数供我们进行调优。光TCP的调优参数就有50多个。在和线上问题斗智斗勇的过程中,笔者积累了一些在内网环境应该进行调优的参数。在此分享出来,希望对大家有所帮助。

调优清单

好了,在这里先列出调优清单。请记住,这里只是笔者在内网进行TCP内核参数调优的经验,仅供参考。同时,笔者还会在余下的博客里面详细解释了为什么要进行这些调优!

序号 内核参数 备注
1.1 /proc/sys/net/ipv4/tcp_max_syn_backlog 2048
1.2 /proc/sys/net/core/somaxconn 2048
1.3 /proc/sys/net/ipv4/tcp_abort_on_overflow 1
2.1 /proc/sys/net/ipv4/tcp_tw_recycle 0 NAT环境必须为0
2.2 /proc/sys/net/ipv4/tcp_tw_reuse 1
3.1 /proc/sys/net/ipv4/tcp_syn_retries 3
3.2 /proc/sys/net/ipv4/tcp_retries2 5
3.3 /proc/sys/net/ipv4/tcp_slow_start_after_idle 0

tcp_max_syn_backlog,somaxconn,tcp_abort_on_overflow

tcp_max_syn_backlog,somaxconn,tcp_abort_on_overflow这三个参数是关于 内核TCP连接缓冲队列的设置。如果应用层来不及将已经三次握手建立成功的TCP连接从队列中取出,溢出了这个缓冲队列(全连接队列)之后就会丢弃这个连接。如下图所示: https://cdn.forsre.cn/kernel001.png 从而产生一些诡异的现象,这个现象诡异之处就在于,是在TCP第三次握手的时候丢弃连接 https://cdn.forsre.cn/kernel002.png 就如图中所示,第二次握手的SYNACK发送给client端了。所以就会出现client端认为连接成功,而Server端确已经丢弃了这个连接的现象!由于无法感知到Server已经丢弃了连接。 所以如果没有心跳的话,只有在发出第一个请求后,Server才会发送一个reset端通知这个连接已经被丢弃了,建立连接后第二天再用,也会报错!所以我们要调大Backlog队列!

1
2
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
echo 2048 > /proc/sys/net/core/somaxconn

当然了,为了尽量避免第一笔调用失败问题,我们也同时要设置

1
echo 1 > /proc/sys/net/ipv4/tcp_abort_on_overflow

设置这个值以后,Server端内核就会在这个连接被溢出之后发送一个reset包给client端。 D:/md/pic/kernel003.png 如果我们的client端是NIO的话,就可以收到一个socket close的事件以感知到连接被关闭! https://cdn.forsre.cn/kernel004.png

注意Java默认的Backlog是50

这个TCP Backlog的队列大小值是min(tcp_max_syn_backlog,somaxconn,应用层设置的backlog),而Java如果不做额外设置,Backlog默认值仅仅只有50。C语言在使用listen调用的时候需要传进Backlog参数。

tcp_tw_recycle

tcp_tw_recycle这个参数一般是用来抑制TIME_WAIT数量的,但是它有一个副作用。即在tcp_timestamps开启(Linux默认开启),tcp_tw_recycle会经常导致下面这种现象。 https://cdn.forsre.cn/kernel005.png 也即,如果你的Server开启了tcp_tw_recycle,那么别人如果通过NAT之类的调用你的Server的话,NAT后面的机器只有一台机器能正常工作,其它情况大概率失败。具体原因呢由下图所示: https://cdn.forsre.cn/kernel006.png 在tcp_tw_recycle=1同时tcp_timestamps(默认开启的情况下),对同一个IP的连接会做这样的限制,也即之前后建立的连接的时间戳必须要大于之前建立连接的最后时间戳,但是经过NAT的一个IP后面是不同的机器,时间戳相差极大,就会导致内核直接丢弃时间戳较低的连接的现象。由于这个参数导致的问题,高版本内核已经去掉了这个参数。如果考虑TIME_WAIT问题,可以考虑设置一下

1
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse

tcp_syn_retries

这个参数值得是client发送SYN如果server端不回复的话,重传SYN的次数。对我们的直接影响呢就是connet建立连接时的超时时间。当然Java通过一些C原生系统调用的组合使得我们可以进行超时时间的设置。在Linux里面默认设置是5,下面给出建议值3和默认值5之间的超时时间。

tcp_syn_retries timeout
1 min(so_sndtimeo,3s)
2 min(so_sndtimeo,7s)
3 min(so_sndtimeo,15s)
4 min(so_sndtimeo,31s)
5 min(so_sndtimeo,63s)

下图给出了,重传和超时情况的对应图: https://cdn.forsre.cn/kernel007.png 当然了,不同内核版本的超时时间可能不一样,因为初始RTO在内核小版本间都会有细微的变化。所以,有时候在抓包时候可能会出现(3,6,12……)这样的序列。当然Java的API有超时时间:

1
2
3
java:
 // 函数调用中携带有超时时间
 public void connect(SocketAddress endpoint, int timeout) ;

所以,对于Java而言,这个内核参数的设置没有那么重要。但是,有些代码可能会有忘了设置timeout的情况,例如某个版本的Kafka就是,所以它在我们一些混沌测试的情况下,容灾恢复的时间会达到一分多钟,主要时间就是卡在connect上面-_-!,而这时我们的tcp_syn_retries设置的是5,也即超时时间63s。减少这个恢复时间的手段就是:

1
echo 3 > /proc/sys/net/ipv4/tcp_syn_retries

tcp_retries2

tcp_retries2这个参数表面意思是在传输过程中tcp的重传次数。但在某个版本之后Linux内核仅仅用这个tcp_retries2来计算超时时间,在这段时间的重传次数纯粹由RTO等环境因素决定,重传超时时间在5/15下的表现为:

tcp_retries2 对端无响应
5 25.6s-51.2s根据动态rto定
15 924.6s-1044.6s根据动态rto定

如果我们在应用层设置的Socket所有ReadTimeout都很小的话(例如3s),这个内核参数调整是没有必要的。但是,笔者经常发现有的系统,因为一两个慢的接口或者SQL,所以将ReadTimeout设的很大的情况。 https://cdn.forsre.cn/kernel008.png 平常这种情况是没有问题的,因为慢请求频率很低,不会对系统造成什么风险。但是,物理机突然宕机时候的情况就不一样了,由于ReadTimeOut设置的过大,导致所有落到这台宕机的机器都会在min(ReadTimeOut,(924.6s-1044.6s)(Linux默认tcp_retries2是15))后才能从read系统调用返回。假设ReadTimeout设置了个5min,系统总线程数是200,那么只要5min内有200个请求落到宕机的server就会使A系统失去响应! https://cdn.forsre.cn/kernel009.png 但如果将tcp_retries2设置为5,那么超时返回时间即为min(ReadTimeOut 5min,25.6-51.2s),也就是30s左右,极大的缓解了这一情况。

1
echo 5 > /proc/sys/net/ipv4/tcp_retries2

但是针对这种现象,最好要做资源上的隔离,例如线程上的隔离或者机器级的隔离。 https://cdn.forsre.cn/kernel010.png

golang的goroutine调度模型就可以很好的解决线程资源不够的问题,但缺点是goroutine里面不能有阻塞的系统调用,不然也会和上面一样,但仅仅对于系统之间互相调用而言,都是非阻塞IO,所以golang做微服务还是非常Nice的。当然了我大Java用纯IO事件触发编写代码也不会有问题,就是对心智负担太高-_-!

物理机突然宕机和进程宕不一样

值得注意的是,物理机宕机和进程宕但内核还存在表现完全不一样。 https://cdn.forsre.cn/kernel011.png 仅仅进程宕而内核存活,那么内核会立马发送reset给对端,从而不会卡住A系统的线程资源。

tcp_slow_start_after_idle

还有一个可能需要调整的参数是tcp_slow_start_after_idle,Linux默认是1,即开启状态。开启这个参数后,我们的TCP拥塞窗口会在一个RTO时间空闲之后重置为初始拥塞窗口(CWND)大小,这无疑大幅的减少了长连接的优势。对应Linux源码为:

1
2
3
4
5
6
7
static void tcp_event_data_sent(struct tcp_sock *tp,
				struct sk_buff *skb, struct sock *sk){
	// 如果开启了start_after_idle,而且这次发送的时间-上次发送的时间>一个rto,就重置tcp拥塞窗口
	if (sysctl_tcp_slow_start_after_idle &&
	    (!tp->packets_out && (s32)(now - tp->lsndtime) > icsk->icsk_rto))
		tcp_cwnd_restart(sk, __sk_dst_get(sk));
}

https://cdn.forsre.cn/kernel012.png 关闭这个参数后,无疑会提高某些请求的传输速度(在带宽够的情况下)。

1
echo 0 > /proc/sys/net/ipv4/tcp_slow_start_after_idle

当然了,Linux启用这个参数也是有理由的,如果我们的网络情况是时刻在变化的,例如拿个手机到处移动,那么将拥塞窗口重置确实是个不错的选项。但是就我们内网系统间调用而言,是不太必要的了。

初始CWND大小

毫无疑问,新建连接之后的初始TCP拥塞窗口大小也直接影响到我们的请求速率。在Linux2.6.32源码中,其初始拥塞窗口是(2-4个)mss大小,对应于内网估计也就是(2.8-5.6K)(MTU 1500),这个大小对于某些大请求可能有点捉襟见肘。 在Linux 2.6.39以上或者某些RedHat维护的小版本中已经把CWND 增大到RFC 6928所规定的的10段,也就是在内网里面估计14K左右(MTU 1500)。

Linux 新版本

1
2
/* TCP initial congestion window */
#define TCP_INIT_CWND		10