写在前面
前一阵领导安排了个任务,说学习并测试一下 Nginx 的并发能力,所以花了不少的时间去研究了下 Nginx 相关的内容,因为之前公司的工作鲜有 Linux 下的实操,所以这次收获也算不少,积累的问题也更多(?)
整个过程经历了各种软件的前期准备安装工作,然后是软件的学习工作(比如 Nginx 的配置、JMeter 的使用),接下来是 Nginx 的持续优化及反复测试,最后是对测试数据进行一个总结分析
因为不是上线前的那种准生产环境测试,所以很多场景都只是模拟,也因为不是很清楚 Nginx 会用在哪些服务上,简单来说就是没有确定真正的应用场景,所以这次只是很泛的去学习了解了 Nginx,没有针对哪方面做过于深入的研究
本来纠结要不要写一篇博客出来,有些内容自己也不是很确定理解的对不对,不过整体来说还是收获了不少,想了想还是写一下,如果有错误的地方希望看到这篇文章的 pong 友多多指教,非常欢迎一起讨论
环境说明
-
RHEL 6.8(Linux 2.6.32)
-
Nginx 1.14.0
-
PCRE 8.42
-
zlib 1.2.11
-
OpenSSL 1.0.2p
安装及基本配置
因为是在内网服务器,所以只能用编译的方式自己安装,根据 Nginx 官方文档,需要先安装三个依赖库:PCRE、zlib、OpenSSL,然后再安装 Nginx,详细的步骤这里就不描述了,按官方文档的步骤依次来就行了
唯一要说的可能就是 OpenSSL 了,官方步骤中设置 Configure 时有一个 darwin64-x86_64-cc
参数,可能会报错,按 OpenSSL 官方文档,可以临时指定一下环境变量
$ export KERNEL_BITS=64
这里附上我安装 Nginx 时的配置(命令执行时需要在一行内)
./configure
--prefix=/home/nginx/app/nginx
--sbin-path=/home/nginx/app/nginx/sbin/nginx
--conf-path=/home/nginx/app/nginx/conf/nginx.conf
--error-log-path=/home/nginx/app/nginx/error.log
--http-log-path=/home/nginx/app/nginx/logs/access.log
--pid-path=/home/nginx/app/nginx/nginx.pid
--user=nginx
--group=nginx
--with-pcre=/home/nginx/app/pcre-8.42
--with-zlib=/home/nginx/app/zlib-1.2.11
--with-openssl=/home/nginx/app/openssl-1.0.2p
--with-http_ssl_module
--with-http_stub_status_module
--with-http_gzip_static_module
--add-module=/home/nginx/app/nginx_upstream_check_module-master/
--with-google_perftools_module
这里的 PCRE、zlib、OpenSSL 分别是是下载的源码的路径,而不是安装后的路径
安装好后编辑 /nginx/conf/nginx.conf
使用 upstream 指令指定一组后端 Tomcat 服务器
upstream nginxDemo {
server 172.16.40.200:8080;
server 172.16.40.201:8081;
server 172.16.40.202:8080;
}
使用proxy_pass指定请求转发到哪一组服务器
location / {
root html;
index index.html index.htm;
proxy_pass http://nginxDemo;
}
高并发优化
0. Linux 优化
- 修改可用端口
$ echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range
- 修改 TW reuse/recycle
$ echo 1 >/proc/sys/net/ipv4/tcp_tw_reuse
$ echo 1 >/proc/sys/net/ipv4/tcp_tw_recycle
注意:官方建议不要修改 recycle 参数,且新版 Linux 中该参数被移除,生产环境需要注意
这里有篇博客讲了这个问题,可以参考一下
- 修改 limits.conf
$ vi /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
主要是修改 open files(nofile) 和 max user processes(nproc)
对于 Nginx 服务器,默认的用户进程数限制几乎无影响,增大用户进程可打开的文件句柄数即可,测试过程中使用65535没有报错,生产环境中如果发现 Nginx 日志中有相关报错可以继续增大配置
1. Nginx 优化
大体分为三个部分:
-
本身的工作进程数、连接数、使用 epoll 机制等等
-
IO 相关,比如缓冲区配置、gzip 压缩等等
-
TCP 连接相关,比如连接数、请求头/体大小等等
#增大工作进程数(和 CPU 数一致),并将其分配在不同的 CPU 上
worker_processes 8;
worker_cpu_affinity 10000000 01000000 00100000 00010000 00001000 00000100 00000010 00000001;
events {
# Linux2.6+ 使用 epoll,不配置时 Nginx 自动根据系统选择最合适的模式
use epoll;
#每个线程最大的连接数
worker_connections 65535;
# V1.11.3 之后默认关闭,打开这个配置时如果新的连接比较少,会导致少量的连接每次唤醒所有线程,更浪费资源
accept_mutex off;
#允许一个工作进程同时接收所有的新连接,禁用时,每个工作进程一次只能接收一个新连接
multi_accept on;
}
#使用 TCMalloc 优化内存管理
google_perftools_profiles /tmp/tcmalloc;
http {
#隐藏错误页面中 Nginx 版本号,较为安全
server_tokens off;
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr [$time_local]'
'$status $upstream_status '
'uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time" rt="$request_time"';
#连接日志延迟写入,缓冲 2M 或延迟1分钟之后才写一次
access_log logs/access.log main buffer=2m flush=1m;
#与 Nginx 做静态资源服务器性能优化相关,只做负载服务器时几乎无用
#sendfile on;
#tcp_nopush on;
#避免小数据包被协议延迟发送
tcp_nodelay on;
#指定 Nginx(作为服务端)的长连接超时时间,默认值75s;设置0则禁用客户端的长连接
keepalive_timeout 75s;
#指定一个长连接允许发起的请求数,超过则关闭该连接
keepalive_requests 9999;
#后端响应缓冲,据目前的理解,也是针对后端返回大量数据时,响应给客户端的一个优化
#proxy_buffering on;
#proxy_buffers 4 4k;
#不开启 buffering 也会生效的一个用于缓冲响应的第一部分(响应头?)的缓冲区,Nginx 会根据平台自动分配一个大小
#proxy_buffer_size 4k;
# gzip 相关的配置,目前不需要(根据业务场景考虑)
#gzip on;
#基本的后端服务器配置
upstream nginxDemo {
server 172.16.40.200:8080 weight=1;
server 172.16.40.201:8081 weight=1;
server 172.16.40.202:8080 weight=1;
#每个 worker 进程和后端服务器之间的长连接个数,默认值为空,即不维持和后端的长连接
#如果高峰期长连接个数超过该值,在高峰期过后,Nginx 会关闭超过配置个数的长连接
keepalive 1000;
}
server {
listen 6066;
server_name localhost;
location / {
root html;
index index.html index.htm;
#配置 Nginx 使用 HTTP1.1 协议,默认为 HTTP1.0 协议(且没有长连接)
proxy_http_version 1.1;
#配置 HTTP 协议为 1.1 时,将请求头的 Connection 置空
proxy_set_header Connection "";
proxy_pass http://nginxDemo;
}
#开启 Nginx stub_status 模块,用于基本的状态信息查看
location /nginx-status {
stub_status on;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
2. 后端 Tomcat 优化
增大线程池
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="500" minSpareThreads="20" maxQueueSize="200"/>
优化运行模式
现在的版本默认已经是 NIO 了,可以根据业务场景和实际生产环境,测试下 AIO 和 APR 的性能差异
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11AprProtocol"
connectionTimeout="20000"
redirectPort="8443" />
或
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11Nio2Protocol"
connectionTimeout="20000"
redirectPort="8443" />
高可用集群
Nginx 的官方文档中有介绍结合 KeepAlived 的例子,配置多台 Nginx 服务器实现高可用,简单来说:
-
一主一备:会浪费备用节点
-
双主互备:上层需要额外的分发路由
想安装测试下这个 KeepAlived 的时候,最初下载了官方最新的 2.x 版本,结果编译不通过,那个报错至今没找到解决方法。。就暂时放弃了,后面又再次尝试安装,改用了 1.4.5,安装就没有报错了,但后续的 Nginx 高可用测试目前还没有进行
后端节点健康检查
0. 官方自带模块
借用官方的 upstream 模块 和 proxy 模块 的相关配置来实现
配置示例:
upstream nginxDemo {
server 172.16.40.200:8080 maxfails=5 fail_timeout=1s;
server 172.16.40.201:8081 maxfails=5 fail_timeout=1s;
server 172.16.40.202:8080 maxfails=5 fail_timeout=1s;
}
fail_timeout 时间内失败次数达到 maxfails,则接下来 fail_timeout 时间内认定服务器为不可用
location / {
root html;
index index.html index.htm;
proxy_pass http://nginxDemo;
proxy_connect_timeout 5;
proxy_send_timeout 5;
proxy_read_timeout 5;
proxy_next_upstream error timeout invalid_header http_500 http_502;
}
指定各种超时时间,以及判断后端服务“异常”需要转发到下一台后端服务器的条件
1. 第三方模块
目前比较多的是淘宝的 upstream_check 模块 ,下载源码并解压,然后根据自己的 Nginx 版本,指定相应的补丁版本,我这里是要用1.14
$ patch -p1 < /home/nginx/app/nginx_upstream_check_module-master/check_1.14.0+.patch
然后重新配置 Nginx,在原先的配置基础上新增 upstream_check 的路径 --add-module=/home/nginx/app/nginx_upstream_check_module-master/
,然后编译安装即可
配置示例:
upstream nginxDemo {
server 172.16.40.200:8080 weight=1;
server 172.16.40.201:8081 weight=1;
server 172.16.40.202:8080 weight=1;
check interval=5000 rise=2 fall=5 timeout=300 type=http;
check_keepalive_requests 5;
check_http_send "GET /NginxTest/hi HTTP/1.1\r\nConnection:keep-alive\r\n\r\n";
}
check
的配置是说,使用 http 连接做健康检查,每隔5秒发送一次请求,请求的超时时间为300毫秒,连续成功两次则判定后端服务为正常,连续失败5次则判定后端服务为失败
check_keepalive_requests
的配置是说一个长连接允许请求5次,超过5次则会关闭该连接
check_http_send
的配置就是 http 请求的描述,什么协议啊、什么请求的路径啊、使用 GET 方法之类的
该模块的详细配置说明可以从 Tengine 的官方文档中查看
2. 总结&问题
-
官方的模块是借用正常的业务请求来实现健康检查,达到“异常”的标准后,会把异常服务移除,并在可能的情况下将失败的请求转发给其他正常的服务
-
第三方模块是由 Nginx 服务器主动发起请求实现健康检查,可以灵活配置请求包的内容和方式
了解了所谓的健康检查”功能”之后,倒是有了个新问题:检查的频率设定多少?
如果服务本身的访问量已经比较大了,比如稳定每秒数百数千,如果用官方模块配置 maxfails
和 fail_timeout
,尤其是 timeout 参数,因为它既是判断失败的时间段,也是判定失败后的不可用时间段,总感觉配置小了也不对,配置大了更不对
用淘宝的第三方模块会稍微好一点,但如果检测的请求也很频繁的话,不得不说对后端服务器也会有一定的影响吧
高级使用
0. 使用 TCMalloc 优化内存管理
TCMalloc 的全称为 Thread-Caching Malloc,是谷歌开发的开源工具 google-perftools 中的一个成员,与标准的 glibc 库的 Malloc 相比,TCMalloc 库在内存分配效率和速度上要高很多,这在很大程度上提高了服务器在高并发情况下的性能,从而降低了系统的负载
目前网上常见的使用包括优化 Nginx、MySQL、Redis 等
这里大概列一下安装步骤(因为踩到了一个小坑):
-
64位系统要先安装 libunwind 库
-
安装 perftools
-
重新编译安装 Nginx
在原 ./configure 配置的基础上加一个 --with-google_perftools_module
- 配置线程目录
$ mkdir /tmp/tcmalloc
$ chown 777 /tmp/tcmalloc
$ vi nginx.conf
加上 google_perftools_profiles /tmp/tcmalloc;
Nginx 的官方说明:设置的文件是用来保存 Nginx worker 进程分析信息的,信息会被保存到以 worker 进程 pid 为结尾的文件中
- 启动 Nginx(报错)
./nginx: error while loading shared libraries: libprofiler.so.0: cannot open shared object file: No such file or directory
之前按网上的步骤已经把默认安装生成的文件路径 /usr/local/lib 配置到了 ld.so.conf.d 下,但启动还是说找不到,所以最后建了个软连接应变
先找到本地的文件,再建立软连接:
$ find / -type f -name "libprofiler.so*"
/home/nginx/app/gperftools-2.7/.libs/libprofiler.so.0.4.18
/usr/local/lib/libprofiler.so.0.4.18
$ ln -sv /usr/local/lib/libprofiler.so.0.4.18 /lib64/libprofiler.so.0
"/lib64/libprofiler.so.0" -> "/usr/local/lib/libprofiler.so.0.4.18"
- 再次启动 Nginx,查看是否生效
$ lsof -n | grep tcmalloc
nginx 8067 nginx 10w REG 8,3 0 2490413 /tmp/tcmalloc.8067
nginx 8068 nginx 12w REG 8,3 0 2490416 /tmp/tcmalloc.8068
nginx 8069 nginx 14w REG 8,3 0 2490420 /tmp/tcmalloc.8069
nginx 8070 nginx 16w REG 8,3 0 2490419 /tmp/tcmalloc.8070
nginx 8072 nginx 18w REG 8,3 0 2490418 /tmp/tcmalloc.8072
nginx 8073 nginx 20w REG 8,3 0 2490417 /tmp/tcmalloc.8073
nginx 8074 nginx 22w REG 8,3 0 2490414 /tmp/tcmalloc.8074
nginx 8075 nginx 24w REG 8,3 0 2490415 /tmp/tcmalloc.8075
1. 结合 Consul 实现自动更新 upstream
Consul 是一个服务发现及配置共享的软件,结合第三方插件 upsync 可以实现不重启 Nginx 就可以更新 upstream 配置的功能
安装使用步骤:
- 准备 Consul
官网 下载,解压后可直接使用,以命令行的方式启动
$ ./consul agent -server -bootstrap -data-dir=/home/nginx/app/consul/data/ -node=ConsulServer01 -bind=172.16.40.224 -config-dir=/home/nginx/app/consul/config/ -client=0.0.0.0 –ui
主要参数的意义:
agent
: 运行一个 consul 代理
-server
: 切换代理到服务器模式
-bootstrap
: 将服务器设置为引导模式
-ui
: 启用内置的静态 web UI 服务器
-data-dir
: 路径到数据目录存储代理状态
-bind
: 设置集群通信的绑定地址
-client
: 设置用于绑定客户端访问的地址。这包括 RPC、DNS、HTTP 和 HTTPS(如果配置)
-node
: 此节点的名称。在集群中必须是唯一的
- 安装 upsync 模块
从 Github 下载,重新编译 Nginx,在原 ./configure 配置的基础上新增 --add-module=/home/nginx/app/nginx-upsync-module-master/
- 修改 Nginx.conf
upstream nginxDemo {
# upstream 下如果不配置一个 server,启动时会报错
server 172.16.40.201:8081 weight=1;
upsync 172.16.40.224:8500/v1/kv/upstreams/nginxDemo/ upsync_timeout=1s upsync_interval=10s upsync_type=consul strong_dependency=off;
upsync_dump_path /home/nginx/app/consul/config/nginxDemo.conf;
}
主要是配置 Consul 服务端的地址及取哪个 k/v 配置,以及同步的时间间隔、超时时间等等
详细的说明参考官方 wiki
- 测试
向 Consul 的 k/v 存储中 PUT 后端服务配置,新增202服务器:
$ curl -X PUT http://172.16.40.224:8500/v1/kv/upstreams/nginxDemo/172.16.40.202:8080
然后通过 Nginx 访问后端服务,已经可以看到有请求被转发到202
从 Consul 的 k/v 存储中 DELETE 后端服务配置,删除202服务器:
$ curl -X DELETE http://172.16.40.224:8500/v1/kv/upstreams/nginxDemo/172.16.40.202:8080
再访问 Nginx,就会发现已经不会再转发到202了
默认的后端服务器配置是weight=1 max_fails=2 fail_timeout=10 down=0 backup=0
如果希望手动指定,可以加额外的 json 格式的配置:
$ curl -X PUT -d '{"weight":2, "max_fails":10, "fail_timeout":10}' http://172.16.40.224:8500/v1/kv/upstreams/accounting/172.16.40.201:8081
- 问题
目前 upsync 模块和 upstream_check 模块一起编译时,会报错
objs/addon/src/ngx_http_upsync_module.o: In function `ngx_http_upsync_add_peers':
/home/nginx/app/nginx-upsync-module-master/src/ngx_http_upsync_module.c:893: undefined reference to `ngx_http_upstream_check_add_dynamic_peer'
objs/addon/src/ngx_http_upsync_module.o: In function `ngx_http_upsync_del_peers':
/home/nginx/app/nginx-upsync-module-master/src/ngx_http_upsync_module.c:1126: undefined reference to `ngx_http_upstream_check_delete_dynamic_peer'
upsync 的作者从官方 upstream_check 库拉了一个分支做了改动,可以兼容编译,但存在一些更新不及时的情况,比如目前官方 upstream_check 已经有 Nginx1.14 的补丁,但兼容分支目前还是1.12的补丁
虽然 issue 中有人说1.12的补丁也兼容1.14、1.15,不过我没编译通过
2. OpenResty(官网搬运)
打包了 Nginx 核心及常用第三方模块的全功能 Web 服务器,可以使用 Lua 脚本对 Nginx 核心进行编程,致力于将服务端应用完全运行于 Nginx 服务器中
应用场景:
-
在 Lua 中混合处理不同 Nginx 模块输出(proxy, drizzle, postgres, Redis, memcached 等)
-
在请求真正到达上游服务之前,Lua 中处理复杂的准入控制和安全检查
-
比较随意的控制应答头(通过 Lua)
-
内容过多,这里不再赘述,可以从下面的相关链接中查看了解…
相关链接:
3. Tengine(官网搬运)
Tengine 是由淘宝网发起的 Web 服务器项目。它在 Nginx 的基础上,针对大访问量网站的需求,添加了很多高级功能和特性
特性:
-
继承 Nginx-1.8.1 的所有特性,兼容 Nginx 的配置;
-
动态模块加载(DSO)支持。加入一个模块不再需要重新编译整个 Tengine;
-
支持 HTTP/2 协议,HTTP/2 模块替代 SPDY 模块;
-
流式上传到 HTTP 后端服务器或 FastCGI 服务器,大量减少机器的 I/O 压力;
-
内容过多,这里不再赘述,可以从下面的相关链接中查看了解…
相关链接:
最后
Nginx 相关的文章还有另外一篇《验证Nginx的长连接(keepalive)配置》,还是如开头所说,这篇文章比较泛,我也只是把整个过程中学到的内容简单整理了一下,暂时没有针对 Nginx 做某方面的深入研究,如果某些细节能帮助到你,那当然很好,如果你发现文章中有哪里写错了,欢迎评论