二、Nginx核心原理
本节介绍Nginx的基础配置,包括事件模型配置、虚拟主机配置、错误页面配置、长连接配置、访问日志配置等。然后,本节还会介绍在配置过程中可能会使用到的Nginx内置变量。
2.1、events事件驱动配置
一个典型的events事件模型配置块的示例如下:
events {
use epoll; #使用epoll类型IO多路复用模型
worker_connections 204800; #最大连接数限制为20万
accept_mutex on; #各个Worker通过锁来获取新连接
}
2.1.1、worker_connections指令
worker_connections指令用于配置每个Worker进程能够打开的最大并发连接数量,指令参数为连接数的上线。
2.1.2、use指令
use指令用户配置IO多路复用模型,有多种模型可配置,常用的有select
、epoll
两种。
Linux系统下,select类型IO多路复用模型有两个较大的缺陷:缺陷之一,单服务进程并发数不够
,默认最大的客户端连接数为1024/2048,因为Linux系统一个进程所打开的FD文件描述符是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数被相应限制了;缺陷之二,性能问题
,每次IO事件查询都会线性扫描全部的FD集合,连接数越大,性能越会线性下降。总之,select类型IO多路复用模型的性能是不高的。
使用Nginx的目标之一是为了高性能和高并发。所以,在Linux系统下建议使用epoll类型的IO多路复用模型。epoll模型是在Linux 2.6内核中实现的,是select系统调用的增强版本。epoll模型中有专门的IO就绪队列,不再像select模型一样进行全体连接扫描,时间复杂度从select模型的O(n)下降到了O(1)。在IO事件的查询效率上,无论上百万连接还是数十个连接,对于epoll模型而言差距是不大的;而对select模型而言,效率的差距就非常巨大了。
select、epoll都是常见的IO多路复用模型。本质上都是查询多个FD描述符,一旦某个描述符的IO事件就绪(一般是读就绪或者写就绪),就进行相应的读写操作,而且都是在读写事件就绪后,应用程序自己负责进行读写。所以,select、epoll本质上都是同步I/O,因为它们的读写过程是阻塞的。虽然不是异步I/O,但是通过合理的设计,epoll类型的IO多路复用模型的性能还是非常高,足以应对目前的高并发处理要求。
如果没有配置IO多路复用模型,在Windows平台下,Nginx默认的IO多路复用模型为select。这一点可以通过设置errors_log的日志级别为debug,打开日志文件可以看出来,具体如下:
... [notice] 3928#18648: using the "select" event method
... [notice] 3928#18648: openresty/1.13.6.2
至于Nginx在Linux平台上默认的事件驱动模型,大家可按照统一的方法自行实验。
2.1.3、accept_mutex指令
accept_mutex指令用于配置各个Worker进程是否通过互斥锁有序接收新的连接请求。on参数表示各个Worker通过互斥锁有序接收新请求;off参数指每个新请求到达时会通知(唤醒)所有的Worker进程参与争抢,但只有一个进程可获得连接。
配置off参数会造成“惊群”问题
影响性能。accept_mutex指令的参数默认为on。
2.2、虚拟主机配置
配置虚拟主机可使用server指令。虚拟主机的基础配置包含套接字配置、虚拟主机名称配置等。
2.2.1、虚拟主机的监听套接字配置
虚拟主机的监听套接字配置使用listen指令,具体的配置有多种形式,分别说明如下:
- 使用listen指令直接配置监听端口:
server {
listen 80;
...
}
- 使用listen指令配置监听的IP和端口
server {
listen 127.0.0.1:80;
...
}
2.2.2、虚拟主机名称配置
虚拟主机名称配置可使用server_name指令。基于微服务架构的分布式平台有很多类型的服务,比如文件服务、后台服务、基础服务等。很多情况下,可以通过域名前缀的方式进行URL路径区分:
#后台管理服务虚拟主机demo
server {
listen 80;
server_name admin.crazydemo.com; #后台管理服务的域名前缀为admin
location / {
default_type 'text/html';
charset utf-8;
echo "this is admin server";
}
}
#文件服务虚拟主机demo
server {
listen 80;
server_name file.crazydemo.com; #文件服务的域名前缀为file
location / {
default_type 'text/html';
charset utf-8;
echo "this is file server";
}
}
#默认服务虚拟主机demo
server {
listen 80 default;
server_name crazydemo.com *.crazydemo.com; #如果没有前缀,这就是默认访问的虚拟主机
location / {
default_type 'text/html';
charset utf-8;
echo "this is default server";
}
}
当然,客户端需要能够通过域名服务器或者本地的hosts文件解析出域名所对应的服务器IP,HTTP请求才能最终到达Nginx服务器。故为了访问上面配置的3个虚拟主机,修改Windows系统本地的hosts文件,加上以下几条映射规则:
127.0.0.1 crazydemo.com #基础服务域名
127.0.0.1 file.crazydemo.com #文件服务域名
127.0.0.1 admin.crazydemo.com #后台管理服务域名
127.0.0.1 xxx.crazydemo.com #XXX服务
重启Nginx,在浏览器中访问http://admin.crazydemo.com/,返回的内容如图:
多个虚拟主机之间,匹配优先级从高到低大致如下:
- 字符串精确匹配:如果请求的域名为admin.crazydemo.com,那么首先会匹配到名称为admin.crazydemo.com的虚拟管理主机。
- 左侧通配符匹配:若浏览器请求的域名为xxx.crazydemo.com,则会匹配到.crazydemo.com虚拟主机。为什么呢?因为配置文件中并没有server_name为xxx.crazydemo.com的主机,所以退而求其次,名称为*.crazydemo.com的虚拟主机按照通配符规则匹配成功。
- 右侧通配符匹配:右侧通配符和左侧通配符匹配类似,只不过优先级低于左侧通配符匹配。
- 正则表达式匹配:与通配符匹配类似,不过优先级更低。
- default_server:在listen指令后面如果带有default的指令
参数,就代表这是默认的、最后兜底的虚拟主机,如果前面的匹配规则
都没有命中,就只能命中default_server指定的默认主机。
2.3、错误页面配置
错误页面的配置指令为error_page,格式如下:
error_page code ... [=[response]] uri;
code表示响应码,可以同时配置多个;uri表示错误页面,一般为服务器上的静态资源页面。
例如,下面的例子分别为404、500等错误码设置了错误页面,具体设置如下:
#后台管理服务器demo
server {
listen 80;
server_name admin.crazydemo.com;
root /var/www/;
location / {
default_type 'text/html';
charset utf-8;
echo "this is admin server";
}
#设置错误页面
error_page 404 /404.html;
#设置错误页面
error_page 500 502 503 504 /50x.html;
}
为了防止404页面被劫持,也就是被前面的代理服务器换掉,则可以修改响应状态码,参考如下:
error_page 404 =200 /404.html #防止404页面被劫持
error_page指令除了可用于server上下文外,还可用于http、server、location、if in location等上下文。
2.4、长连接相关配置
配置长连接的有效时长可使用keepalive_timeout指令,格式如下:
keepalive_timeout timeout [header_timeout];
配置项中的timeout参数用于设置保持连接超时时长,0表示禁止长连接,默认为75秒
。
如果要配置长连接的一条连接允许的最大请求数,那么可以使用keepalive_requests指令,格式如下:
keepalive_requests number;
配置项中的number参数用于设置在一条长连接上允许被请求的资源的最大数量,默认为100。
如果要配置向客户端发送响应报文的超时限制,那么可以使用下面的指令:
send_timeout time;
配置项中的time参数用于设置Nginx向客户端发送响应报文的超时限制,此处时长是指两次向客户端写操作之间的间隔时长,并非整个响应过程的传输时长。
2.4、访问日志配置
Nginx将客户端的访问日志信息记录到指定的日志文件中,用于后期分析用户的浏览行为等,此功能由ngx_http_log_module
模块负责,其指令在HTTP处理流程的log阶段执行。访问记录配置指令的完整格式如下:
access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
其中,path表示日志文件的本地路径
;format表示日志输出的格式名称
。定义日志输出格式的配置指令为log_format,它的完整格式如下:
log_format name string ...;
其中,name参数用于指定格式名称;string参数用于设置格式字符串,可以有多个。字符串中可以使用Nginx核心模块及其他模块的内置变量。
下面是一个比较完整的例子:
http {
#先定义日志格式,format_main是日志格式的名字
log_format format_main '$remote_addr - $remote_user [$time_local] request - ' ' $status - $body_bytes_sent [$http_referer]
#配置:日志文件、访问日志格式
access_log logs/access_main.log format_main;
...
}
修改配置后,需要重启Nginx。然后在浏览器中访问http://crazydemo.com/demo/hello,在
access_main.log文件中可以看到一条新增的日志记录:
127.0.0.1 - - [12/Jan/2020:18:32:28 +0800] GET /demo/hello HTTP/1.1 - 200 - 32 [-] [Mozilla/5.0 (Windows NT 10.0; Win64; x64; r
接下来,对以上实例中所有用到的Nginx内置变量进行简单说明,具体如下:
- $request:记录用户的HTTP请求的起始行信息。
- $status:记录HTTP状态码,即请求返回的状态,例如200、404、502等。
- $remote_addr:记录访问网站的客户端地址。
- $remote_user:记录远程客户端用户名称。
- $time_local:记录访问时间与时区。
- $body_bytes_sent:记录服务器发送给客户端的响应body字节数。
- $http_referer:记录此次请求是从哪个链接访问过来的,可以根据其进行盗链的监测。
- $http_user_agent:记录客户端访问信息,如浏览器、手机客户端等。
- $http_x_forwarded_for:当前端有正向代理服务器时,此参数用于保持客户端真实的IP地址。该参数生效的前提:前端的代理服务器上进行了相关的x_forwarded_for设置。
2.5、Nginx核心模块内置变量
Nginx核心模块ngx_http_core_module中定义了一系列存储HTTP请求信息的变量,例如http_user_agent、http_cookie等。这些内置变量在Nginx配置过程中使用较多,故对其进行介绍,具体如下:
- arg_PARAMETER:请求URL中以PARAMETER为名称的参数值。请求参数即URL的“?”号后面的name=value形式的参数对,变量arg_name得到的值为value。另外,arg_PARAMETER中的参数名称不区分字母大小写,例如通过变量arg_name不仅可以匹配name参数,也可以匹配NAME、Name请求参数,Nginx会在匹配参数名之前自动把原始请求中的参数名调整为全部小写的形式。
- args:请求URL中的整个参数串,其作用与query_string相同。
- $binary_remote_addr:二进制形式的客户端地址。
- $body_bytes_sent:传输给客户端的字节数,响应头不计算在内。
- $bytes_sent:传输给客户端的字节数,包括响应头和响应体。
- content_length:等同于http_content_length,用于获取请求体body的大小,指的是Nginx从客户端收到的请求头中Content-Length字段的值,不是发送给客户端响应中的Content-Length字段值,如果需要获取响应中的Content-Length字段值,就使用$sent_http_content_length变量。
- request_length:请求的字节数(包括请求行、请求头和请求体)。注意,由于request_length是请求解析过程中不断累加的,如果解析请求时出现异常,那么$request_length是已经累加部分的长度,并不是Nginx从客户端收到的完整请求的总字节数(包括请求行、请求头、请求体)。
- $connection:TCP连接的序列号。
- $connection_requests:TCP连接当前的请求数量。
- $content_type:请求中的Content-Type请求头字段值。
- $cookie_name:请求中名称name的cookie值。
- $document_root:当前请求的文档根目录或别名。
- uri:当前请求中的URI(不带请求参数,参数位于args变量)。$uri变量值不包含主机名,如“/foo/bar.html”。此参数可以修改,可以通过内部重定向。
- $request_uri:包含客户端请求参数的原始URI,不包含主机名,此参数不可以修改,例如“/foo/bar.html?name=value”。
- $host:请求的主机名。优先级为:HTTP请求行的主机名>HOST请求头字段>符合请求的服务器名。
- http_name:名称为name的请求头的值。如果实际请求头name中包含中画线“-”,那么需要将中画线“-”替换为下画线“_”;如果实际请求头name中包含大写字母,那么可以替换为小写字母。例如获取Accept-Language请求头的值,变量名称为http_accept_language。
- $msec:当前的UNIX时间戳。UNIX时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
- $nginx_version:获取Nginx版本。
- $pid:获取Worker工作进程的PID。
- $proxy_protocol_addr:代理访问服务器的客户端地址,如果是直接访问,那么该值为空字符串。
- $realpath_root:当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径。
- $remote_addr:客户端请求地址。
- $remote_port:客户端请求端口。
- $request_body:客户端请求主体。此变量可在location中使用,将请求主体通过proxy_pass、fastcgi_pass、uwsgi_pass和scgi_pass传递给下一级的代理服务器。
- $request_completion:如果请求成功,那么值为OK;如果请求未完成或者请求不是一个范围请求的最后一部分,那么值为空。
- $request_filename:当前请求的文件路径,由root或alias指令与URI请求结合生成。
- $request_length:请求的长度,包括请求的地址、HTTP请求头和请求主体。
- $request_method:HTTP请求方法,比如GET或POST等。
- $request_time:处理客户端请求使用的时间,从读取客户端的第一个字节开始计时。
- $scheme:请求使用的Web协议,如HTTP或HTTPS。
- sent_http_name:设置任意名称为name的HTTP响应头字段。例如,如果需要设置响应头Content-Length,那么将“-”替换为下画线,大写字母替换为小写字母,变量为sent_http_content_length。
- $server_addr:服务器端地址为了避免访问操作系统内核,应将IP地址提前设置在配置文件中。
- $server_name:虚拟主机的服务器名,如crazydemo.com。
- $server_port:虚拟主机的服务器端口。
- $server_protocol:服务器的HTTP版本,通常为HTTP/1.0或HTTP/1.1。
- $status:HTTP响应代码。