# 路由转发

  • 当 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    127.0.0.1:80;
    listen    unix:/var/run/nginx.sock;
    
    • 一个 server 可以同时声明多个 listen 指令。
    • listen 指令决定了 server 绑定一个什么样的 Socket 并监听,只会接收发送到该 Socket 的 TCP 数据包。
  • 如果有多个 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.com1.www.test.com ,不匹配 test.com
      • 不允许在其它位置使用通配符 * ,例如 server_name www.*.comserver_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 会报错。
  • 支持正则替换:
    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
  • 例:

    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 阶段重新处理。
      • 此时总是会内部重定向,不会返回重定向报文。
  • rewrite 指令的执行结果要么是将请求内部重定向,要么是返回一个外部重定向报文。

    • 每个 HTTP 请求最多被内部重定向 10 次,超过该次数则返回响应报文:500 Internal Server Error
    • 当 server 接收一个 HTTP 请求时,会先解析出 request_uri 等变量的值,即使发生内部重定向也不会改变,除非转发到其它 server 。
  • 例:将 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 组合使用则无效。
  • 例:
    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 指令。
  • 反向代理多个 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/;
          }
      }
      

# 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   # 取值等于请求头的 字段,加上 $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 想修改该字段的值,则可添加以下配置:
    proxy_hide_header Access-Control-Allow-Origin;
    add_header Access-Control-Allow-Origin *;
    
    如果只配置 add_header ,则不会覆盖 HTTP 响应中的同名 Header ,会产生两行重复的 Header 。

# 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 状态。
    • 优点:
      • 适用于 Nginx 与 upstream 的通信速度,比 Nginx 与客户端的通信速度快很多的情况。可以尽早与 upstream 断开连接,减少其负载。
    • 缺点:
      • 增加了客户端等待 HTTP 响应的时间。
    • 如果不启用缓冲,则 Nginx 每接收 proxy_buffer_size 大小的响应数据就同步发送给客户端。这样通信延迟更低,但是通信效率低。
      • 启用了 cache 时,可以不启用 buffer ,反正都要暂存到磁盘。
  • 缓存(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 ,则不缓存。
  • 可以给客户端加上一个响应头,表示缓存的使用情况。

    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;    # 指定 UDP 协议,默认为 TCP 协议
            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 的地址
        }
    }