Nginx(读音engine x)服务器由于性能优秀稳定、配置简单以及跨平台,被越来越多的公司和个人所采用,现已成为市场份额继Apache之后的第二大Web服务器。各大小网站论坛博客也介绍说明了Nginx从安装到优化的各种配置。不过看了很多这些相关Nginx的文档之后,发现一个比较大的问题,就是这些文档基本也就从两个方面着手,一是修改Nginx的配置文件,二是调整操作系统的相关内核参数;而且文档说明也不够明了,缺乏比较系统级别的优化。本文将从Nginx源码编译安装开始,到修改配置文件,调整系统内核参数以及架构四个方面着手分别介绍如何优化。

一. 安装

(1) 精简模块

Nginx由于不断添加新的功能,附带的模块也越来越多。很多操作系统厂商为了用户方便安装管理,都增加了rpm、deb或者其他自有格式软件包,可以本地甚至在线安装。不过我不太建议使用这种安装方式。这虽然简化了安装,在线安装甚至可以自动解决软件依赖关系,但是安装后软件的文件布局过于分散,不便管理维护;同时也正是由于存在软件包之间的依赖关系,导致当有安全漏洞、或者其它问题,想要通过更新升级Nginx新版本时却发现yum、deb源还未发布新版本(一般都落后于官网发布的软件版本)。最重要的是采用非源码编译安装的方式,默认会添加入许多模块,比如邮件相关、uwsgi、memcache等等,很多网站运行时这些模块根本未用到,虽然平时占用的资源很小,但是仍然可能是压弯骆驼的一根稻草。各种非必需模块默认安装运行的同时,也给Web系统带来了安全隐患。尽量保持软件的轻装上阵,是每个运维应当尽力做到的,所以我建议一般常用的服务器软件使用源码编译安装管理。。我一般使用的编译参数如下,PHP相关模块fastcgi被保留用作后文优化说明,:

./configure \
"--prefix=/App/nginx" \
"--with-http_stub_status_module" \
"--without-http_auth_basic_module" \
"--without-http_autoindex_module" \
"--without-http_browser_module" \
"--without-http_empty_gif_module" \
"--without-http_geo_module" \
"--without-http_limit_conn_module" \
"--without-http_limit_req_module" \
"--without-http_map_module" \
"--without-http_memcached_module" \
"--without-http_proxy_module" \
"--without-http_referer_module" \
"--without-http_scgi_module" \
"--without-http_split_clients_module" \
"--without-http_ssi_module" \
"--without-http_upstream_ip_hash_module" \
"--without-http_upstream_keepalive_module" \
"--without-http_upstream_least_conn_module" \
"--without-http_userid_module" \
"--without-http_uwsgi_module" \
"--without-mail_imap_module" \
"--without-mail_pop3_module" \
"--without-mail_smtp_module" \
"--without-poll_module" \
"--without-select_module" \
"--with-cc-opt='-O2'"

编译参数根据网站是否真正用到的原则增添或者减少,比如我们公司如果需要用到ssi模块,从而能够实现访问shtml页面,可以将第17行删除,那么Nginx将默认安装。大家可以通过运行 "./configure --help" 查看编译帮助,决定是否需要安装哪些模块。

(2) GCC编译参数优化 [可选项】

GCC总共提供了5级编译优化级别:
-O0: 无优化。
-O和-O1: 使用能减少目标代码尺寸以及执行时间并且不会使编译时间明显增加的优化。在编译大型程序的时候会显著增加编译时内存的使用。
-O2: 包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化。编译器不执行循环展开以及函数内联。此选项将增加编译时间和目标文件的执行性能。
-Os: 可以看成 -O2.5,专门优化目标文件大小,执行所有的不增加目标文件大小的-O2优化选项,并且执行专门减小目标文件大小的优化选项。适用于磁盘空间紧张时使用。但有可能有未知的问题发生,况且目前硬盘容量很大,常用程序无必要使用。
-O3: 打开所有 -O2 的优化选项外增加 -finline-functions、-funswitch-loops、-fgcse-after-reload 优化选项。相对于 -O2 性能并未有较多提高,编译时间也最长,生成的目标文件也更大更占内存,有时性能不增反而降低,甚至产生不可预知的问题(包括错误),所以并不被大多数软件安装推荐,除非有绝对把握方可使用此优化级别。
修改GCC编译参数,提高编译优化级别,此方法适用于所有通过GCC编译安装的程序,不止Nginx。稳妥起见用 -O2,这也是大多数软件编译推荐的优化级别。查看Nginx源码文件 auto/cc/gcc,搜索NGX_GCC_OPT,默认GCC编译参数为-O,可以直接修改内容为NGX_GCC_OPT="-O2"或者在 ./configure配置时添加--with-cc-opt='-O2'选项。

二.配置

应用服务器的性能优化主要在合理使用CPU、内存、磁盘IO和网络IO四个方面,现在我们从Nginx配置文件 nginx.conf 入手进行优化:

(1) 工作进程数的选择

指令:worker_processes
定义了Nginx对外提供web服务时的工作进程数。最优值取决于许多因素,包括(但不限于)CPU核心的数量、存储数据的硬盘数量及负载模式。不能确定的时候,将其设置为可用的CPU内核数将是一个好的开始(设置为“auto”将尝试自动检测它)。Shell执行命令 ps ax | grep "nginx: worker process" | grep -v "grep" 可以看到运行中的Nginx工作进程数,一般建议设置成服务器逻辑核心数,Shell执行命令 cat /proc/cpuinfo | grep processor | wc -l 可以检测出服务器逻辑核心总数,偷懒可以直接写auto,Nginx自适应。

(2) 是否绑定CPU

指令:worker_cpu_affinity
绑定工作进程到对应CPU核心,Nginx默认未开启CPU绑定。目前的服务器一般为多核CPU,当并发很大时,服务器各个CPU的使用率可能出现严重不均衡的局面,这时候可以考虑使用CPU绑定,以达到CPU使用率相对均匀的状态,充分发挥多核CPU的优势。top、htop等程序可以查看所有CPU核心的使用率状况。绑定样例:

worker_processes    4;
worker_cpu_affinity 0001 0010 0100 1000;

(3) 打开文件数限制

指令:worker_rlimit_nofile
设定了每个Nginx工作进程打开的最大文件数,受限于系统的用户进程打开文件数限制,未设置则使用系统默认值。理论上应该设置为当前Shell启动进程的最大打开文件数除以Nginx的工作进程数。由于Nginx的工作进程打开文件数并不一完全均匀,所以可以将其设置成Shell启动进程的最大打开文件数。Shell执行命令 ulimit -n 可以查看当前登录Shell会话最大打开文件数数限制。Linux系统用户进程默认同时打开文件最大数为1024,这个值太小,访问量稍大就报“too many open files"。Shell执行命令先修改用户打开文件数限制:

echo "* - nofile 65536" >> /etc/security/limits.conf

然后添加入/etc/profile如下两行内容,修改所有Shell和通过Shell启动的进程打开文件数限制:

echo "ulimit -n 65536" >> /etc/profile

Shell执行命令使当前Shell临时会话立即生效:

ulimit -n 65536

(4) 惊群问题

指令:accept_mutex
如果 accept_mutex 指令值为 on 启用,那么将轮流唤醒一个工作进程接收处理新的连接,其余工作进程继续保持睡眠;如果值为 off 关闭,那么将唤醒所有工作进程,由系统通过use指令指定的网络IO模型调度决定由哪个工作进程处理,未接收到连接请求的工作进程继续保持睡眠,这就是所谓的“惊群问题”。Web服务器Apache的进程数很多,成百上千也是时有的事,“惊群问题”也尤为明显。Nginx为了稳定,参数值保守的设置为 on 开启状态。可以将其设置成Off 提高性能和吞吐量,但这样也会带来上下文切换增多或者负载升高等等其它资源更多消耗的后果。

(5) 网络IO模型

指令:use
定义了Nginx设置用于复用客户端线程的轮询方法(也可称多路复用网络IO模型)。这自然是选择效率更高的优先,Linux 2.6+内核推荐使用epoll,FreeBSD推荐使用kqueue,安装时Nginx会自动选择。

(6) 连接数

指令:worker_connections
定义了Nginx一个工作进程的最大同时连接数,不仅限于客户端连接,包括了和后端被代理服务器等其他的连接。官网文档还指出了该参数值不能超过 worker_rlimit_nofile 值,所以建议设置成和 worker_rlimit_nofile 值相等。

(7) 打开文件缓存

指令:open_file_cache
开启关闭打开文件缓存,默认值 off 关闭,强烈建议开启,可以避免重新打开同一文件带来的系统开销,节省响应时间。如需开启必须后接参数 max=数字,设置缓存元素的最大数量。当缓存溢出时,使用LRU(最近最少使用)算法删除缓存中的元素;可选参数 inactive=时间 设置超时,在这段时间内缓存元素如果没有被访问,将从缓存中删除。示例:open_file_cache max=65536 inactive=60s。
指令:open_file_cache_valid
设置检查open_file_cache缓存的元素的时间间隔。
指令:open_file_cache_min_uses
设置在由open_file_cache指令的inactive参数配置的超时时间内, 文件应该被访问的最小次数。如果访问次数大于等于此值,文件描述符会保留在缓存中,否则从缓存中删除。

(8) 日志相关

指令:access_log 和 error_log
当并发很大时,Nginx的访问日志和错误日志的保存肯定会造成对磁盘的大量读写,也将影响Nginx的性能。并发量越大,IO越高。这时候可以考虑关闭访问日志和错误日志,或者将日志保存到tmpfs文件系统里,或者减少保存的访问日志条目和错误日志的级别,从而避免磁盘IO的影响。关闭日志使用 access_log off。如必须保存日志,可以按每日或者每时或者其它时间段对日志做切割,这也可以减小IO,虽然可能效果不是特别大,不过因为日志文件尺寸变小了很多,也方便查阅或归档分析日志。一般线上环境建议错误日志设置为 error 或者 crit。自定义访问日志的条目和错误日志的级别,详细信息可以参阅官网或者网上其它文档,按需修改。

(9) 隐藏Nginx版本号

指令:server_tokens
开启或关闭“Server”响应头中输出的Nginx版本号。推介设置为 off,关闭显示响应头的版本号,对性能的提高有小小的裨益,主要还是为了安全起见,不被骇客找到版本号对应的漏洞,从而被攻击。

(10) 压缩相关

指令:gzip
Nginx默认开启了gzip压缩功能。有可能很多人认为,开启gzip压缩会增加CPU的处理时间和负载。但是经过我们网站的测试发现,关闭了gzip压缩功能的Nginx虽然减少了CPU计算,节省了服务器的响应时间,但网站页面总体响应时间反而加长了,原因在于js和css、xml、json、html等等这些静态文件的数据传输时间的增长大大超过了服务器节省出来的响应时间,得不偿失。gzip on 开启压缩后,大约可以减少75%的文件尺寸,不但节省了比较多的带宽流量,也提高了页面的整体响应时间。所有建议还是开启。当然也不是所有的静态文件都需要压缩,比如静态图片和PDF、视频,文件本身就应当做压缩处理后保存到服务器。这些文件再次使用gzip压缩,压缩的比例并不高,甚至适得其反,压缩后文件尺寸增大了。CPU压缩处理这些静态文件增加占用的服务器响应时间绝大部分时候会超过了被压缩减小的文件尺寸减少的数据传输时间,不划算。是否需要对Web网站开启压缩,以及对哪些文件过滤压缩,大家可以通过使用HttpWatch、Firebug等等网络分析工具对比测试。
指令:gzip_comp_level
指定压缩等级,其值从1到9,数字越大,压缩率越高,越消耗CPU,负载也越高。9等级无疑压缩率最高,压缩后的文件尺寸也最小,但也是最耗CPU资源,负载最高,速度最慢的,这对于用户访问有时是无法忍受的。一般推荐使用1-4等级,比较折衷的方案。我们公司网站使用等级2。
指令:gzip_min_length
指定压缩的文件最小尺寸,单位 bytes 字节,低于该值的不压缩,超过该值的将被压缩。我们网站设置为1k,太小的文件没必要压缩,压缩过小尺寸文件带来增加的CPU消耗时间和压缩减少的文件尺寸降低的数据下载时间互相抵消,并有可能增加总体的响应时间。
指令:gzip_types
指定允许压缩的文件类型,Nginx配置目录 conf 下的 mime.types 文件存放了Nginx支持的文件类型,text/html类型文件,文件后缀为html htm shtml默认压缩。推荐配置:gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript。

(11) 浏览器缓存

指令:expires
设置HTTP应答中的“Expires”和“Cache-Control”头标。"Expires"一般结合"Last-Modified"使用。当设置了合理的expires配置时,浏览器第一次访问Web页面元素,会下载页面中的的静态文件到本机临时缓存目录下。第二次及之后再次访问相同URL时将发送带头标识"If-Modified-Since"和本地缓存文件时间属性值的请求给服务器,服务器比对服务器本地文件时间属性值,如果未修改,服务器直接返回http 304状态码,浏览器直接调用本地已缓存的文件;如果时间属性值修改了,重新发送新文件。这样就避免了从服务器再次传送文件内容,减小了服务器压力,节省了带宽,同时也提高了用户访问速度,一举三得。指令后接数字加时间单位,即为缓存过期时间;-1 表示永远过期,不缓存。强烈建议添加expires配置,过期时间的选择具体分析。我们公司的部分Nginx配置如下:

location ~ .+\.(gif|jpg|jpeg|png|bmp|swf)$
{
    expires 30d;
}

location ~ .+\.(js|css|xml|javascript|txt|csv)$
{
    expires 30d;
}

或者统一将静态文件放在固定目录下再对目录做location和expires,示例:

location /static/
{
    expires 30d;
}

(12) 持久连接

指令:keepalive_timeout
启用Http的持久连接Keepalive属性,复用之前已建立的TCP连接接收请求、发送回应,减少重新建立TCP连接的资源时间开销。在此的建议是当网站页面内容以静态为主时,开启持久连接;若主要是动态网页,且不能被转化为静态页面,则关闭持久连接。后接数字和时间单位符号。正数为开启持久连接,0关闭。

(13) 减少HTTP请求次数

网站页面中存在大量的图片、脚本、样式表、Flash等静态元素,减少访问请求次数最大的优点就是减少用户首次访问页面的加载时间。可以采用合并相同类型文件为一个文件的办法减少请求次数。这其实属于Web前端优化范畴,应当由Web前段工程师做好相关静态文件的规划管理,而不是由运维来做。不过Nginx也可以通过安装阿里巴巴提供的Concat或者Google的PageSpeed模块实现这个合并文件的功能。我们公司并未使用合并功能,具体安装配置信息请查询网上相关文档,这里不再累述。Concat源代码网址:https://github.com/alibaba/nginx-http-concat/,PageSpeed源代码网址:https://github.com/pagespeed/ngx_pagespeed

(14) PHP相关

Nginx不能直接解析PHP代码文件,需要调用FastCGI接口转给PHP解释器执行,然后将结果返回给Nginx。PHP优化本文暂不介绍。Nginx可以开启FastCGI的缓存功能,从而提高性能。
指令:fastcgi_temp_path
定义FastCGI缓存文件保存临时路径。
指令:fastcgi_cache_path
定义FastCGI缓存文件保存路径和缓存的其它参数。缓存数据以二进制数据文件形式存储,缓存文件名和key都是通过对访问URL使用MD5计算获得的结果。缓存文件先保存至fastcgi_temp_path指定的临时目录下,然后通过重命名操作移至fastcgi_cache_path指定的缓存目录。levels指定了目录结构,子目录数以16为基数;keys_zone指定了共享内存区名和大小,用于保存缓存key和数据信息;inactive指定了缓存数据保存的时间,当这段时间内未被访问,将被移出;max_size指定了缓存使用的最大磁盘空间,超过容量时将最近最少使用数据删除。建议fastcgi_temp_path和fastcgi_cache_path设为同一分区,同分区移动操作效率更高。示例:

fastcgi_temp_path /tmp/fastcgi_temp;
fastcgi_cache_path /tmp/fastcgi_cache levels=1:2 keys_zone=cache_fastcgi:16m inactive=30m max_size=1g;

示例中使用/tmp/fastcgi_temp作为FastCGI缓存的临时目录;/tmp/fastcgi_cache作为FastCGI缓存保存的最终目录;一级子目录为16的一次方16个,二级子目录为16的2次方256个;共享内存区名为cache_fastcgi,占用内存128MB;缓存过期时间为30分钟;缓存数据保存于磁盘的最大空间大小为1GB。
指令:fastcgi_cache_key
定义FastCGI缓存关键字。启用FastCGI缓存必须加上这个配置,不然访问所有PHP的请求都为访问第一个PHP文件URL的结果。
指令:fastcgi_cache_valid
为指定的Http状态码指定缓存时间。
指令:fastcgi_cache_min_uses
指定经过多少次请求相同的URL将被缓存。
指令:fastcgi_cache_use_stale
指定当连接FastCGI服务器发生错误时,哪些情况使用过期数据回应。
指令:fastcgi_cache
缓存使用哪个共享内存区。
我常用nginx.conf模板,大家根据情况做适当修改:

user  nginx nginx;
worker_processes  auto;

error_log  logs/error.log error;

pid        logs/nginx.pid;
worker_rlimit_nofile    65536;

events
{
    use epoll;
    accept_mutex off;
    worker_connections  65536;
}

http
{
    include       mime.types;
    default_type  text/html;

    charset UTF-8;
    server_names_hash_bucket_size   128;
    client_header_buffer_size       4k;
    large_client_header_buffers  4  32k;
    client_max_body_size            8m;

    open_file_cache max=65536  inactive=60s;
    open_file_cache_valid      80s;
    open_file_cache_min_uses   1;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile    on;
    server_tokens off;

    fastcgi_temp_path  /tmp/fastcgi_temp;
    fastcgi_cache_path /tmp/fastcgi_cache levels=1:2 keys_zone=cache_fastcgi:128m inactive=30m max_size=1g;
    fastcgi_cache_key $request_method://$host$request_uri;
    fastcgi_cache_valid 200 302 1h;
    fastcgi_cache_valid 301     1d;
    fastcgi_cache_valid any     1m;
    fastcgi_cache_min_uses 1;
    fastcgi_cache_use_stale error timeout http_500 http_503 invalid_header;

    keepalive_timeout  60;

    gzip  on;
    gzip_min_length 1k;
    gzip_buffers  4 64k;
    gzip_http_version   1.1;
    gzip_comp_level 2;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    server
    {
        listen       80;
        server_name  localhost;
        index        index.html;
        root         /App/web;

        location ~ .+\.(php|php5)$
        {
            fastcgi_pass   unix:/tmp/php.sock;
            fastcgi_index  index.php;
            include        fastcgi.conf;
            fastcgi_cache  cache_fastcgi;
        }

        location ~ .+\.(gif|jpg|jpeg|png|bmp|swf|txt|csv|doc|docx|xls|xlsx|ppt|pptx|flv)$
        {
            expires 30d;
        }

        location ~ .+\.(js|css|html|xml)$
        {
            expires 30d;
        }

        location /nginx-status
        {
            stub_status on;
            allow 192.168.1.0/24;
            allow 127.0.0.1;
            deny all;
        }
    }
}

三.内核

Linux内核参数部分默认值不适合高并发,一般临时方法可以通过调整/Proc文件系统,或者直接修改/etc/sysctl.conf配置文件永久保存。调整/Proc文件系统,系统重启后还原至默认值,所以不推荐。Linux内核调优,主要涉及到网络和文件系统、内存等的优化,下面是我常用的内核调优配置:

grep -q "net.ipv4.tcp_max_tw_buckets" /etc/sysctl.conf || cat >> /etc/sysctl.conf << EOF
########################################
net.core.rmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_default = 262144
net.core.wmem_max = 16777216
net.core.somaxconn = 262144
net.core.netdev_max_backlog = 262144
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_max_tw_buckets = 10000
net.ipv4.ip_local_port_range = 1024 65500
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_mem = 786432 1048576 1572864
fs.aio-max-nr = 1048576
fs.file-max = 6815744
kernel.sem = 250 32000 100 128
vm.swappiness = 10
EOF
sysctl -p

详细说明大家可以查看我的Linux内核优化文章:https://www.moyann.com/archives/130/

四.架构

Nginx的最大优势在于处理静态文件和代理转发功能,支持7层负载均衡和故障隔离。 动静分离是每个网站发展到一定规模之后必然的结果。静态请求则应当最好将其拆分,并启用独立的域名,既便于管理的需要,也便于今后能够快速支持CDN。如果一台Nginx性能无法满足,则可以考虑在Nginx前端添加LVS负载均衡,或者F5等硬件负载均衡(费用昂贵,适合土豪公司单位),由多台Nginx共同分担网站请求。还可以考虑结合Varnish或者Squid缓存静态文件实现类似CDN功能。新版Nginx目前已经支持直接读写Memcache,可以编译安装时候选择添加此类模块,从而节省了转交给PHP或者JPS等动态程序服务器处理时间,提高效率的同时,减小了动态服务器的负载。

最后修改:2019 年 08 月 29 日
如果觉得我的文章对你有用,请随意赞赏