# Nginx 执行流程
# Nginx 启动
# Nginx 启动、退出时的回调方法
- init_module 在 master 进程中调用
- init_process 在 worker 进程中调用
- exit_process 在 worker 进程退出时调用
- exit_master 在 master 进程退出时调用
# Nginx 的启动流程
- 根据命令行得到配置文件路径
- 如果处于升级中则监听环境变量里传递的监听句柄
- 调用所有核心模块的 create_conf 方法生成存放配置项的描述体
- 针对所有核心模块解析 nginx.conf 配置文件
- 调用所有核心模块的 init_conf 方法
- 创建目录、打开文件、初始化共享内存等进程间通信方式
- 打开由各 Nginx 模块从配置文件中读取到的监听端口
- 调用所有模块的 init_module 方法(检测 Nginx 的运行方式,单进程还是 master 多进程)
- 一般会以 master 多进程方式运行 Nginx,进入 master 模式
- 启动 master 进程
- 根据 worker_process 启动 worker 进程,调用所有模块的 init_process 方法
- 启动 cache manager 进程
- 启动 cache loader 子进程,关闭父进程启动时监听的端口
# HTTP 请求处理的 11 个阶段
HTTP 请求处理的阶段为:POST_READ、SERVER_REWRITE、FIND_CONFIG、REWRITE、POST_REWRITE、PREACCESS、ACCESS、POST_ACCESS、PRECONTENT、CONTENT、LOG
# 1、postread 阶段
这个阶段中,获取真实客户端地址的 realip 模块会生效。
# realip 模块
默认不会编译进 Nginx,需要通过 --with-http_realip_module
启用功能,作用是修改客户端的地址。
因为模块覆盖了 $remote_addr
,为了能得到原来的 ip 信息,模块创建了两个变量:
- realip_remote_addr
- realip_remote_port
模块有三个指令:
- set_real_ip_from:决定什么 ip 地址才是可信的,能替换
$remote_addr
变量 - real_ip_header:从哪个头字段中获取替换的 ip
- real_ip_recursive:环回地址,如果 X-Forwarded-For 中的最后一个 ip 是本地 ip,就获取上一个 ip。
# 网络中存在许多反向代理的情况下,如何拿到真实的用户 IP 地址?
例如用户的地址是:122.3.3.2,经过许多反向代理后,$remote_addr
可能就变成了 8.8.8.8
- HTTP 头部 X-Forwarded-For 用于传递 IP,它可以设置多个 IP,用逗号分割。
- HTTP 头部 X-Real-IP 用于传递用户 IP,它只能设置一个 IP
根据 relip 模块的指令,relip 模块可以通过这些 HTTP 头部字段覆写入 binary_remote_addr、remote_addr 变量,使变量的值成为真实的用户 IP。并且只有拿到真实用户的 IP 后做连接限制(如 preaccess 阶段生效的 limit_conn 模块)才有意义。
# 2、server_rewrite 和 rewrite 阶段
server_rewrite 和 rewrite 这两个阶段中,rewrite 模块都会生效。
# return 指令
return 指令生效后,后面的指令都不会再执行。
return 指令后面可以是 code [text]
、code URL
、URL
。要特别注意一下使用上下文,没有 http,只能用在 server,location,if
上下文中。
# code 返回状态码
Nginx 自定义
- 444 关闭连接,此状态码不会返回到客户端
HTTP 1.0 标准
- 301 http1.0 永久重定向,客户端再次访问时会直接访问新地址
- 302 临时重定向,禁止被缓存
HTTP 1.1 标准
- 303 临时重定向,允许改变方法,禁止被缓存
- 307 临时重定向,不允许改变方法,禁止被缓存
- 308 永久重定向,不允许改变方法
如果是
return URL;
,此时状态码默认是 302。
# server 与 location 块下的 return 指令关系?
server {
server_name return.example.com;
listen 8080;
root html/;
reutrn 403;
location / {
return 404 "find nothing!";
}
}
return 403;
是在 server_rewrite 阶段,return 404 "find nothing!";
是在 rewrite 阶段,server_rewrite 阶段先处理,return 指令生效后,后面的指令都不会再执行,并且 return 指令是动作类指令,不会发生继承合并,所以上面的配置会返回 403。
# return 与 error_page 指令的关系?
server {
server_name return.example.com;
listen 8080;
root html/;
error_page 404 /403.html;
location / {
return 404 "find nothing!\n";
}
}
上面的配置会返回状态码 404,返回的内容是 find nothing!,error_page 不会生效。
# rewrite 指令
使用反向代理解决跨域时,用到了 rewrite 指令。rewrite 模块提供出来的指令还包括 set、if、break、return。
rewrite 是脚本类型的指令。语法是 rewrite regex replacement [flag];
,当 replacement 以 http://或者 https://或者$schema 开头,则直接返回 302 重定向。
rewrite 利用正则表达式和标志位实现 uri 重写和重定向,正则表达式可以用小括号进行变量提取。rewrite 只能放在 server、location 上下文和 if 判断中,并且只能对域名后边的除去传参外的字符串起作用。如果想对域名或参数字符串起作用,可以使用全局变量匹配,也可以使用 proxy_pass 反向代理。
例如 http://microloan-sms-platform.yxapp.xyz/proxy/sms/task/querydeleted?page=1&pagesize=10
只能对 /proxy/sms/task/querydeleted 进行重写。
rewrite 规则后边,通常会带有 flag 标志位:
- last:表示持续,用 replacement 这个 URI 进行新的 location 匹配
- break:停止当前脚本指令的执行,等价与独立的 break 指令
- redirect:返回 302 临时重定向,地址栏会显示跳转后的地址
- permanent:返回 301 永久重定向,地址栏会显示跳转后的地址
last 和 break 的区别:
- last 一般写在 server 和 if 中,而 break 一般使用在 location 中
- last 不终止重写后的 uri 匹配,即新的 uri 会再从 server 走一遍匹配流程,而 break 终止重写后的匹配
- break 和 last 都能阻止继续执行当前上下文后面的 rewrite 指令。
# rewrite 和 return 的关系?
不带 flag 的 rewrite,会跟 return 顺序执行,并以 return 结果为准。如果带了 last 会跳过后面的 return 执行,如果带了 break ,会阻止后面的 return 执行。
# 3、find_config 阶段
此阶段主要是 location 匹配,可以查看 location 配置
# 4、preaccess 阶段
# limit_conn 模块
模块的功能是限制每个客户端的并发连接数,默认编译进 Nginx,生效范围:
- 全部 worker 进程(基于共享内存)
- 进入 preaccess 阶段前不生效
- 并发连接数限制的有效性取决于 key 的设计:一般使用客户端的 ip,所以就需要依赖 postread 阶段的 realip 模块取到真实的 ip
# limit_conn_zone 指令
定义共享内存(包括大小),以及 key 关键字。
语法 limit_conn_zone key zone=name:size;
只能在 http 上下文中使用
# limit_conn 指令
限制并发连接数。
语法 limit_conn zone number;
可以用在 http,server,location 上下文中。
# limit_conn_log_level 指令
限制发生时的日志级别,可选的值是 info|notice|warn|error,默认是 error。
# limit_conn_status 指令
限制发生时向客户端返回的错误码,默认是 503。
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
server_name limit.example.com;
root html/
error_log logs/error.log info;
location / {
limit_conn_status 500; #超限后的状态码
limit_conn_log_level warn;
limit_conn addr 1;
limit_rate 50; #限制速度
}
}
# limit_req 模块
功能是限制每个客户端的每秒处理请求数,默认编译进 Nginx,生效算法是 leaky bucket 算法(漏桶接水把不同的流量转换成均匀的流量),生效范围也是全部 worker 进程(基于共享内存),进入 preaccess 阶段前不生效。
# limit_req_zone 指令
定义共享内存(包括大小),以及 key 关键字和限制速率,语法 limit_req_zone key zone=name:size rate=rate;
,只能在 http 上下文中使用。rate 的单位为 r/s(每秒请求)或者 r/m(每分钟请求)。
# limit_req 指令
限制并发连接数,语法 limit_req zone=name [burst=number] [nodelay];
,burst 默认为 0(即漏桶的容量),nodelay,对 burst 中的请求不再采用延时处理的做法,而是立即处理。
# limit_req_log_level 指令
限制发生时的日志级别
# limit_req_status 指令
限制发生时向客户端返回的错误码,默认为 503。
# limit_req 与 limit_conn 配置同时生效时,哪个有效?
limit_req 生效时间在 limit_conn 之前,所以只会看到 limit_req 的配置结果。
# 5、access 阶段
# access 模块
限制某些 IP 地址的访问,默认编译进 Nginx,生效范围是:进入 access 之前不生效。
# allow 指令
允许哪些地址访问,语法 allow address|CIDR|unix:|all;
,可以用在 http,server,location,limit_except 上下文中
# deny 指令
禁止哪些地址访问,语法 deny address|CIDR|unix:|all;
,可以用在 http,server,location,limit_except 上下文中
location / {
deny 192.168.1.1;
allow 192.168.1.0/24;
allow 2001:0db8::/32;
deny all;
}
# auth_basic 模块
基于 HTTP Basic Authutication 协议进行用户名密码的认证,模块默认编译进 Nginx。
# auth_basic 指令
可以设置 string|off,默认为 off
# auth_basic_user_file
用户名和密码配置在哪个文件中。
生成密码文件可以使用生成工具 htpassed,安装依赖包 httpd-tools,然后执行 htpasswd -c file -b user pass
# auth_request 模块(第三方模块)
原理是收到请求后,先把请求 hold,然后生成子请求,通过反向代理技术把请求传递给上游服务。向上游的服务转发请求,若上游服务返回的响应码是 2xx,则继续执行,若上游服务返回的是 401 或 403,则将响应返回给客户端。默认没有编译进 Nginx。
# auth_request 指令
语法 auth_request uri | off;
,默认为 off。
# auth_request_set 指令
语法 auth_request_set $variable value;
可以根据上游返回的变量来设置新的变量。
# satisfy 指令
限制所有 access 阶段模块,例如 access 模块、auth_basic 模块、auth_request 模块等,语法 satisfy all | any;
,如果是 all 就表示必须所有配置的 access 模块都通过才能使 access 阶段放行,any 只要有一个 access 模块的配置通过就能在 access 阶段放行。
# precontent 阶段
# try_files 指令
指令来自 try_files 模块,可以用在 server,location 上下文中,语法是:
try_files file... uri;
try_files file... =code
try_files 的作用是依次试图访问多个 url 对应的文件(由 root 或者 alias 指令指定),当文件存在时直接返回文件内容,如果所有文件都不存在,则按最后一个 uri 结果或者状态码 code 返回。
# mirror 模块
处理请求时,生成子请求访问其他服务(copy 一份流量),对子请求的返回值不做处理。模块默认编译进 Nginx。
# mirror 指令
可以设置 uri | off ,默认为 off。
# mirror_request_body
指定是否需要把请求中的 body 转发给上游服务,默认为 on。
# content 阶段
# root 指令
将 url 映射为文件路径,以返回静态文件内容。默认值为 root html
,可以用在 http,server,location,if in location 上下文中。
root 会将完整 url 映射进文件路径中。
location /root {
root html;
}
访问:example.com/root/1.txt 文件路径为:html/root/1.txt
# alias 指令
将 url 映射为文件路径,以返回静态文件内容。没有默认值,只能用在 location 上下文中。
alias 只会将 location 匹配后的 url 映射到文件路径
location /alias {
alias html;
}
访问:example.com/alias/1.txt 文件路径为:html/1.txt
# root 或 alias 配置中,访问目录时 URL 最后没有带 /
?
static 模块实现了 root/alias 功能时,发现访问目标是目录,但 URL 末尾未加 /
时,会返回 301 重定向,自动加上 /
。
关于重定向后的跳转问题,可以根据 absolute_redirect、port_in_redirect、server_name_in_redirect 来进行配置。
# content 阶段生成待访问文件的三个相关变量
- request_filename 待访问文件的完整路径,包括文件名和扩展名
- document_root 由 URI 和 root/alias 规则生成的文件夹路径
- realpath_root 将 document_root 中的软链接等换成真实路径
location /RealPath/ {
alias html/realpath/ # 这是一个软链接指向的是 first 文件夹
}
访问 /RealPath/1.txt 时
- request_filename 为 html/realpath/1.txt
- document_root 为 html/realpath
- realpath_root 为软链接指向的真实路径 html/first
# 静态文件返回时的 content-tpe 相关
- types 指令,语法
types {...};
,对文件扩展名和文件类型的映射 - default_type 指令,语法
default_type mime-type;
默认为 text/plain
# index 模块
指定访问时返回 index 文件内容,语法 index file...;
默认是 index index.html;
。index 是优先于 auto_index 处理的。
# auto_index 模块
当 URL 以 /
结尾时,尝试以 html/xml/json/jsonp 等格式返回 root/alias 中指向目录的文件。默认编译进了 Nginx。
# log 阶段
log 模块就是把 HTTP 请求相关信息记录到日志中,并且 ngx_http_log_module 无法禁用。
# log_format 指令
语法 log_format name [escape=default|json|none] string ...;
默认是 log_format combined "...";
默认的 combined 日志格式:log_format combined '$remote_addr - $remote_user [$time_local]' '"$request" $status $body_bytes_sent' '"$http_referer""$http_user_agent"';
# access_log 指令
语法:access_log off;
或 access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]]
默认值为 access_log logs/access.log combined;
- path 路径可以包含变量:不打开 cache 时每记录一条日志都需要打开、关闭日志文件,可以通过 open_log_file_cache 对日志文件名包含变量时进行优化
- if 通过变量值控制请求日志是否记录
- 日志缓存
- 功能 批量将内存中的日志写入磁盘
- 写入磁盘的条件
- 所有待写入磁盘的日志大小超出缓存大小,即 buffer 的大小
- 达到 flush 指定的过期时间
- worker 进程执行 reopen 命令,或者正在关闭
- 日志压缩
- 功能 批量压缩内存中的日志,在写入磁盘
- buffer 大小默认为 64KB
- 压缩级别默认为 1(1 最快压缩率最低,9 最慢压缩率最高)
# HTTP 过滤相关的模块处理过程
Nginx 的过滤模块就是对 HTTP 请求进行加工处理的。
接收 HTTP 头部后
会先经过 preaccess 阶段的处理
- 首先是 limit_req 模块
- 其次是 limit_conn 模块
然后经过 access 阶段,access 相关模块会进行处理
然后经过 content 阶段,access pass 后的内容,会先经过 concat 模块处理,然后是 static 模块处理
static 模块处理后的内容,就进入了响应阶段,会经过 header 过滤模块,先经过 image_filter 模块,然后经过 gzip 模块,此时会发送 HTTP 头部
然后就进入到响应 body 的处理中,同样的 image_filter 先处理,然后 gzip 后处理,处理完成后就可以发送 HTTP 响应包体了。
响应的包体会通过以下模块加工响应内容
- copy_filter 复制包体内容,必须在 gzip 之前
- postpone_filter 处理子请求
- header_filter 构造响应头部,例如会添加 server,Nginx 版本号等等
- write_filter 发送响应
← Nginx 进阶