2.2 Nginx详解
本节主要讲解Nginx服务器的核心内容,其中包含Nginx的文件的目录结构、命令行参数、配置文件、底层依赖库和Nginx内部的工作原理,最后分享一些常用的Nginx优化建议,供读者参考。
2.2.1 Nginx文件的目录结构
Nginx内部文件不仅包含可执行文件,还包含大量配置项、依赖库和运行日志文件。我们可以使用nginx-V指令查看Nginx内部文件对应的目录位置,这里以Mac系统为例。
$ nginx -V nginx version: nginx/1.17.1 built by clang 10.0.1 (clang-1001.0.46.4) built with OpenSSL 1.0.2s 28 May 2019 TLS SNI support enabled configure arguments: --prefix=/usr/local/Cellar/nginx/1.17.1 --sbin- path=/usr/local/Cellar/nginx/1.17.1/bin/nginx --with-cc-opt='- I/usr/local/opt/pcre/include -I/usr/local/opt/openssl/include' -- with-ld-opt='-L/usr/local/opt/pcre/lib -L/usr/local/opt/openssl/lib' --conf-path=/usr/local/etc/nginx/nginx.conf --pid- path=/usr/local/var/run/nginx.pid --lock- path=/usr/local/var/run/nginx.lock --http-client-body-temp- path=/usr/local/var/run/nginx/client_body_temp --http-proxy-temp- path=/usr/local/var/run/nginx/proxy_temp --http-fastcgi-temp- path=/usr/local/var/run/nginx/fastcgi_temp --http-uwsgi-temp- path=/usr/local/var/run/nginx/uwsgi_temp --http-scgi-temp- path=/usr/local/var/run/nginx/scgi_temp --http-log- path=/usr/local/var/log/nginx/access.log --error-log- path=/usr/local/var/log/nginx/error.log --with-compat --with-debug - -with-http_addition_module --with-http_auth_request_module --with- http_dav_module ...
下面根据指令输出顺序,对Nginx中的一些重要目录进行讲解。
·--prefix:Nginx的安装目录如果用户使用源码编译安装Nginx,可以自定义安装目录,默认安装在/usr/local/Cellar/nginx/1.17.1/下(Homebrew安装)。Nginx的安装目录如下。
. ├── CHANGES # Nginx各版本之间变更信息 ├── INSTALL_RECEIPT.json # Homwbrew安装工具的自带信息 ├── LICENS ├── README # 帮助信息 ├── bin # 可执行文件目录 ├── html -> ../../../var/www # Nginx默认根目录位置 └── share # Nginx帮助文档
·--sbin-path:Nginx可执行文件存放的位置,在Nginx安装目录的bin子目录下。
·--conf-path:Nginx默认配置文件存放的位置,默认在/usr/local/etc/nginx/下,结构如下。
. ├── fastcgi.conf # fastcgi相关参数的配置文件 ├── fastcgi.conf.default # fastcgi.conf的原始备份 ├── fastcgi_params # fastcgi的参数文件 ├── fastcgi_params.default # fastcgi_params的原始备份 ├── koi-utf # 编码转换映射文件 ├── koi-win # 编码转换映射文件 ├── mime.types # 媒体类型文件 ├── mime.types.default # 媒体类型文件的原始备份 ├── nginx.conf # Nginx默认的主配置文件 ├── nginx.conf.default # nginx.conf的原始备份 ├── scgi_params # scgi相关参数文件,一般用不到 ├── scgi_params.default # scgi_params的原始备份 ├── servers ├── uwsgi_params # uwsgi相关参数文件,一般用不到 ├── uwsgi_params.default # uwsgi_params的原始备份 └── win-utf # 编码转换映射文件
·--with-ld-opt:Nginx运行时加载的库路径,默认包含两个库:OpenSSL库和PCRE库,目录结构如下。
├── ... ├── openssl -> ../Cellar/openssl/1.0.2s ├── ... ├── pcre -> ../Cellar/pcre/8.43 ├── ...
·--pid-path:运行Nginx进程对应的文件,在重启Nginx服务时会使用到,默认在/usr/local/var/run/目录下,文件名为nginx.pid,用户也可以自定义文件名。
·--http-log-path、--error-log-path:Nginx日志文件存在的位置,默认在/usr/local/var/log/nginx/目录下,对应的日志名称分别为access.log与error.log。修改Nginx配置文件可以修改日志文件的存放路径和文件名称。
2.2.2 命令行参数
/prefix/bin目录中存放了Nginx的可执行文件,我们可以用它对Nginx服务器执行启停、重启、校验配置文件等操作。下面我们详细看一下Nginx提供了哪些指令及具体的使用方法。
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives] Options: -?,-h : this help -v : show version and exit -V : show version and configure options then exit -t : test configuration and exit -T : test configuration, dump it and exit -q : suppress non-error messages during configuration testing -s signal : send signal to a master process: stop, quit, reopen, reload -p prefix : set prefix path (default: /usr/share/nginx/) -c filename : set configuration file (default: /etc/nginx/nginx.conf) -g directives : set global directives out of configuration file
·-?,-h:打印命令行参数帮助信息。
·-c filename:使用用户自定义配置文件filename启动Nginx服务,默认配置文件为/etc/nginx/nginx.conf。
·-g directives:设置全局配置,不使用配置文件,例如:
nginx -g "pid /var/run/nginx.pid; worker_processes`sysctl -n hw.ncpu`;"
·-p:设置Nginx路径前缀,默认路径为/usr/local/nginx。
·-q:在验证配置文件时不打印非错误信息。
·-s signal:向Nginx主进程发送信号,可以包含以下参数。
·stop:快速关闭Nginx服务器,可能会造成正在处理的请求发生异常。
·quit:优雅关闭Nginx服务器。
·reload:使用新的配置项重启Nginx worker进程,系统会优雅地关闭旧的worker进程。
·reopen:重新打开日志文件。
·-t:验证配置文件是否符合Nginx配置文件语法规范,同时尝试打开配置文件中引用的文件。
·-T:与-t参数类似,在验证时会将配置文件信息打印到标准输出。
·-v:打印Nginx版本信息。
·-V:打印Nginx版本信息,包含编译器版本和配置参数。
注意
在生产环境中,Nginx服务器有多种管理方式:可以通过自身命令行指令管理;也可以通过系统管理工具管理,如Systemd;还可以借助Docker容器管理。用户可以根据实际使用情况选用合适的管理方式。
2.2.3 配置文件
nginx.conf为Nginx服务器的核心配置文件。Nginx会根据配置文件中指定的配置项启动,默认配置文件为/usr/local/etc/nginx/nginx.conf。用户也可以自定义配置项,使用-c参数指定配置文件。代码清单2-1为nginx.conf配置文件示例,读者可以通过它了解Nginx配置文件的详情。
程序清单2-1 nginx.conf配置文件
1 # 以Nginx进程运行的用户 2 user nginx; 3 # Nginx工作的进程数量,默认自动配置,可配置成CPU数 4 worker_processes auto; 5 # Nginx的错误日志位置 6 error_log /var/log/nginx/error.log; 7 # Nginx进程运行后的进程id文件 8 pid /run/nginx.pid; 9 # 包含模块文件;*.conf表示所有以.conf结尾的文件 10 include /usr/share/nginx/modules/*.conf; 11 events { # events块开始 12 # 一个worker进程的最大连接数 13 worker_connections 1024; 14 15 } # events块结束 16 17 http { # http块开始 18 # Nginx日志格式 19 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 20 '$status $body_bytes_sent "$http_referer" ' 21 '"$http_user_agent" "$http_x_forwarded_for"'; 22 # Nginx access_log日志文件位置 23 access_log /var/log/nginx/access.log main; 24 # 设置允许以sendfile方式传输文件 25 sendfile on; 26 # 防止网络阻塞 27 tcp_nopush on; 28 # 在TCP协议中,使用Nagle算法,把小包组成大包提高带宽利用率 29 tcp_nodelay on; 30 # 服务端对连接保持的时间,默认是65秒 31 keepalive_timeout 65; 32 # 设置size类型哈希表的最大值 33 types_hash_max_size 2048; 34 # 包含资源类型文件 35 include /etc/nginx/mime.types; 36 # 定义响应的默认MIME类型 37 default_type application/octet-stream; 38 # 引入其他的配置文件,文件名必须以.conf结尾 39 include /etc/nginx/conf.d/*.conf; 40 server { 41 # 监听的端口号,写法一 42 listen 80 default_server; 43 # 监听的端口号,写法二 44 listen [::]:80 default_server; 45 # 对外提供的虚拟主机名称,可以理解为域名;_表示无效域名之一,也可以使用"--"和"!@#" 46 server_name _; 47 # 请求的根目录位置 48 root /usr/share/nginx/html; 49 # 注释信息 50 51 # 引入其他的配置文件,文件名必须以.conf结尾 52 include /etc/nginx/default.d/*.conf; 53 location / { 54 } 55 # 定义将为指定错误显示的URI,返回状态码为404。一个URI值可以包含变量 56 error_page 404 /404.html; 57 location = /40XX.html { 58 } 59 # 定义将为指定错误显示的URI,返回状态码为500或者502、503、504。一个URI值可以包含变量 60 error_page 500 502 503 504 /50x.html; 61 location = /50x.html; # location块开始,精准匹配uri 62 } # location块结束 63 } # server块结束 64 # server块开始 65 server { 66 # 监听端口,ssl表示允许此端口接收的所有连接在SSL模式下工作,http2表示配置端口接收HTTP2 连接 67 listen 443 ssl http2 default_server; 68 # 监听端口的另一种写法 69 listen [::]:443 ssl http2 default_server; 70 # 对外提供的虚拟主机名称,可以理解为域名;_表示无效域名之一,也可以使用"--"和"!@#" 71 server_name _; 72 # 请求的根目录位置 73 root /usr/share/nginx/html; 74 # 指定带有PEM格式证书的文件位置 75 ssl_certificate "/etc/pki/nginx/server.crt"; 76 # 指定带有PEM格式的密钥文件位置 77 ssl_certificate_key "/etc/pki/nginx/private/server.key"; 78 # 设置存储会话参数缓存的类型和大小。Shared表示所有工作进程之间共享的缓存 79 ssl_session_cache shared:SSL:1m; 80 # 指定客户端可以重用会话参数的时间 81 ssl_session_timeout 10m; 82 # 返回客户端支持的密码列表 83 ssl_ciphers HIGH:!aNULL:!MD5; 84 85 ssl_prefer_server_ciphers on; 86 # 引入其他配置文件,文件名必须以.conf结尾 87 include /etc/nginx/default.d/*.conf; 88 89 # location块开始 90 location / { 91 # location块结束 92 } 93 # 定义将为指定错误显示的URI 94 error_page 404 /404.html; 95 # location块开始,精准匹配URI 96 location = /40XX.html { 97 # location块结束 98 } 99 # 定义将为指定错误显示的URI 100 error_page 500 502 503 504 /50x.html; 101 # location块开始,精准匹配URI 102 location = /50x.html { 103 # location块结束 104 } 105 # server块结束 106 } 107 }
上述配置文件仅包含一些常用的Nginx配置项,更多配置读者可以参考Nginx官网的Directives章节查询。
2.2.4 依赖库
PCRE和OpenSSL为Nginx默认依赖的库。除此之外,Nginx中还可以添加Zlib库,实现压缩功能。我们这里简要介绍一下这些库。
1.PCRE库
PCRE(Perl Compatible Regular Expression)库是一组函数,这些函数使用与Perl 5相同的语法和语义来实现正则表达式模式匹配。在Nginx中,PCRE库与location块结合得比较紧密,可以用来匹配大量静态资源、配置防盗链、禁止爬虫等。以下为防盗链配置示例。
server { ... location ~* \.(jpg|gif|png|swf|flv|wma|wmv|asf|mp3|mmf|zip|rar)$ { valid_referers none blocked http://www.xxx.com/*; if ($invalid_referer) { return 404; } } ... }
注意
Nginx中的location块可以包含多种正则表达式语句。正则表达式语法大致如下。
·~:匹配指定资源文件,区分大小写;
·~*:匹配指定资源文件,不区分大小写;
·!~:不匹配指定资源文件,区分大小写;
·!~*:不匹配指定资源文件,不区分大小写;
·^:匹配以指定内容开头的资源文件;
·$:匹配以指定内容结尾的资源文件;
·\:转义字符;
·*:匹配任意字符。
2.OpenSSL库
OpenSSL整个软件包大概可以分成三个部分:SSL协议库、应用程序以及密码算法库。作为一个基于密码学的安全开发包,OpenSSL提供的功能相当强大和全面,囊括了主要的密码算法、常用的密钥、证书封装管理功能以及SSL协议,并提供了丰富的应用程序。在Nginx中,其对应的模块为ngx_http_ssl_module和ngx_mail_ssl_module。常见的使用方式是基于ngx_http_ssl_module模块实现对站点的访问,示例如下。
server { ... ssl_certificate cert.pem; ssl_certificate_key cert.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ... }
该配置示例基于ngx_http_ssl_module模块指定了cert.pem与cert.key的文件位置。ssl_ciphers用来选择加密套件。它们必须是OpenSSL能够识别的。且多个加密套件之间使用“!”分隔。“!”表示从算法列表中删除指定加密算法,如!MD5表示排除MD5算法。
3.Zlib库
Zlib是通用的压缩库,由Jean-loup Gailly和Mark Adler开发。其提供了一套完整的压缩和解压缩函数,并可以检测解压后的数据的完整性。Zlib同时支持读写gzip(.gz)格式的文件。在Nginx中,ngx_http_gzip_module和ngx_http_gzip_static_module模块使用到了Zlib库。它主要用于对http包内的内容或静态文件进行压缩,减少网络传输量。gzip on指令能直接开启压缩模式。
http { ... gzip on; gzip_comp_level 5; gzip_min_length 20k; ... }
该配置示例基于ngx_http_gzip_module模块开启了gzip压缩模式,并设置压缩的级别为5,原始文件小于20KB时不执行压缩操作。
2.2.5 Nginx的工作原理
图2-2展示了Nginx的工作原理。Nginx启动后,会有一个master进程和多个worker进程。master进程与worker进程之间是通过信号进行交流的。管理员通过发送指令或者信号的形式,来告诉master进程应该执行什么操作,最终master进程会把信号发送给worker进程,由worker进程来处理。所有的客户端都会连接worker进程。一个worker进程可以对应多个客户端,但是多个worker进程不会对应同一个客户端。
图2-2 Nginx的工作原理
master进程:主要用来管理worker进程、接收来自客户端的信号,向worker进程发送信号,监控worker进程的运行状态。当worker进程在异常情况下退出后,Nginx会自动重新启动新的worker进程。master进程充当整个进程组与客户端的交互接口,同时对worker进程进行监控。它不需要处理网络事件,不负责业务的执行,只会通过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。我们要想控制Nginx,只需要通过kill指令向master进程发送信号就可以。比如kill-HUP pid指令是告诉Nginx从容地重启。我们一般用这个信号来重启Nginx或重新加载配置,因此服务是不中断的。master进程在接到信号后,会先重新加载配置文件,然后再启动新的worker进程,并向所有旧的worker进程发送信号,告诉它们可以退出了。新的worker进程在启动后,开始接收新的请求,而旧的worker进程在收到来自master进程的信号后,就不再接收新的请求,并且处理完当前进程中的所有未处理完的请求后再退出。
worker进程:主要用来处理基本的网络事件。多个worker进程之间是对等的。它们同等竞争来自客户端的请求。各worker进程之间是独立的。一个worker进程不可能处理其他worker进程中的请求。worker进程之间是平等的,每个进程处理请求的机会也是一样的。每个worker进程都是从master进程派生而来的。在master进程中先建立好需要监听的socket(listenfd)之后,然后再派生出多个worker进程。所有worker进程的listenfd都会在新连接到来时变为可读。为保证只有一个worker进程处理该连接,所有worker进程在注册listenfd读事件前抢互斥锁accept_mutex,抢到互斥锁的worker进程注册listenfd读事件,在读事件里调用accept方法接收该连接。worker进程在调用accept方法接收连接之后,就开始读取、解析、处理请求,产生数据后再返给客户端,最后才断开连接。
2.2.6 Nginx优化指南
在对Nginx进行调优时,有一条比较好的准则,就是一次只修改一个配置项,然后进行验证,如果修改之后没有性能上的显著提升,就退回初始值。我们主要从Linux配置和Nginx配置两大方面讨论常见的Nginx优化策略。
1.Linux配置
Linux配置项有很多,这里我们仅讨论普通工作负载下最可能需要优化的配置项。
(1)文件描述符
文件描述符是一种操作系统资源,用来处理诸如连接和打开文件的操作。对于每一个连接,Nginx可以使用两个文件描述符。例如,如果Nginx作为代理服务器,其中一个文件描述符用于连接客户端,另一个用于连接被代理的服务器。如果启用了HTTP Keepalive,连接描述符的使用会少很多。对于有大量连接的系统,可能需要调整如下配置项。
·sys.fs.file_max:系统范围内的文件描述符限制;
·nofile:用户级别的文件描述符,在/etc/security/limits.conf文件中配置。
(2)临时端口
如果Nginx作为代理服务器,每一个到上游服务器的连接都会使用一个临时端口。
·net.ipv4.ip_local_port_range:用来指定可以使用的端口号范围,如果用户发现端口耗尽,可以增大该值范围,常设置为1024到65000。
·net.ipv4.tcp_fin_timeout:用来指定一个不被使用的端口多久之后可以被另一个连接再次使用,默认值为60秒,可以减小到30秒或15秒。
2.Nginx配置
这里推荐的Nginx配置项适合大多数用户自行调整。其他未提及的配置项,如果用户没有很大的把握,一般不推荐自行修改。
(1)woker_process
Nginx可以运行多个worker进程,每个worker进程都能处理大量连接。用户可以修改以下配置项来控制worker进程的个数和连接处理方式。
·worker_processes:用来设置Nginx的worker进程的个数。大多数情况下,一个CPU核心对应一个worker进程。用户可以将这个值设置为auto。当worker进程需要处理大量磁盘I/O操作时,我们可以适当增大该值,默认值为1。
·worker_connections:表示每个worker进程能够同时处理的最大连接数,默认值是512。该值的大小取决于服务器硬件配置以及流量的特性。
(2)keepalive
建立持久化连接(keepalive)可以减小打开和关闭连接所需要的CPU和网络开销,因而对性能提升有重大影响。Nginx支持客户端和上游服务器的持久化连接。以下配置项涉及客户端持久化连接。
·keepalive_requests:表示客户端能在单个持久化连接上发送多少请求,默认值为100。用户可以将其设置为更高的值。
·keepalive_tiomeout:表示一个空闲持久化连接能保持打开状态的时间。
以下配置项涉及上游服务器持久化连接:
·keepalive:表示每个worker进程连接到上游服务器的空闲持久化连接数量。
为了开启上游服务器持久化连接,我们还需要添加如下配置:
proxy_http_version 1.1; proxy_set_header Connection "";
(3)access_log
记录每个请求都需要花费CPU和I/O资源。减少这种影响的一种方法是启用access_log,这将导致Nginx缓冲一系列日志条目,然后一次性写入文件而不是单个写入。通过buf fer=size选项设置要使用的缓冲区的大小。通过f lush=time选项告诉Nginx多长时间后把缓冲区中的条目写入文件。定义了这两个选项后,当缓冲区放不下下一条日志,或者缓冲区中的条目容量超过了buf fer参数指定的大小,Nginx就会将缓冲区中的条目写入日志文件。当worker进程重新打开或者关闭日志文件时,缓冲区中的条目也会被写入文件。
(4)sendfile
sendfile是一个操作系统特性,也可以在Nginx上启用。它通过在内核中从一个文件描述符向另一个文件描述符复制数据(可达到零复制),提供更快的TCP数据传输。Nginx可以使用该机制将缓存或者磁盘上的内容写到socket,无须从内核空间到用户空间的上下文切换,因而数据传输非常快并且只使用较少的CPU。由于数据内容不会存放到用户空间,因此在处理过程中不能使用任何需要改变数据内容的Nginx过滤器,比如gzip过滤器。Nginx默认没有启用该机制。
注意
零复制可以理解为是一种避免系统将数据从一块存储复制到另外一块内存存储的技术。针对操作系统中的设备驱动程序、文件系统以及网络协议堆栈而出现的各种零复制技术极大地提升了特定应用程序的性能,并且使得这些应用程序可以更加有效地利用系统资源。这种性能的提升就是通过在数据复制的同时,允许CPU执行其他的任务来实现的。零复制技术可以减少数据复制和共享总线操作的次数,消除传输数据在存储器之间不必要的中间复制次数,从而有效地提高数据传输效率。
3.其他配置
除此之外,我们还能启用Nginx缓存或者压缩响应减小响应的大小和带宽占用等方法来提升Nginx的性能。关于更多调优方法,用户可以查看Nginx官网。