# 路由转发
当 Nginx 在某个 TCP 端口收到一个 HTTP 请求时,会交给监听该端口的 server 处理。
- 如果监听该端口的 server 有多个,则考虑请求头中的 Host 参数与哪个 server 监听的 server_name 匹配。
- 如果没有匹配的 server_name ,则交给监听该端口的默认 server 处理。
当 server 收到请求时,会从多个地方查找与请求 URI 匹配的资源,作出响应报文。包括:
location proxy_pass index autoindex
如果前一个资源不存在,则查找后一个资源。如果都没找到匹配 URI 的资源,则返回响应报文:
404 Not Found
# 关于路由
# listen
:声明 server 监听的 IP:Port 。
- 可用范围:server
- 例:
listen 80; # 相当于 listen 0.0.0.0:80 listen 8080-8090; # 可以监听一组端口 listen 127.0.0.1:80; # 监听 80 端口,并且只允许被本地 IP 127.0.0.1 访问 listen unix:/var/run/nginx.sock;
- 一个 server 可以同时声明多个 listen 指令。
- 如果有多个 server 监听同一个端口,则第一个定义的 server 是默认 server 。也可以主动指定:
listen 80 default_server;
# server_name
:声明 server 监听的域名。
- 可用范围:server
- server_name 有以下几种格式,排在前面的优先匹配:
server_name www.test.com localhost; # 完全匹配,区分大小写 server_name *.test.com; # 以 *. 开头,后缀匹配 server_name www.test.*; # 以 .* 结尾,前缀匹配 server_name ~^(?<www>.+)\.test\.com$; # 正则表达式 server_name ""; # 空字符串,相当于不填 server_name ,不会匹配任何域名
- 一个 server 可以声明多行 server_name 指令,每行可声明多个域名。
- Nginx 不会检查 server_name 的域名是否有效,是否能被 DNS 解析。
- server_name 允许以
*.
开头或.*
结尾,实现模糊匹配,可匹配任意个字段。- 例如
server_name *.test.com
匹配www.test.com
、1.www.test.com
,不匹配test.com
。 - 不允许在其它位置使用通配符 * ,例如
server_name www.*.com
、server_name test*.com
是无效的。
- 例如
- 如果有两个 server 监听的端口、域名都相同,则启动 Nginx 时会报错:
conflicting server name
- Nginx 的所有 server_name 存储在一个哈希表里,由多个 bucket 组成,并尽量减少 bucket 数。相关配置:
http{ server_names_hash_bucket_size 64; # bucket 的大小。如果声明了特别长的域名,则 Nginx 会报错说该值不足,需要 x2 倍 server_names_hash_max_size 512; # 域名哈希表的最大大小。如果声明了大量域名,则 Nginx 会报错说该值不足,需要 x2 倍 }
# alias
:用于修改请求 URI 的前缀,覆盖 root 指令的效果。
- 可用范围:location
- 例:
root /usr/share/nginx/html; location /www/ { alias /static/img/; }
- 上例中,如果没有 alias 指令,则请求 /www/1.jpg 时,会尝试获取 /usr/share/nginx/html/1.jpg 文件,即最终文件路径为
$root/$uri
。 - 有了 alias 指令,则请求 /www/1.jpg 时,会尝试获取 /static/img/1.jpg 文件,即最终文件路径为
$alias/$uri
。 - 一个 location 区块中不能同时存在 location、alias 指令,否则 Nginx 会报错。
- 上例中,如果没有 alias 指令,则请求 /www/1.jpg 时,会尝试获取 /usr/share/nginx/html/1.jpg 文件,即最终文件路径为
- 支持正则替换:
location ~ ^/www/(.+\.(gif|jpe?g|png))$ { alias /static/img/$1; }
# location
:用于定义 URI 路由规则。
可用范围:server、location
- location 语句可以嵌套到 location 内,但不支持写在 if 语句内。
语法:
location [type] URI { ... }
- 匹配类型 type 有以下几种,优先级从高到低如下:
=
:字符串精确匹配,即匹配与该字符串完全相同的 URI 。^~
:字符串前缀匹配,即匹配以该字符串开头的 URI 。- 如果某个 URI 同时匹配多个前缀匹配规则,则采用前缀最长的那个规则。
~
:与部分字符串的正则匹配。- 如果某个 URI 同时匹配多个正则 location ,则采用第一个匹配的 location 。
- 如果 location URI 中包含 Nginx 的保留字符,比如 { } ,则需要加上双引号作为定界符。
~*
:不区分大小写的正则匹配。(其它匹配类型都区分大小写,除非是 Cygwin 等操作系统)- 不指定 type 时,匹配类型相当于
^~
,但优先级最低。
- 如果有两个 location 的 type、URI 都相同,则启动 Nginx 时会报错:
duplicate location
。
- 匹配类型 type 有以下几种,优先级从高到低如下:
例:
location /img/ { root /www/; }
- 服务器收到指向
http://127.0.0.1/img/1.jpg
的请求时,会匹配该 location ,于是尝试获取文件 /www/img/1.jpg ,通过 HTTP 200 响应报文传输给客户端。如果不存在该文件,则返回 HTTP 404 响应报文。 - 服务器收到指向
http://127.0.0.1/img
的请求时,不会匹配该 location 。如果也不匹配其它 location ,则返回 404 响应报文。- 特别地,如果该 location 区块内包含了 proxy_pass、fastcgi_pass、uwsgi_pass 等代理指令之一(即使没有被执行),则会匹配该 location ,返回一个 301 重定向报文,指向 /img/ 。
- 服务器收到指向
另一种语法:
location @name { ... }
- 这是将 location 命名,从而可以在其它位置调用。像一个 URI ,可以将请求内部重定向到它。
- 这种语法不支持嵌套:不能被 location 包含,也不能包含 location 。
- 例:
location / { root /usr/share/nginx/html; error_page 404 @test; } location @test { return 200 'Hello\n'; }
- 该例中把所有
@test
替换成/test
,效果一样。
- 该例中把所有
# root
:到指定目录下寻找路径与 URI 匹配的文件作出响应报文。
- 可用范围:http、server、location
- 默认值:
root html;
- 例:
location /img/ { root /www/; }
- 服务器收到指向 /img/1.jpg 的请求时,会尝试获取文件 /www/img/1.jpg ,通过 HTTP 200 响应报文传输给客户端。如果不存在该文件,则返回 HTTP 404 响应报文。
- 如果不用 root 用户启动 Nginx 主进程,比如用 nginx 用户,则需要给该用户分配对 root 目录的 r、x 权限。
# 关于重定向
# index
:如果请求的 URI 以 / 结尾,则通过内部重定向将请求转发到 $uri/index
。
- 可用范围:http、server、location
- 例:
index index.html index.htm;
- 可以定义多个文件,如果前一个文件不存在,则查找后一个文件。
# autoindex
:如果请求的 URI 以 / 结尾,则返回一个 HTML 页面,显示 URI 对应的磁盘目录的文件列表。
- 可用范围:http、server、location
- 默认值:
autoindex off;
# internal
:限制指定 location 只能被内部重定向的请求访问到。
- 可用范围:location
- 例:
location /index.html { internal; }
- 如果被外部请求直接访问,则返回 404 响应报文。
# rewrite
:如果请求 URI 中的部分字符串匹配表达式,则重写整个 URI 。
可用范围:server、location
语法:
rewrite regex replacement [flag];
flag 有多种取值:
- 不填 :先执行完 ngx_http_rewrite_module 模块的指令(不会执行其它指令),然后:
- 如果 replacement 以 http:// 或 https:// 开头,则返回 302 临时重定向报文,指向 replacement 。
- 如果 replacement 不以它们开头,则将请求回退到 find-config 阶段重新处理(属于内部重定向)。
- permanent :立即返回 301 永久重定向报文。
- redirect :立即返回 302 临时重定向报文。
- break :结束执行 ngx_http_rewrite_module 模块的指令,继续执行后续指令。
- last :结束执行 ngx_http_rewrite_module 模块的指令,将请求回退到 find-config 阶段重新处理。
- 此时总是会内部重定向,不会返回重定向报文。
- 不填 :先执行完 ngx_http_rewrite_module 模块的指令(不会执行其它指令),然后:
rewrite 指令的执行结果要么是将请求内部重定向,要么是返回一个外部重定向报文。
- 每个 HTTP 请求最多被内部重定向 10 次,超过该次数则返回响应报文:
500 Internal Server Error
- 当 server 接收一个 HTTP 请求时,会先解析出 request_uri 等变量的值,即使发生内部重定向也不会改变,除非转发到其它 server 。
- 每个 HTTP 请求最多被内部重定向 10 次,超过该次数则返回响应报文:
例:将 HTTP 请求重定向为 HTTPS 请求
server { listen 80; rewrite ^(.*)$ https://$host$1 permanent; # 可以使用正则替换 # return 301 https://$host$request_uri; # return 语句比 rewrite 更快 }
例:
location /www/ { rewrite /www/1.html /www/2.html; rewrite /3.html /4.html break; proxy_pass http://10.0.0.1:81/test/; }
- 请求 /www/1.html 时,会被内部重定向为 /www/2.html ,然后回退到 find-config 阶段,重新匹配 location ,最后转发到 http://10.0.0.1:81/test/2.html 。
- 请求 /www/3.html 时,会被内部重定向为 /4.html ,然后 break ,转发到 http://10.0.0.1:81/4.html 。
- 如果在
rewrite ... ... break;
之后使用 proxy_pass ,则会将 rewrite 重写之后的 URI 作为转发结果,忽略 proxy_pass 的目标 URI 。
- 如果在
# try_files
:尝试寻找多个位置的文件作为响应报文。
- 可用范围:server、location
- 语法:
try_files file ... uri; try_files file ... =code;
- 可以指定一个或多个 file 参数,寻找对应的文件作为响应报文。如果所有 file 都不存在,则返回最后一个参数对应的响应报文。
- file 参数只能是本机上有效的文件路径,不能指向 URL 。
- 最后一个参数可以是一个 uri 或 code 。
- 该命令属于内部重定向,不会改变客户端请求的 URL 。
- 只能使用本机文件作为响应,与 proxy_pass 组合使用则无效。
- 可以指定一个或多个 file 参数,寻找对应的文件作为响应报文。如果所有 file 都不存在,则返回最后一个参数对应的响应报文。
- 例:
try_files $uri $uri/index.html $uri.html /index.html; try_files $uri $uri/index.html $uri.html =404; # try_files $uri $uri/index.html $uri.html; # 这里最后一个路由也引用了 $uri ,如果故意访问一个不存在的 uri ,则会导致内部循环重定向
# error_page
:将指定状态码的响应报文内部重定向到某个 URI 。
- 可用范围:http、server、location
- 语法:
error_page code ... [=[response]] uri;
- 状态码 code 的取值范围为 300~599 。
- 例:
error_page 404 /404.html; error_page 401 =200 /login; # 401 时重定向到登录页面 error_page 500 502 503 504 /50x.html; # 可以同时指定多个状态码 location = /50x.html { root /usr/share/nginx/html; }
# absolute_redirect
:服务器返回重定向的响应报文时,重定向之后的地址是完整的 URL (即绝对路径,从 http 协议头开始),还是 URI (即相对路径)。
- 可用范围:http、server、location
- 默认值:
absolute_redirect on;
- 例如:如果启用该参数时,重定向报文的 Headers 中包含了
Location: http://localhost/index.html
,则禁用该参数时就会变成Location: /index.html
。
# server_name_in_redirect
:启用 absolute_redirect 时,重定向之后的 URL 中,服务器地址是否采用 Nginx 配置的 server_name 参数。
- 可用范围:http、server、location
- 默认值:
server_name_in_redirect on;
- 如果禁用该参数,则采用请求头中的 Host 字段,或者服务器的 IP 地址。
# port_in_redirect
:启用 absolute_redirect 时,重定向之后的 URL 中,服务器的端口是否采用 Nginx 配置的 listen 参数。
- 可用范围:http、server、location
- 默认值:
port_in_redirect on;
- 如果禁用该参数,则不注明端口号,即采用默认的 80 端口。
# 关于代理
# proxy_pass
:将请求转发给指定的服务器(称为 upstream ),实现反向代理。
可用范围:location
例:
server { listen 80; location / { proxy_pass http://127.0.0.1:81; # upstream 地址可以是 IP # proxy_pass http://host1.test.com; # upstream 地址可以是域名。Nginx 只会在 reload 时执行一次 DNS 查询,解析到 IP 并缓存使用,如果解析失败则报错 # proxy_pass http://unix:/tmp/backend.socket:/uri/; # upstream 地址可以是 Unix 套接字 # proxy_http_version 1.0; # 转发时的 HTTP 协议版本,默认为 1.0 # proxy_pass_request_body on; # 是否转发请求 body ,默认为 on # proxy_set_body $request_body; # 设置转发过去的请求 body # proxy_connect_timeout 60s; # 与 upstream 建立连接的超时时间 # proxy_send_timeout 60s; # 限制发送请求给 upstream 时,写操作中断的超时时间 # proxy_read_timeout 60s; # 限制从 upstream 读取响应时,读操作中断的超时时间 # 当 upstream 是 HTTPS 服务器时 # proxy_ssl_server_name off; # 与 upstream 进行 SSL 握手时是否发送 SNI 。当 upstream 有多个 SSL 证书时,需要启用 SNI ,否则可能握手失败 # proxy_ssl_name $proxy_host; # 与 upstream 进行 SSL 握手时,在 SNI 中发送的 Host 目标域名是什么 # proxy_ssl_session_reuse on; # 与 upstream 进行 SSL 握手之后,是否允许复用 SSL 会话 # proxy_ssl_verify off; # 是否检查 upstream 的 SSL 证书的有效性,默认为 false # proxy_ssl_trusted_certificate xxx; # 指定一个 pem 格式的 CA 证书文件,用于验证 upstream 的 SSL 证书 } } server { listen 79; location / { return 200 '$request_uri\n'; } }
如果 proxy_pass 目标地址中包含 URI ,则将原 URI 被 location 匹配之后剩下的部分附加到目标地址之后(即转发相对路径)。否则直接将原 URI 附加到目标地址之后(即转发绝对路径)。如下,测试发送指向
127.0.0.1:80/www/1.html
的请求:location /www/ { proxy_pass http://127.0.0.1:81; # 转发到 /www/1.html }
location /www { proxy_pass http://127.0.0.1:81/; # 转发到 //1.html }
location /www/ { proxy_pass http://127.0.0.1:81/; # 转发到 /1.html }
proxy_pass 支持调用变量:
- 如果 proxy_pass 目标地址中包含 URI 且调用了变量,则直接将它的值作为转发结果,不考虑原 URL 。
location /www/ { proxy_pass http://127.0.0.1:81$request_uri; # 转发到 /www/1.html }
- 如果将 proxy_pass 与正则表达式 或 if 语句一起使用,则目标地址不能包含 URI ,除非用变量决定 URI 。
location ~ ^/www/\d*.html { proxy_pass http://127.0.0.1:81/; # 不可行,启动 Nginx 时会报错 }
location ~ ^/www/(\d*.html) { proxy_pass http://127.0.0.1:81/$1; # 可行,转发到 /1.html }
- 如果 proxy_pass 调用了变量,则必须添加 resolver 指令,从而自动对 upstream 做 DNS 解析。如下:
location ~ ^/proxy/(.+) { proxy_pass http://$1/$args; proxy_set_header Host $1; resolver 8.8.8.8; # 指定一个 DNS 服务器 # resolver 8.8.8.8 valid=30s; # 进行一次 DNS 查询之后,默认会根据响应的 TTL 缓存查询结果。可用 valid 参数覆盖 TTL # resolver 8.8.8.8 10.0.0.1:53; # 可指定多个 DNS 服务器 }
- 如果 proxy_pass 没有调用变量,则会在 Nginx 启动时将名称形式的 upstream 解析成 IP 地址,然后缓存使用,不再更新。
- 因此 upstream 的 DNS 变化时需要重载 Nginx ,比较麻烦,不如添加 resolver 指令。
- 如果 proxy_pass 目标地址中包含 URI 且调用了变量,则直接将它的值作为转发结果,不考虑原 URL 。
反向代理多个 upstream 时,需要区分发向不同 upstream 的 HTTP 请求。
- 可以给多个 upstream 分配不同的子域名,根据 server_name 区分:
server { listen 80; server_name www.test.com; location / { proxy_pass http://127.0.0.1:81; } } server { listen 80; server_name img.test.com; location / { proxy_pass http://127.0.0.1:82; } }
- 也可以根据 location 匹配的 URI 区分:
location /www/ { proxy_pass http://127.0.0.1:81/; } location /img/ { proxy_pass http://127.0.0.1:82/; }
- 也可以根据 if 条件区分:
# 提取子域名 set $sub_domain ''; if ($http_host ~* "^(.*?)\.test\.com$") { set $sub_domain $1; } location / { if ($sub_domain = "www") { proxy_pass http://127.0.0.1:81/; } if ($sub_domain = "img") { proxy_pass http://127.0.0.1:82/; } }
- 可以给多个 upstream 分配不同的子域名,根据 server_name 区分:
# proxy_redirect
:对 upstream 发出的响应头中的 Location、Refresh 字段进行字符串替换。
- 可用范围:http、server、location
- 例:
location /www/ { proxy_pass http://127.0.0.1:81/; proxy_redirect default; # proxy_redirect http://localhost:80/ http://$host:$server_port/; # proxy_redirect http://localhost:(\d*)/ http://$host:$1/; # proxy_redirect / /; # proxy_redirect off; }
- 默认会启用
proxy_redirect default;
,其规则为proxy_redirect <proxy_pass_url> <location_url>;
。- 在上例中相当于
proxy_redirect http://127.0.0.1:81/ /www/;
。 - 如果 proxy_pass 中调用了变量,则默认规则会失效。
- 在上例中相当于
- 默认会启用
# proxy_set_header
:转发 HTTP 请求给 upstream 时,添加一个 Header 。
- 可用范围:http、server、location
- 默认值:
proxy_set_header Host $proxy_host; proxy_set_header Connection close; # 默认对 upstream 禁用 TCP 长连接
- 如果 proxy_set_header 设置的值是空字符串 '' ,则会删除该字段。
- 例:反向代理一个网站
location / { proxy_pass https://www.baidu.com; proxy_set_header Host $proxy_host; # 修改 HTTP 请求指向的目标域名 proxy_set_header X-Real-IP $remote_addr; # 记录客户端的真实 IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 按顺序记录该请求经过的每层代理服务器的 IP ,第一个地址是客户端的真实 IP proxy_set_header X-Forwarded-Proto $scheme; # 记录该请求与代理服务器之间,采用 http 还是 https 协议 }
- 反向代理 webSocket 时,需要添加以下配置:
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection Upgrade;
- if 语句块中可以使用 proxy_pass ,但不支持使用 proxy_set_header ,解决方案是使用临时变量:
# 如果 HTTP 请求的 Headers 中已存在 X-Real-IP 字段,则保留它。否则,将 remote_addr 赋值给 X-Real-IP 字段 set $x_real_ip $http_x_real_ip; if ($x_real_ip = "") { set $x_real_ip $remote_addr; } proxy_set_header X-Real-IP $x_real_ip; proxy_set_header Host $http_host;
- 以下变量只能被 proxy_set_header 指令调用:
proxy_host # proxy_pass 指令中配置的 upstream 地址 proxy_port proxy_add_x_forwarded_for # 取值等于请求头的 X-Forwarded-For 字段,加上 $remote_addr 值,用逗号分隔
- Nginx 的以下指令支持多次配置,但不支持部分继承。如果当前作用域没有配置该指令,则继承自上级作用域,否则不继承。
- proxy_set_header
- add_header
- limit_req
- limit_conn
# proxy_hide_header
:转发 upstream 的 HTTP 响应给客户端时,删除一个 Header 。
- 可用范围:http、server、location
- 默认值:会删除 Date、Server 等 Header 。
- 例:假设 upstream 的 HTTP 响应中存在 Access-Control-Allow-Origin 字段,nginx 想修改该字段的值,则可添加以下配置:如果只配置 add_header ,则不会覆盖 HTTP 响应中的同名 Header ,会产生两行重复的 Header 。
proxy_hide_header Access-Control-Allow-Origin; add_header Access-Control-Allow-Origin *;
# proxy_buffering
:是否启用缓冲。
- 可用范围:http、server、location
- 例:
proxy_request_buffering on; # 接收客户端的请求报文时,是否先缓冲整个报文,然后才转发给 upstream ,默认为 on 。如果禁用缓冲,则会一边接收、一边转发,通常耗时更少 proxy_buffering on; # 接收 upstream 的响应报文时,是否先缓冲整个报文,然后才转发给客户端。默认为 on proxy_buffers 8 4k; # 用于接收每个响应报文的缓冲区的数量、大小。默认为一个内存页大小 proxy_buffer_size 4k; # 用于接收每个响应报文的第一部分的缓冲区大小。默认为一个内存页大小 proxy_busy_buffers_size 8k; # busy 状态的缓冲区的最大大小,一般为 2 个缓冲区 proxy_temp_path /tmp/nginx/proxy_temp 1 2; # 在一个磁盘目录下创建临时文件来缓冲数据,划分 2 层子目录 proxy_temp_file_write_size 16k; # 每次写入临时文件的最大数据量 proxy_max_temp_file_size 1024m; # 所有临时文件的总大小
当 Nginx 通过 proxy_pass 接收 upstream 的响应报文时,默认启用了缓冲,但没有启用缓存。
- 缓冲(buffer)
- :从 upstream 接收响应报文时,暂存几秒钟,等接收整个报文之后,再发送给客户端。
- 如果该响应报文超出内存缓冲区,则暂存到磁盘的 proxy_temp_path 目录下,并在 error_log 中记录:
[warn] an upstream response is buffered to a temporary file
- 还没有完全接收该响应报文时,最多可以将缓冲区中 proxy_busy_buffers_size 大小的部分,发送给客户端。这部分缓冲称为 busy 状态。
- 如果该响应报文超出内存缓冲区,则暂存到磁盘的 proxy_temp_path 目录下,并在 error_log 中记录:
- 优点:
- 适用于 Nginx 与 upstream 的通信速度,比 Nginx 与客户端的通信速度快很多的情况。可以尽早与 upstream 断开连接,减少其负载。
- 缺点:
- 增加了客户端等待 HTTP 响应的时间。
- 如果不启用缓冲,则 Nginx 每接收 proxy_buffer_size 大小的响应数据就同步发送给客户端。这样通信延迟更低,但是通信效率低。
- 启用了 cache 时,可以不启用 buffer ,反正都要暂存到磁盘。
- :从 upstream 接收响应报文时,暂存几秒钟,等接收整个报文之后,再发送给客户端。
- 缓存(cache)
- :从 upstream 接收响应报文时,暂存几分钟甚至几天,当客户端再次请求同一个响应报文时就拿缓存文件回复,不必请求 upstream 。
- 优点:
- 可以避免重复向 upstream 请求一些固定不变的响应报文,减少 upstream 的负载,减少客户端等待响应的时间。
- 缺点:
- 增加了磁盘 IO 。
# proxy_cache
:是否启用缓存。
可用范围:http、server、location
例:
proxy_cache cache1; # 定义一个名为 cache1 的共享内存空间,用于记录缓存项的索引,可以被多个地方使用(默认为 off ) proxy_cache_path /tmp/nginx/proxy_cache # 在一个磁盘目录下创建临时文件来缓存数据 levels=1:2 # 划分 2 层子目录 keys_zone=cache1:10m # 在缓存空间 cache1 中占用 10 MB 的内存 max_size=10g # 最多缓存 10 GB 的数据(缓存空间满了时会自动删掉较少访问的缓存项) inactive=60m # 如果一个缓存项一直没有被访问,则超过 60 min 之后就删除 use_temp_path=off; # 待缓存数据会先写入临时文件,再重命名为缓存文件。该参数是指是否在其它目录写入临时文件 proxy_no_cache $cookie_nocache $arg_nocache $arg_comment; # 定义不使用缓存的条件(只要任一变量值为真) # proxy_cache_background_update off; # 是否允许更新过期的缓存项 proxy_cache_valid 200 302 10m; # 限制不同状态码的响应的缓存时间 proxy_cache_valid 404 1m; proxy_cache_valid any 5m; # 限制所有响应报文的缓存时间 proxy_cache_revalidate on; # 刷新过期的缓存项时,向 upstream 发出的请求头中包含 If-Modified-Since、If-None-Match proxy_cache_min_uses 3; # 当同一个响应被客户端请求至少 3 次之后,才缓存它(调大该参数可以只缓存频繁请求的响应) proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; # 刷新过期的缓存项时,如果 upstream 报出这些错误而不能响应,则将已过期的缓存项发送给客户端 # proxy_cache_key $scheme$proxy_host$request_uri; # 定义每个缓存项的标签值(用于判断内容是否变化) proxy_cache_lock on; # 多个请求指向同一个缓存项且没有命中缓存时,只将一个请求转发给 upstream ,其它请求则被阻塞直到从缓存区获得响应 # proxy_cache_lock_age 5s; # 一组请求每被阻塞 5 s ,就解锁一个请求,转发给 upstream # proxy_cache_lock_timeout 5s; # 一组请求最多被阻塞 5 s 。超过该时间则全部解锁,但不会缓存响应报文
如果客户端发来的请求 URL 与缓存的某个 URL 的 hash 值相同,则直接从缓存中取出数据回复给客户端,此时响应头中包含
Nginx-Cache: HIT
。- 否则,将请求转发给 upstream 处理,此时响应头中包含
Nginx-Cache: MISS
。 - 如果响应头中的 Cache-Control 取值为 Private、No-Cache、No-Store 或 Set-Cookie ,则不缓存。
- 否则,将请求转发给 upstream 处理,此时响应头中包含
可以给客户端加上一个响应头,表示缓存的使用情况。
add_header X-Cache-Status $upstream_cache_status;
其可能的取值如下:
BYPASS # 该响应不应该使用缓存,这是 proxy_no_cache 参数在起作用 MISS # 该响应存在缓存 HIT # 该响应不存在缓存 EXPIRED # 该响应存在缓存,但是过期了 UPDATING # 该响应的缓存已过期,这是刚刚请求的新响应 STALE # 这是 proxy_cache_use_stale 参数在起作用 REVALIDATED # 这是 proxy_cache_revalidate 参数在起作用
# upstream
:用于定义一组上游服务器,供 proxy_pass 转发,可实现负载均衡、高可用。
- 可用范围:http
- 例:
upstream backend { # 定义一组 upstream ,名为 backend server 127.0.0.1:81; # 添加一个 server server 127.0.0.1:82 down; server test.backend.com; server unix:/tmp/backend; # keepalive 2; } server { listen 80; location / { proxy_pass http://backend; # 将请求转发给 upstream } }
- server 地址可以是 IP:Port 或 Unix Socket 文件,不能包含 http:// 协议头。
- server 的可用参数:
weight=1 # 权重,默认为 1 max_conns=0 # 限制该 server 同时处理的 TCP 连接数。默认为 0 ,即没有限制 max_fails=1 # 默认值为 1 ,取值为 0 则不限制 fail_timeout=10s # 如果在一个 fail_timeout 周期内,与该 server 通信失败 max_fails 次,则在该周期内认为它不可用 backup # 将该 server 标记为备份服务器,当所有非 backup 服务器不可用时才使用它 down # 将该 server 标记为不可用
- 比起将上游服务器的地址直接写在 proxy_pass 指令中,写在 upstream 指令中的优点更多:
- 可以配置 server 的详细参数。
- 可以配置多个 server ,进行负载均衡。
- 可以启用 keepalive 长连接。
- 安装第三方模块 lua-nginx-module 之后,可以插入 lua 代码,实时生成 upstream 配置,实现动态路由。
常见的 upstream 分配策略:
轮询分配:将 HTTP 请求按时间顺序依次分配给各个 server ,实现简单的平均分配。
upstream backend { server 127.0.0.1:81; server 127.0.0.1:82; server test.backend.com; }
- Nginx 在启动时会解析域名。如果一个域名解析到多个 IP ,则对这些 IP 采用轮询分配。
- 如果一个 server 不可用,则分配给下一个 server 处理。如果所有 server 都不可用,则将最后一个 server 的报错返回给客户端。
加权轮询分配:权重越大的 server 被分配的概率越大。适合处理几台 server 性能不均的情况。
upstream backend { server 127.0.0.1:81 weight=5; server 127.0.0.1:82 weight=10; }
- 默认采用加权轮询分配。
按响应时间分配:响应时间越短的 server 被分配的概率越大。
upstream backend { fair; server 127.0.0.1:81; server 127.0.0.1:82; }
- 需要安装第三方模块 nginx-upstream-fair 。
按 ip 的哈希分配:对 ip 的前 24 位计算 hash 值,将 hash 值相同的 HTTP 请求分配给同一个 server 。适合保持 session 、利用缓存。
upstream backend { ip_hash; server 127.0.0.1:81; server 127.0.0.1:82; }
自定义的哈希分配:
upstream backend { hash $remote_addr consistent; server 127.0.0.1:81; server 127.0.0.1:82; }
- 启用 consistent 参数则会采用 ketama 一致性哈希算法,使得改变 upstream 中的 server 数量时,绝大部分哈希值依然映射到原来的 server 。
# stream
:用于定义 TCP、UDP 代理。
- 可用范围:main
- 例:
stream { upstream mysql { server 10.0.0.1:3306 weight=5; server 10.0.0.2:3306 weight=10; } server { listen 3306; # listen 3306 udp; # 监听端口时,默认采用 TCP 协议,也可指定为 UDP 协议 proxy_pass mysql; proxy_connect_timeout 5s; # 与 upstream 建立连接的超时时间,默认为 1m proxy_timeout 1m; # 如果超过一定时间未读写数据,则断开连接。默认为 10m # proxy_next_upstream on; # 不能与 upstream 建立连接时,是否尝试连接下一个 upstream server # proxy_bind $remote_addr transparent; # 将转发的数据包的源地址 ip:port 改为真实的源地址,实现透明代理,此时需要以 user root 运行 Nginx ,且 upstream 会返回响应给该地址。默认采用当前 Nginx 的地址 } }