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