本篇只讨论OS级别的并发优化,不包括语言层面对并发的支持特性
有关Linux系统的限制
系统最大文件数
使用ulimit
设置最多能打开文件数:
- 查看所有的限制:
ulimit -a
- 设置最大打开文件句柄数:
ulimit -n 65535
, 65535 = 2^16 = 0xFFFF - 设置 每个用户的 最大进程数:
ulimit -u 32768
- 设置线程栈的大小:
ulimit -s 10240
- 设置最大线程数数:
ulimit -T
(在Unix上可能不同) - 设置产生core文件大小:
ulimit -c xxx
- 不限制core的大小:
ulimit -c unlimited
ulimit起作用的范围是”当前Shell”, 并不是作用于”当前用户”, 如要对”用户”级别做限制, 则需要修改系统文件 /etc/security/limits.conf
:
# * 表示所有用户, nofile表示限制文件打开数, 限制在100 |
如果是针对整个系统, 则需要使用sysctl
修改, 命令格式为: sysctl -w fs.nr_open=10000000
, 每个系统参数对应一个/proc下的文件, fs.nr_open
对应的文件路径是/proc/sys/fs/nr_open
系统最大打开文件数相关的参数有两个:
- fs.nr_open,进程级别
- fs.file-max,系统级别
至此总结一下, “Linux系统最多能打开文件数”有当前shell, 用户, 系统三个级别,
shell级别的更改限制命令是ulimit,
更改系统级别限制的命令是sysctl, 限制优先级最大的当然是fs.file-max, 假如fs.file-max设置为100万, ulimit是不能超过100万的.
cat /proc/sys/fs/file-nr, 输出 9344 0 592026,分别为:1.已经分配的文件句柄数,2.已经分配但没有使用的文件句柄数,3.最大文件句柄数
file-nr不是单个进程的限制, 是系统级的, 最后一个数字与file-max
相同
此外, sysctl能修改的其他系统参数包括:
- net.ipv4.ip_local_port_range = 1024 65000 //允许系统打开的端口范围, 操作系统上端口号1024以下是系统保留的,从1024-65535是用户使用的
- net.core.netdev_max_backlog = 262144 // 每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
- net.ipv4.tcp_max_tw_buckets = 6000 //timewait 的数量,默认是180000。
- net.ipv4.tcp_tw_recycle = 1 // 启用timewait 快速回收
- net.ipv4.tcp_tw_reuse = 1 // 开启重用。允许将TIME-WAIT sockets 重新用于新的TCP 连接。
- net.ipv4.tcp_syncookies = 1 // 开启SYN Cookies,当出现SYN 等待队列溢出时,启用cookies 来处理。
- net.ipv4.tcp_max_orphans = 262144 //系统所能处理不属于任何进程的 socket数量,当我们需要快速建立大量连接时,就需要关注下这个值了. 这个限制仅仅是为了防止简单的DoS 攻击,不能过分依靠它或者人为地减小这个值,更应该增加这个值(如果增加了内存之后)。
- net.ipv4.tcp_max_syn_backlog = 262144 // 记录的那些尚未收到客户端确认信息的连接请求的最大值。对于有128M 内存的系统而言,缺省值是1024,小内存的系统则是128。
- net.ipv4.tcp_timestamps = 0 //时间戳可以避免序列号的卷绕。一个1Gbps 的链路肯定会遇到以前用过的序列号。时间戳能够让内核接受这种“异常”的数据包。这里需要将其关掉。
- net.ipv4.tcp_synack_retries = 1 //为了打开对端的连接,内核需要发送一个SYN 并附带一个回应前面一个SYN 的ACK。也就是所谓三次握手中的第二次握手。这个设置决定了内核放弃连接之前发送SYN+ACK 包的数量。
- net.ipv4.tcp_syn_retries = 1 // 在内核放弃建立连接之前发送SYN 包的数量。
- net.ipv4.tcp_fin_timeout = 1 //如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2 状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60 秒。2.2 内核的通常值是180 秒,3你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB 服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2 的危险性比FIN-WAIT-1 要小,因为它最多只能吃掉1.5K 内存,但是它们的生存期长些。
- net.ipv4.tcp_keepalive_time = 30 //当keepalive 起用的时候,TCP 发送keepalive 消息的频度。缺省是2 小时。
- net.ipv4.tcp_mem = 786432 2097152 3145728 // tcp的内存大小,其单位是页, 当超过第二个值时,TCP进入 pressure模式,此时TCP尝试稳定其内存的使用,当小于第一个值时,就退出pressure模式。当内存占用超过第三个值时,TCP就拒绝分配 socket了
- net.ipv4.tcp_rmem = 4096 4096 16777216 # 读缓冲的大小, 三个分别是最小/默认/最大值
- net.ipv4.tcp_wmem = 4096 4096 16777216
永久生效方法:在/etc/sysctl.conf中添加
如何查看已创建文件描述符数?
- 某进程打开文件数
ll /proc/1599/fd | wc -l
- 某进程打开socket的数量:
ll /proc/1599/fd | grep socket | wc -l
# nginx一个worker打开了200-300个socket - 系统全部打开的文件数
lsof | wc -l
- 系统全部打开的TCP连接数
lsof | grep TCP | wc -l
- 查看tcp不同状态连接数:
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
- 注意处于TIME_WAIT的链接, 如果这个数过高会占用大量连接, 应该调整参数尽快的释放time_wait连接
在Nginx机器上测试:[@zw_85_63 ~]# netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
TIME_WAIT 37968
SYN_SENT 1
FIN_WAIT1 5
FIN_WAIT2 4
ESTABLISHED 2725
SYN_RECV 18
LAST_ACK 4
系统最大进线程数
某些服务程序(Apache, Tomcat) 采用 “Thread Per Request”, 系统的进线程最大数也会影响并发性能.
Linux 没有 直接限制 每个进程能够创建线程数, 仅限制了系统最大进线程数, 相关的配置有 :
- 仅对当前 shell有效:
ulimit -u 102400
,-u
表示 “max user processes”; - 系统级别有效:
- 临时生效:
echo 102400 > /proc/sys/kernel/threads-max
或sysctl -w sys.kernel.threads-max=10240
; - 永久生效: 修改 /etc/sysctl.conf 文件;
- 临时生效:
这里的 threads-max 不是指 进程, 是 “maximum number of threads that can be created using fork()”,
参考 @Ref https://www.kernel.org/doc/Documentation/sysctl/kernel.txt
每个进程能创建的最大线程数, 是由 total virtual memory 和stack size 共同决定的, number of threads = total virtual memory / stack size
这两个参数分别用 ulimit -v xxx
和 ulimit -s xxx
设置
此外系统能创建最大进程数还受 kernel.pid_max
影响:
- 方式1 运行时限制,临时生效
echo 999999 > /proc/sys/kernel/pid_max
- 方式2 修改/etc/sysctl.conf,永久生效
sys.kernel.pid_max = 999999
应用程序的设置
根据不同程序不同IO模型, 设置的思路也不一样
BIO:Apache/Tomcat, 主要是并发量要求不高的场景
NIO:Nginx/Redis, 主要是高并发量要求的场景
Redis使用单线程的I/O复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和select。对于单纯只有I/O操作来说,单线程可以将速度优势发挥到最大。
但是Redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实际会严重影响整体吞吐量,CPU计算过程中,整个I/O调度都是被阻塞住的,
nginx
- worker_connections= #每个worker线程能创建的连接数
- upstream可以使用http 1.1的keepalive #与后端服务器创建的连接池大小
- worker_processes 8; # nginx进程数,一般等于cpu core数量
- worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000; # 每个进程分配到cpu的core上
- worker_rlimit_nofile 65535; # 一个nginx进程打开的最多文件描述符数目
redis
@TODO
apache
默认是多进程同步处理request, 所以思路和Nginx每个Core一个进程epoll轮询的方式不同, apache应该增加”系统创建进程数上限”, 并且减小进程栈内存
tomcat
- maxThreads=500 最大线程数,此值限制了bio的最大连接数
- 一般的当一个进程有500个线程在跑的话,那性能已经是很低很低了。Tomcat默认配置的最大请求数是150。当某个应用拥有250个以上并发的时候,应考虑应用服务器的集群。
- maxConnection=8192: 使用nio或者apr时,最大连接数受此值影响。
参考 聊下并发和Tomcat线程数(Updated) - zhanjindong - 博客园 @Ref
FastCGI
- fastcgi_connect_timeout 300;
高并发配置-无废话总结
- 应用程序的并发设置: 主要是timeout, 进/线程数这几类参数
- 操作系统打开文件数量限制:
ulimit -n
单个Shell环境的限制,sysctl -w fs.file-max
修改系统打开文件限制 - 操作系统打开端口数量限制: 最大端口数65535(2^16), 但1024以后的端口是给系统用的
- sysctl修改的TCP协议栈参数
并发性能测试工具
ab(Apache Bench)
1000并发, 总共20000次请求: ab -n 20000 -c 1000 <url>
http_load
30个并发线程, 共60秒测试: http_load -p 30 -s 60 Url.txt
JMeter
配置
@TODO
测试
@TODO
报告
在聚合报告中,会显示一行数据,共有10个字段,含义分别如下。
- Label:每个 JMeter 的 element(例如 HTTP Request)都有一个 Name 属性,这里显示的就是 Name 属性的值
- #Samples:表示你这次测试中一共发出了多少个请求,如果模拟10个用户,每个用户迭代10次,那么这里显示100
- Average:平均响应时间——默认情况下是单个 Request 的平均响应时间,当使用了 Transaction Controller 时,也可以以Transaction 为单位显示平均* 响应时间
- Median:中位数,也就是 50% 用户的响应时间
- 90% Line:90% 用户的响应时间
- Min:最小响应时间
- Max:最大响应时间
- Error%:本次测试中出现错误的请求的数量/请求的总数
- Throughput:吞吐量——默认情况下表示每秒完成的请求数(Request per Second)
- KB/Sec:每秒从服务器端接收到的数据量,相当于LoadRunner中的Throughput/Sec
参考: 使用JMeter进行负载测试——终极指南 - ImportNew @Ref
wrk
wg/wrk: Modern HTTP benchmarking tool