Priest Tomb



Nginx学习总结

写在前面

前一阵领导安排了个任务,说学习并测试一下 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 服务器主动发起请求实现健康检查,可以灵活配置请求包的内容和方式

了解了所谓的健康检查”功能”之后,倒是有了个新问题:检查的频率设定多少?

如果服务本身的访问量已经比较大了,比如稳定每秒数百数千,如果用官方模块配置 maxfailsfail_timeout,尤其是 timeout 参数,因为它既是判断失败的时间段,也是判定失败后的不可用时间段,总感觉配置小了也不对,配置大了更不对

用淘宝的第三方模块会稍微好一点,但如果检测的请求也很频繁的话,不得不说对后端服务器也会有一定的影响吧


高级使用

0. 使用 TCMalloc 优化内存管理

TCMalloc 的全称为 Thread-Caching Malloc,是谷歌开发的开源工具 google-perftools 中的一个成员,与标准的 glibc 库的 Malloc 相比,TCMalloc 库在内存分配效率和速度上要高很多,这在很大程度上提高了服务器在高并发情况下的性能,从而降低了系统的负载

目前网上常见的使用包括优化 Nginx、MySQL、Redis 等

这里大概列一下安装步骤(因为踩到了一个小坑):

在原 ./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)

  • 内容过多,这里不再赘述,可以从下面的相关链接中查看了解…

相关链接:

官网

OpenResty 最佳实践

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 做某方面的深入研究,如果某些细节能帮助到你,那当然很好,如果你发现文章中有哪里写错了,欢迎评论