本文目录

[[toc]]

语法规则

  • 配置文件由指令与指令块构成,每条指令以 ; 结尾,指令与参数间使用 分割,指令块使用 {} 包裹。

  • 使用 include 语句可以组合多个配置文件,提升可维护性。

  • 使用 # 添加单行注释,使用 $ 调用变量

  • 时间单位为有:

    • ms: 毫秒
    • s: 秒
    • m: 分钟
    • h: 小时
    • d: 天
    • w: 周
    • M: 月份,固定一个月 = 30 天
    • y: 年份,固定一年 = 365 天
  • 空间单位有:

    • 不加单位: 字节
    • k / K: 1024 字节
    • m / M: 1024 * 1024 字节
    • g / G: 1024 * 1024 * 1024 字节
  • 指令块有四种:

    • http: 根配置
    • upstream: 配置上游服务信息
    • server: 指定站点信息
    • location: 配置路径、资源匹配等

常见 Nginx 配置

日志配置

http {
  # 配置日志输出的内容格式,命名为 main
  log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

  # 配置访问日志存放位置,记录日志使用 main 格式
  access_log /var/log/nginx/access.log main;

  # 配置错误日志存放位置
  error_log /var/log/nginx/error.log;
}

gzip

http {
  # 开启 gzip
  gzip on;

  # 声明小于 1KB 的文件不开启 gzip
  gzip_min_length 1k;

  # gzip 压缩级别
  gzip_comp_level 2;

  # 针对哪些 MIME 类型开启 gzip 压缩
  gzip_types text/plain text/css application/xml text/javascript image/jpeg image/png;
}

自动索引

http {
  # 开启自动索引支持,直接访问目录时将会展示目录下所有文件,样式由 Nginx 提供
  autoindex on;
}

访问限流

http {
  # 限制传输速度最大为 1k/s
  # 避免大资源占满带宽,影响其他正常业务请求连接
  set $limit_rate 1k;
}

连接池

# 每个 worker 支持的并发连接数,包括 client 连接与 upstream 连接
worker_connections: 512;

负载均衡

先配置提供服务的 Nginx 只能接受网关请求,限制外部直接访问

http {
  server {
    # 限制只能接受本机发来的请求,如果是集群可以配置集群的内网地址,比如 192.168.0.1:8080
    listen 127.0.0.1:8080
  }
}

网关 Nginx 配置如下:

http {
  # 为提供服务的集群命名为 local
  upstream local {
    # 前面配置的提供服务的设备 1
    server 127.0.0.1:8080

    # 前面配置的提供服务的设备 2
    server 127.0.0.1:8081

    # 前面配置的提供服务的设备 3
    server 127.0.0.1:8082
  }

  # 定义一份缓存配置
  # /tmp/nginx_cache   缓存文件的根目录
  # levels=1:2   生成两层子目录,比如 `hash(key)` 为 `d41d8cd98f00b204e9800998ecf8427e`
  #     会存放到 `/d/41/d41d8cd98f00b204e9800998ecf8427e` 的目录中
  # keys_zone=test_cache:10m   定义共享内存区域的名称与 key 容量, 1M 约 8000 个 key
  # max_size=10g   设置缓存总容量上限,超出会使用 LRU 淘汰
  # inactive=60m   缓存过期时间为 1 小时,只要 1 小时没有被访问就过期
  # use_temp_path=off   控制是否使用临时路径存储缓存文件
  proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=test_cache:10m max_size=10g inactive=60m use_temp_path=off;

  server {

    # 配置站点的域名
    server_name nginx.he110.site
    # 配置监听任意 IP 发来的消息
    listen 80

    location / {
      # 更新 header 中的 Host 为客户端的信息,而不是反向代理的信息
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      # 将反向代理的结果缓存起来,提高性能
      # 此缓存类似浏览器强缓存,没到时间不会过期
      # test_cache 为之前配置的共享内存区名称
      proxy_cache test_cache;

      # 缓存的 key 配置,通过 主机 + uri + 参数 缓存请求结果
      proxy_cache_key $host$uri$is_args$args;

      # 针对这些状态码的请求,缓存时间设置为 1 天
      proxy_cache_valid 200 304 302 1d;

      # 所有请求转发到前面配置的集群中
      proxy_pass http://local;
    }
  }
}

Nginx 指令

常见指令

  • 查询运行中的 Nginx: ps -ef | grep nginx
  • 热部署升级 Nginx: kill -USR2 ${Nginx 进程 pid} ,会自动平滑过度,新老 Nginx 一起工作,流量逐步切换到新 Nginx
  • 停止老 Nginx: kill -WINCH ${Nginx 进程 pid} ,老 Nginx 会关闭 worker ,但是进程还在运行,不接收流量了,方便回退老 Nginx
  • 日志切割: mv /var/log/nginx /var/log/nginx-bak && nginx -s reopen ,直接将日志挪走再重新运行,就可以切割日志了,可以通过 crontab 自动备份 Nginx 日志

指令冲突

指令生效条件

Nginx 指令生效的条件是:

该条指令能在这个配置块中生效。例如: log_format 指令只能在 http 块下生效,在 serverlocation 中不会生效。

如果指令生效,并且相同指令使用了不同的值时,即为指令冲突。

指令将划分为两种类型:

  • 值指令: 存储配置项的值,例如: rootaccess_loggzip
  • 动作指令: 指定 Nginx 行为,例如: rewriteproxy_pass

值指令是可以合并的,而动作类指令是不能合并的。

通常情况下,在 server_rewiterewritecontent 等阶段能够生效的,就是动作类指令。

值指令继承

值指令继承规则:

  • 子配置块定义: 直接使用子配置块的配置
  • 子配置块未定义: 使用父配置块的值,如果父配置块未定义,使用默认值

参考如下示例

server {
  listen 8080;
  root /home/he110/nginx/html;
  accesss_log logs/he110.access.log main;

  location /test {
    # 子块 root 覆盖父块的 root
    root /home/he110/nginx/test;
    # 子块 accesss_log 覆盖父块的 accesss_log
    accesss_log logs/access.test.log main;
  }

  location /dlib {
    # 直接使用子块的 alias 配置
    alias dlib/;

    # 不能定义到 location 中,不会生效
    listen 4396;
  }

  location / {
    # 继承父块的 root 与 accesss_log
  }
}

第三方 Nginx 模块

针对于第三方 Nginx 模块,指令是值指令还是动作指令不太好确定,可以从以下方面考虑:

  • 指令在哪个块下生效。 当指令生效时,其值就被确定,不可被覆盖了。
  • 指令允许出现在哪些块中。 如果允许在多个块中出现,粒度更细的块更具备决定作用。
  • 如果是 server 块中生效,会通过 merge_srv_conf 进行合并,可以通过源码确定是否合并,以及合并流程。
  • 如果是 location 块中生效,会通过 merge_loc_conf 进行合并,可以通过源码确定是否合并,以及合并流程。

listen 指令

  • 作用: 立即响应该请求,跳过后续的所有阶段
  • 默认值: listen *:80 | *:8000;
  • 作用域: server
# 监听 socket ,性能更好的方式
listen unix:/var/run/nginx.sock;

# 监听本地的 8080 端口
listen 127.0.0.1:8080;

# 监听本地请求
listen 127.0.0.1;

# 监听 IPv4 的 8080 端口
listen 8080;

# 监听 IPv4 + IPv6 的 8080 端口
listen *:8080;

# 老的 Nginx 需要使用 bind 覆盖 * ,从而实现单独的 listen
# 新版本的 Nginx 已经不需要了
listen localhost:8080 bind;

# 只监听 IPv6 的 8080
listen [::]:8080 ipv6only=on;

# 监听本机 IPv6
listen [::1];

server_name 指令

  • server_name

    • 作用: 决定了请求应该走到哪个配置块中,支持多域名、泛域名、正则表达式等方式。
    • 默认值: server_name _;
    • 作用域: httpserverlocation
  • server_name_in_redirect

    • 作用: 返回响应的时候,是否修改为 主域名 返回
    • 默认值: server_name_in_redirect off;
    • 作用域: httpserverlocation

server_name 的匹配顺序为:

  1. 精确匹配,比如: www.he110.site
  2. * 在前面的泛域名,比如: *.he110.site
  3. * 在后面的泛域名,比如: wap.*.he110.site
  4. 按照配置中, server 块定义顺序匹配正则表达式
  5. 按顺序匹配第一个 default server
    • 没有配置 server_name 的第一个 server 就是 default server
    • 配置了 listen default; 的 server 也是 default server
# 配置单域名
server_name www.he110.site;

# 配置多域名,第一个域名为主域名
server_name m.he110.site wap.he110.site;
# 返回响应的时候,修改为 主域名 返回
# 例如用户请求 wap.he110.site ,响应头中的 url 会自动修改为 m.he110.site
server_name_in_redirect on;

# 配置泛域名,支持 *.domain 或者直接省略 * 的写法
server_name www.he110.site wap.*.he110.site .m.he110.site;

# 配置正则表达式,需要使用 ~ 前缀,说明是正则匹配,可以与普通域名一起混用
server_name www.he110.site ~www\d+\.he110\.site$;

# 可以匹配所有没有传递 Host 头的请求
server_name "";

# 可以匹配所有未被 server 捕获的请求
server_name _;

Nginx 进程间通信

基础同步工具

  • 信号: 早期 Nginx 会基于信号,也就是 Signal 传递消息
  • 共享内存: 现在更换为共享内存通信
    • 锁: 通过锁确保进程操作的原子性,使用共享内存的模块有以下几个
      • ngx_http_lua_api
      • rbtree( 红黑树 )
        • ngx_http_limit_conn_module
        • ngx_stream_limit_conn_module: 流控模块
        • ngx_stream_limit_req_module: 流控模块
        • http cache: http 缓存
          • ngx_http_file_cache
          • ngx_http_proxy_cache
          • ngx_http_scgi_cache
          • ngx_http_uwsgi_cache
          • ngx_http_fastcgi_cache
        • ssl: TLS/SSL
          • ngx_http_ssl_module
          • ngx_mail_ssl_module
          • ngx_stream_ssl_module
      • 单链表:
        • ngx_http_upstream_zone_module
        • ngx_stream_upstream_zone_module
    • Slab 内存管理器

Nginx 信号

信号作用
master 进程CHLD子进程给父进程发送该信号说明子进程故障,需要重新拉起 worker
master 进程TERM立即停止 Nginx 进程,对应 nginx -s stop
master 进程INT立即停止 Nginx 进程,对应 nginx -s stop
master 进程QUIT不再接受新请求,当前所有请求处理完成后停止 Nginx 进程,对应 nginx -s quit
master 进程HUP重载配置文件,对应 nginx -s reload
master 进程USR1重新打开日志文件,用于日志文件分割,对应 nginx -s reopen
master 进程USR2热部署使用,两 Nginx 平滑过度,老进程不再接收流量,但是也不会退出,无对应指令
master 进程WINCH热部署使用,停止老的 Nginx 所有 worker 子进程, Nginx 主进程不退出,无对应指令
worker 进程TERM立即停止 worker 进程,一般通过 Nginx 主进程统一管理,不会单独使用
worker 进程INT立即停止 worker 进程,一般通过 Nginx 主进程统一管理,不会单独使用
worker 进程QUIT不再接受新请求,当前所有请求处理完成后停止 worker 进程,一般通过 Nginx 主进程统一管理,不会单独使用
worker 进程USR1重新打开日志文件,用于日志文件分割,一般通过 Nginx 主进程统一管理,不会单独使用
worker 进程WINCH热部署使用,停止 worker 子进程,一般通过 Nginx 主进程统一管理,不会单独使用

OpenResty 使用共享内存代码示例

http {
  # 声明使用 10M 共享内存
  lua_shared_dict dogs 10m;

  server {
    location /set {
      content_by_lua_block {
        # 拿到共享内存
        local dogs = ngx.shared.dogs
        #Jim = 10 存入共享内存
        dogs:set('Jim', 8)
        # 发给用户 stoped
        ngx.say('stoped')
      }
    }

    location /get {
      content_by_lua_block {
        # 拿到共享内存
        local dogs = ngx.shared.dogs
        # 返回共享内存中纯的 Jim
        ngx.say(dogs:set('Jim'))
      }
    }
  }
}

Slab 内存管理

  1. 将内存按固定大小,一般为 4kb ,切割为不同的空闲页,空闲页使用链表串联
  2. 每一页按照内存大小分割为不同类型的 slot ,比如 32 字节、 64 字节、 128 字节等等
  3. 每一页有三种状态: full 、 partial 、 empty
  4. 在分配的的时候,优先选择 partial 存放, slot 按顺序排列
  5. 对象按固定大小分类存储,避免外部碎片;空闲对象复用减少频繁释放导致的内碎片

这套内存管理成为 bestfit ,有以下特点

  • 适合小对象
  • 避免碎片
  • 避免重复初始化

可以使用 ngx_slab_stat 查看不同 slot 的使用情况

http {
  server {
    listen 80;

    location = /slab_stat {
      slab_stat;
    }
  }
}

TLS/SSL

根据 Nginx 的主要使用场景,可以针对性的对算法进行优化与选择,包括更换算法、调整加密等级、使用会话缓存等,以提高通信性能

  • 小文件传输时,性能瓶颈在非对称加密,也就是握手阶段
  • 大文件传输时,性能瓶颈在对称加密,也就是数据传输阶段

完整通信过程如下:

---
title: TLS 通信流程
---
sequenceDiagram
  participant Client
  participant Server as Nginx

  Note over Client,Server: === 握手阶段 ===
  Client->>Server: Client Hello<br>通知服务器自身支持哪些 TLS 版本、密码套件列表、Client Random

  Server->>Client: Server Hello<br>选择 TLS 版本、密码套件,返回 Server Random
  Server->>Client: Server Certificate<br>发送证书链(含公钥)
  Server->>Client: Server Key Exchange (可选)<br>如使用ECDHE/DHE算法,发送临时公钥
  Server->>Client: Server Hello Done

  Note over Client,Server: === 证书验证与密钥交换 ===
  Client ->> Client: 验证证书有效性(CA链、有效期、吊销状态等)
  Client->>Server: Client Key Exchange<br>生成 PreMasterSecret ,用服务器公钥加密后发送
  Client->>Server: ChangeCipherSpec<br>通知切换至加密通信
  Client->>Server: Finished<br>加密验证握手完整性

  Server->>Client: ChangeCipherSpec
  Server->>Client: Finished

  Note over Client,Server: === 加密通信阶段 ===
  Note over Client,Server: 双方进行会话密钥生成<br>基于 ClientRandom + ServerRandom + PreMasterSecret
  Client->>Server: Application Data (Encrypted)
  Server->>Client: Application Data (Encrypted)
---
title: TLS 通信命中缓存
---
sequenceDiagram
  participant Client
  participant Server

  Note over Client,Server: === 握手阶段 ===
  Client->>Server: Client Hello<br>通知服务器自身支持哪些 TLS 版本、密码套件列表、Client Random

  Server->>Client: Server Hello<br>确认复用会话,返回 Server Random

  Note over Client,Server: === 证书验证与密钥交换 ===
  Server->>Client: ChangeCipherSpec
  Server->>Client: Finished<br>使用缓存密钥加密验证

  Note over Client,Server: === 加密通信阶段 ===
  Note over Client,Server: 复用上次通信使用的密钥
  Client->>Server: Application Data (Encrypted)
  Server->>Client: Application Data (Encrypted)

Nginx 添加 TLS 证书相关配置如下:

http {
  # HTTPS 使用的是 443 端口
  listen 443 ssl;
  # 配置证书的公钥
  ssl_certificate /etc/letsencrypt/live/me.he110.site/fullchain.pem;
  # 配置证书的私钥
  ssl_certificate_key /etc/letsencrypt/live/me.he110.site/private.pem;

  # session cache 最多缓存多大的空间, 1MB 大约对应 4000 个 session
  ssl_session_cache shared:le_nginx_SSL:1m;
  # 设置 session cache 有效期为 1 天,也就是说,缓存没溢出的情况下, 1 天内断开后重新连接不需要握手
  ssl_session_timeout 1440m;

  # 支持的 TLS 协议版本
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

  # 通过 ssl_ciphers 决定使用什么安全套件与客户端进行通信
  ssl_prefer_server_ciphers on;

  # 声明支持的安全套件,一般越前面的越先使用,除非客户端不支持
  ssl_ciphers "ECDHE-ECDSA-CHACHA20-P0LY1305:ECDHE-RSA-CHACHA20-P0LY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS";

  # 非对称加密时的参数,决定了加密强度
  ssl_dhparam /etc/letsencrypt/live/me.he110.site/dhparam.pem;
}

Nginx 架构

Nginx 本身是多进程架构,由主进程管理 worker 进程、 cache 进程 、 cache-loader 进程。

多进程可以很好的保障共享内存异常时整个服务崩溃的情况,为了保障高可用, Nginx 采用了多进程架构。

Nginx 所有的功能都是由 worker 进程处理的,为了充分利用 CPU 资源,一般会将 worker 数量与 CPU 核心数设置为相同,并为每个 worker 进程绑定一个对应的的 CPU 。

当使用 nginx -s reload 或者 kill -SIGHUP ${Nginx PID} reload Nginx 时,本质上是将已有的 worker 进程 kill 掉,再重新创建 worker 进程。

当通过 kill -SIGTERM ${Nginx worker PID} 单独关闭某个子进程的时候,子进程会通知 Nginx 主进程,主进程会重新创建一个新的 worker 进程。

内存池分配

建立连接

在建立连接后, Nginx 会根据负载均衡策略,选中一个 worker 处理连接, worker 会为连接分配一块内存池,在连接断开时才会回收该内存空间。

在客户端发来请求后,会预先为请求分配 buffer 缓冲区,用于读取请求的 DATA ,空间从 连接内存池 中分配,如果连接内存池不够则进行扩容。

buffer 缓冲区主要用于读取用户发来的请求 data 。

# 默认为连接内存池分配的空间
connection_pool_size: 512;

# 连接的超时时间,默认为 60s ,如果 60s 还没有处理完成请求,就把连接设置为超时,关闭连接
# 该超时时间涵盖的范围包括: 接收请求、解析请求 URI 、 解析 header ,需要根据业务情况调整超时时间
client_header_timeout: 60s;

# 接收到请求后,将请求 data 读取到用户态中,预先分配出来读取 data 的空间
client_header_buffer_size: 1k;

接受 URI

读取完请求的 data 后,会预先为请求分配一块请求内存池,用来存储解析后的请求上下文,请求结束后立即回收该内存空间。

开始解析请求 data ,存储到请求内存池中。

如果遇到请求内存池不够用的情况,会进行扩容。

处理完成后,会将存储 URI 的内存块地址,更新到指针中,标识 URI 。

开始解析 header ,存储到之前复用的请求内存池中,解析完成后标识 header 。

全都处理完成后,会移除超时定时器 client_header_timeout

# 预先为每个请求分配的内存池大小
request_pool_size: 4k;

# 设置请求内存池最大有多少空间
# 这里不是直接分配 8k ,而是每次分 8k ,如果不够用再扩容 8k ,一共 4 块 8k 的内存空间
# URL + header 都会使用这块空间
large_client_header_buffers: 4 8k;

Nginx 工作流程

QUIT

QUIT 只能处理 HTTP 请求,针对 websocket 、 TCP 、 UDP 等通信是无法识别通信是否结束的,只能等待定时器超时。

---
title: worker QUIT 流程
---
graph TB
  A[设置定时器 worker_shutdown_timeout] --> B[关闭监听句柄]
  B --> C[关闭空闲连接]
  C --> D[在循环中等待全部连接关闭,超时会强制关闭]
  D --> E[退出进程]

reload

---
title: Nginx reload 流程
---
graph TB
  A[向 master 进程发送 HUP 信号] --> B[master 进程校验配置语法是否正确]
  B --> C[master 进程打开新的监听端口]
  C --> D[master 进程使用新配置启动新的 worker 子进程]
  D --> E[master 进程向老的 worker 发送 QUIT 信号]
  E --> F[老 worker 关闭监听句柄,处理完当前连接后结束进程]

由于先启动新 worker 再停止老 worker ,所以 Nginx 不会停止服务,但是可能会存在老 worker 处理时间很长,一直占用资源。

在新版本中 Nginx 引入超时机制,在超时后强行关闭老 worker 进程。

热升级

---
title: Nginx 热升级流程
---
graph TB
  A[将旧 Nginx 的二进制文件替换为新的 Nginx 二进制文件] --> B[向 master 进程发送 USR2]
  B --> C[master 进程修改 PID 文件名,添加加后缀 .oldbin]
  C --> D[master 进程使用新的 Nginx 文件启动新的 master 进程]
  D --> E[向老的 master 进程发送 QUIT 信号,关闭老 master 进程]
  E --> F[回滚:向老 master 进程发送 HUP ,向新 master 进程发送 QUIT]

请求处理

---
title: Nginx 请求处理流程
---
graph TD
  POST_READ["POST_READ阶段
  (获取真实客户端IP)"] --> SERVER_REWRITE["SERVER_REWRITE阶段
  (服务级URI重写)"]
  SERVER_REWRITE --> FIND_CONFIG["FIND_CONFIG阶段
  (匹配location块)"]
  FIND_CONFIG --> REWRITE["REWRITE阶段
  (location级URI重写)"]
  REWRITE -->|触发重写| FIND_CONFIG
  REWRITE --> POST_REWRITE{POST_REWRITE检查}
  POST_REWRITE -->|未重写| PREACCESS["PREACCESS阶段
  (并发/QPS限制)"]
  POST_REWRITE -->|已重写| FIND_CONFIG

  subgraph 权限验证
    PREACCESS --> ACCESS["ACCESS阶段
    (auth_basic/access控制)"]
    ACCESS --> POST_ACCESS{POST_ACCESS检查}
    POST_ACCESS -->|验证失败| LOG
    POST_ACCESS -->|验证通过| PRECONTENT["PRECONTENT阶段
    (try_files/镜像请求)"]
  end

  PRECONTENT --> CONTENT["CONTENT阶段
  (生成响应内容)"]
  CONTENT --> LOG["LOG阶段
  (记录访问日志)"]

  classDef phase fill:#f0f8ff,stroke:#4682b4,stroke-width:2px;
  class POST_READ,REWRITE,PREACCESS,ACCESS,CONTENT,LOG phase;
处理阶段负责的模块
POST READrealip
SERVER REWRITErewrite
FIND CONFIG
REWRITErewrite
POST REWRITE
PREACCESSlimit_connlimit_req
ACCESSauth_basicaccessauth_request
POST ACCESS
PRECONTENTtry_files
CONTENTindexautoindexconcat
LOGaccess_log

涉及的模块处理顺序如下图:

所有模块都会在 ngx_module_names 数组中定义。

不同阶段的模块会按阶段顺序执行。

相同阶段内的模块按顺序执行。

顺序在前的模块可以对流程进行控制,比如: 跳过当前阶段剩下所有模块,进入下一阶段。

Nginx 处理请求的阶段

postread 阶段

ngx_http_realip_module 模块

在网络传输过程中,源 IP 与目的 IP 会被交换机、路由器等网络设备修改,包括防火墙也可以进行修改,所以依据源 IP 、 目的 IP 是无法获取到请求客户端的真实 IP 地址的,只能拿到上一跳设备的地址。

在 HTTP 协议中,有 X-Forwarded-ForX-Real-IP 两个请求头,其中:

  • X-Forwarded-For: 完整的传输链路,链路中的发送端 IP 都会被接收端记录到该字段中
  • X-Real-IP: 传递用户的 IP

realip 模块将会获取用户的真实地址,并记录到变量中,传输给其他模块,所以 realip 模块的执行优先级会比 Nginx 的 rewrite 模块都要高。

realip 模块可以在编译 Nginx 时,通过 --with-http_realip_module 启用。

realip 生成两个变量:

  • realip_remote_addr: 用户的真实 IP
  • realip_remote_port: 用户的真实端口

realip 提供了三条指令:

  • set_real_ip_from:
    • 基本格式: set_real_ip_from ${address} / ${CIDR} / ${unix}
    • 作用: 配置针对哪些来源的数据包,替换 real ip
    • 默认值: 无
    • 作用域: httpserverlocation
  • real_ip_header:
    • 基本格式: real_ip_header ${field} / X-Real-IP / X-Forwarded-For / ${proxy_protocol}
    • 作用: 配置从哪个字段里面取真实 IP
    • 默认值: real_ip_header X-Real-IP;
    • 作用域: httpserverlocation
  • real_ip_recursive:
    • 基本格式: real_ip_recursive on / off;
    • 作用: real ip 可能取到多个字段,当最近的 IP 是环回地址,也即是本机 IP 的时候,是否丢弃该地址,继续取新地址
    • 默认值: real_ip_recursive off;
    • 作用域: httpserverlocation

server_rewrite 阶段

rewrite 模块

return 指令

return 是 rewrite 模块提供的指令,一旦被使用,将立即终止请求处理流程,返回响应给客户端

return 指令的基本格式为 return [code] msg , return 省略 code 时,默认使用 302 状态码

  • 作用: 立即响应该请求,跳过后续的所有阶段
  • 默认值: 无
  • 作用域: serverlocationif

error_page 指令

  • 作用: 将请求转发到指定的资源上
  • 默认值: 无
  • 作用域: httpserverlocationlocation 中的 if
# 捕获 404 状态码,重定向到 404.html(保持原始 404 状态码)
# 用户访问不存在的资源时返回自定义 404 页面, HTTP 状态码仍为 404
# 需确保 /404.html 文件存在,且路径相对于 root 指令设置的目录
error_page 404 /404.html;

# 捕获服务器内部错误( 500 / 502 / 503 / 504 ),统一跳转至 50x.html
# 适用于上游服务崩溃、网关超时等场景,需在对应 location 配置 50x.html 的路径
error_page 500 502 503 504 /50x.html;

# 强制将 404 状态码改写为 200 ,返回空图像(常用于隐藏敏感错误信息)
# 等号后无具体状态码时默认使用 200
# 适用于需要前端展示正常页面但后端实际资源缺失的场景
error_page 404 =200 /empty.gif;

# 动态处理 404 错误( PHP 需配合 fastcgi_intercept_errors on 使用)
# 等号使 PHP 的 header() 状态码修改生效,若 PHP 返回 404 需改用 410 避免冲突
error_page 404 = /404.php;

# 高级重定向配置(通过命名 location 实现反向代理)
# @fallback 是内部重定向标记,外部无法直接访问
# 当原始请求不存在时,将流量转发至 backend 服务集群做容灾处理
location / {
  error_page 404 = @fallback;
}

# 命名 location 块(仅限内部跳转)
# 可在此处配置日志记录、自定义响应头等高级操作
location @fallback {
  proxy_pass http://backend;
}

# 直接重定向到外部域名(默认 302 临时跳转)
# 适用于权限校验失败时跳转至统一拦截页
error_page 403 http://example.com/forbidden.html;

# 永久重定向 404 请求到新域名(显式指定 301 状态码)
# 浏览器会缓存该跳转,后续直接访问新地址
error_page 404 =301 http://example.com/forbidden.html;

rewrite 与 error_page 同时配置

当两者同时配置时,由于二者都属于动作指令,将控制 Nginx 返回响应,所以不会合并,例如:

server {
  server_name return.he110.site;
  listen 8080;

  root /app/html;

  # 定义为语句 1
  error_page 404 /404.html;

  # 定义为语句 2
  return 403;

  location / {
    # 定义为语句 3
    return 404 "nothing!";
  }
}

如果同时开启,语句执行顺序为:

  1. 语句 2
  2. 语句 3
  3. 语句 1

三个语句只能由一个被执行,会按照优先级,先执行的先返回,后面的被忽略。

rewrite 指令

  • 基本语法: rewrite ${regex} ${replacement} [flag] ,其中 flag 有如下取值:
    • last: 用新的 URI 进行 location 匹配, last 后定义的命令会被跳过
    • break: 停止当前脚本指令的执行,等价于 break 指令, break 后定义的命令会被跳过
    • redirect: 返回 302 重定向
    • permanent: 返回 301 重定向
  • 作用: 将用户访问的 url 替换为新的 url
  • 默认值: 无
  • 作用域: serverlocationif
server {
  root html/;

  location /first {
    # 替换 first 为 second ,转发到 location /second 继续解析
    rewrite /first(.*) /second$1 last;
    # rewrite 已经转发走了,所以不会执行 return
    return 200 'first!';
  }

  location /second {
    # 修改 second 为 third ,并读取本地的 /second/xx 文件返回
    rewrite /second(.*) /third$1 break;
    # break 已经停止了 location 处理,所以 return 不会被执行
    return 200 'second!';
  }

  location /third {
    # 执行 return
    return 200 'third!';
  }
}
server {
  location /redirectl {
    # 返回 301 状态码,因为定义了 permanent
    rewrite /redirectl(.*) $1 permanent;
  }

  location /redirect2 {
    # 返回 302 状态码,因为定义了 redirect
    rewrite /redirect2(.*) $1 redirect;
  }

  location /redirect3 {
    # 由于返回了 http 请求,并且未指定状态码,会自动填充为 302
    rewrite /redirect3(.*) http://rewrite.he110.site$l;
  }

  location /redirect4 {
    # 返回 301 状态码,因为定义了 permanent
    rewrite /redirect4(.*) http://rewrite.he110.site$1 permanent;
  }
}

if 指令

  • 基本语法: if (condition) {} , condition 取值如下:
    • 检查变量是否为 空 或者 值为 0
    • 使用 = / != 将变量与字符串做匹配
    • 使用正则匹配变量,需要加上前缀
      • ~ / !~ 开头表示大小写敏感
      • ~* / !~* 开头表示大小写不敏感
    • 检查文件是否存在: -f / !-f
    • 检查目录是否存在: -d / !-d
    • 检查文件 / 目录 / 软连接是否存在: -e / !-e
    • 检查是否为可执行文件: -x / !-x
  • 作用: 按照 condition 确定 {} 内的命令是否执行
  • 默认值: 无
  • 作用域: serverlocation
# 匹配正则
if ($http_user_agent ~ MSIE) {
  rewrite ^(.*)$ /msie/$1 break;
}

# 匹配正则,不区分大小写
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
  set $id $1;
}

# 字符串匹配
if ($request_method = POST) {
  return 405;
}

# 变量匹配
if ($slow){
  limit_rate 10k;
}

if ($invalid_referer) {
  return 403;
}

find_config 模块

  • location
    • 基本语法:
      • location [= / ~ / ~* / ^~] uri {}
        • uri: 配置 uri 前缀
        • = uri: 完全匹配 uri
        • ^~ uri: 正则表达式匹配,匹配后不再进行正则表达式匹配
        • ~ uri: 正则表达式匹配,严格大小写
        • ~* uri: 正则表达式匹配,大小写不敏感
      • location @name {}: 跳转到内部的处理流程,该 location 无法通过 uri 直接访问,直接被其他命令跳转进来。
    • 默认值: 无
    • 作用域: serverlocation
  • merge_slashes
    • 作用: 开启后,会将 // 合并为 /
    • 基本语法: merge_slashes on / off
    • 默认值: merge_slashes on;
    • 作用域: httpserver

整个 location 匹配顺序如下:

  1. 完全匹配 ( = uri )
  2. 正则匹配,并且后续不进行正则匹配 ( ^~ uri )
  3. 按照最长匹配前缀排序,匹配的前缀最长的,先匹配。

server {
  merge_slashes off;

  location ~ /Test1/$ {
    return 200 'first regular expressions match!';
  }

  location ~* /Test1/(w+)$ {
    return 200 'longest regular expressions match!';
  }

  location ^~ /Test1/ {
    return 200 'stop regular expressions match!';
  }

  location /Test1/Test2 {
    return 200 'longest prefix string match!';
  }

  location /Test1 {
    return 200 'prefix string match!';
  }

  location = /Test1 {
    return 200 'exact match!';
  }
}

发送以下请求:

  • /Test1: 命中 = /Test1 ,完全匹配优先级最高,哪怕是最后定义的
  • /Test1/: 命中 ^~ /Test1/ ,匹配时遵循最长前缀匹配,所以不会命中完全匹配,而是命中了正则,而正则中 ^~ 有高优先级
  • /Test1/Test2: 命中 ~* /Test1/(w+)$ ,匹配了正则表达式,并且是最长前缀,先命中
  • /Test1/Test2/: 命中 /Test1/Test2 ,正则表达式没命中,匹配了最长前缀
  • /test1/Test2: 命中 ~* /Test1/(w+)$ ,匹配了正则表达式,只有一个匹配项,没什么争议

preaccess 阶段

ngx_http_limit_conn_module 模块

该模块用于限制每秒钟处理的 连接数

NGX_HTTP_PREACCESS_PHASE 阶段生效,默认编译进 nginx

通过共享内存在所有 worker 进程中生效。

使用 key 对链接进行分类,针对同一类的请求进行限流。

用于分类的 key 一般依赖于 realip 模块,需要针对真实 IP 限流才有意义。

  • limit_conn_zone
    • 作用: 定义共享内存(包括声明内存大小),以及 key 关键字
    • 基本语法: limit_conn_zone ${key} zone=${name:size};
      • ${key}: 限流对象,一般为用户真实 IP
      • ${name}: 共享内存名称
      • ${size}: 共享内存大小
    • 默认值: 无
    • 作用域: http
  • limit_conn
    • 作用: 限制并发连接数
    • 基本语法: limit_conn ${zone} ${number};
    • 默认值: 无
    • 作用域: httpserverlocation
  • limit_conn_log_level
    • 作用: 触发限流时的日志级别
    • 基本语法: limit_conn_log_level info / notice / warn / error;
    • 默认值: limit_conn_log_level error;
    • 作用域: httpserverlocation
  • limit_conn_status
    • 作用: 触发限流时,返回给客户端的错误码
    • 基本语法: limit_conn_status code;
    • 默认值: limit_conn_status 503;
    • 作用域: httpserverlocation

示例:

# 使用二进制的用户 IP 地址作为 key
# 定义了名为 addr 的共享内存,有 10M 大
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
  root html/;
  error_log logs/limit_conn.log info;

  location / {
    # 限流时返回 500 错误码
    limit_conn_status 500;

    # 限流时记录的日志为 warn 级别,避免频繁 IO
    limit_conn_log_level warn;

    # 使用 addr 内存记录,一次只处理 1 条请求,超过的部分会被限流,返回 limit_conn_status
    limit_conn addr 1;

    # 限制每秒钟返回 50 字节,延长请求响应时间,便于测试限流场景
    limit_rate 50;
  }
}

ngx_http_limit_req_module 模块

该模块用于限制每秒钟处理的 请求数

NGX_HTTP_PREACCESS_PHASE 阶段生效,默认编译进 nginx

通过共享内存在所有 worker 进程中生效。

限流的算法采用 leaky bucket 算法: 在遇到突发流量时,如果缓冲区已满则返回错误,缓冲区未满则存储到缓冲区中,等待 worker 进程消费。

  • limit_req_zone
    • 作用: 定义共享内存(包括声明内存大小),以及 key 关键字
    • 基本语法: limit_req_zone ${key} zone=${name:size} rate=${rate};
      • ${key}: 限流对象,一般为用户真实 IP
      • ${name}: 共享内存名称
      • ${size}: 共享内存大小
      • ${rate}: 请求处理速率,单位为每秒请求数 ( r/s ) 或者每分钟请求数 ( r/m )
    • 默认值: 无
    • 作用域: http
  • limit_req
    • 作用: 限制并发请求数
    • 基本语法: limit_req ${zone} [burst=${number}] [nodelay];
      • burst: 配置请求缓冲区容纳的请求数
      • nodelay: 声明为 nodelay 的话, burst 内的请求也会被立即处理,也就是返回错误码
    • 默认值: burst 默认为 0
    • 作用域: httpserverlocation
  • limit_req_log_level
    • 作用: 触发限流时的日志级别
    • 基本语法: limit_req_log_level info / notice / warn / error;
    • 默认值: limit_req_log_level error;
    • 作用域: httpserverlocation
  • limit_req_status
    • 作用: 触发限流时,返回给客户端的错误码
    • 基本语法: limit_req_status code;
    • 默认值: limit_req_status 503;
    • 作用域: httpserverlocation

示例:

# 使用二进制的用户 IP 地址作为 key
# 定义了名为 addr 的共享内存,有 10M 大
limit_req_zone $binary_remote_addr zone=addr:10m rate=2r/m;

server {
  root html/;
  error_log logs/limit_req.log info;

  location / {
    # 限流时返回 500 错误码
    limit_req_status 500;

    # 限流时记录的日志为 warn 级别,避免频繁 IO
    limit_req_log_level warn;

    # 使用 addr 内存记录,一次只处理 3 条请求,超过的部分会被限流,返回 limit_req_status
    limit_req zone=addr burst=3 nodelay;
  }
}

同时配置 limit_conn 与 limit_req

可以使用如下配置,触发 limit_conn 返回 500 ,触发 limit_req 返回 503

limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_req_zone $binary_remote_addr zone=req:10m rate=2r/m;

server {
  root html/;
  error_log logs/limit_conn.log info;

  location / {
    limit_conn_status 500;
    limit_conn_log_level warn;
    limit_rate 50;
    limit_conn addr 1;

    limit_req zone=one;
  }
}

结果是返回 503 ,因为 http_limit_req_module 会比 http_limit_conn_module 先被调用,所以先完成了返回

access 阶段

ngx_http_access_module 模块

该模块用于限制特定 IP 的访问,例如 IP 封禁等功能。

NGX_HTTP_ACCESS_PHASE 阶段生效,默认编译进 nginx

  • allow
    • 作用: 允许访问的用户地址
    • 基本语法: allow ${address} / ${CIDR} / ${unix} / all;
    • 默认值: 无
    • 作用域: httpserverlocationlimit_except
  • deny
    • 作用: 禁止访问的用户地址
    • 基本语法: deny ${address} / ${CIDR} / ${unix} / all;
    • 默认值: 无
    • 作用域: httpserverlocationlimit_except

例如:

location / {
  # 支持配置单个 IP
  deny 192.168.0.1;

  # 支持子网掩码
  allow 192.168.1.0/24;

  # 支持 IPv6
  allow 2001:0db8::/24;

  # deny 与 allow 是顺序执行,全匹配必须放到最后面,否则后续命令都不会生效
  deny all;
}

ngx_http_auth_basic_module 模块

该模块基于 HTTP Basic Authutication 协议,对用户身份进行明文的账号密码验证,需要搭配 https 一起使用。

NGX_HTTP_ACCESS_PHASE 阶段生效,默认编译进 nginx ,通过 --without-http_auth_basic_module 禁用 。

  • auth_basic
    • 作用: 配置是否开启 HTTP Basic Authutication
    • 基本语法: auth_basic ${string} / off;
      • ${string}: 给用户输入账号密码时,展示的提示文本。
    • 默认值: auth_basic off;
    • 作用域: httpserverlocationlimit_except
  • auth_basic_user_file
    • 作用: 用户密码存储在哪个文件
    • 基本语法: auth_basic_user_file ${file}
    • 默认值: 无
    • 作用域: httpserverlocationlimit_except

通过 httpd-tools 安装存储密码的数据库操作工具。

使用 htpasswd -c ${file} -b ${user} ${pass} 添加账号

  • ${file}: 数据库文件地址
  • ${user}: 用户名
  • ${pass}: 密码,如果同名会覆盖密码

HTTP Basic Authutication 协议基本没有使用了,但是在部署内网服务或者快速上线服务,配合 https 也是一个不错的方案。

ngx_http_auth_request_module 模块

收到请求后,生成子请求,通过反向代理技术传递给上游服务。

若上游服务返回的响应码是 2xx ,则继续执行。

若上游服务返回的是 401 或者 403 ,则将响应返回给客户端。

NGX_HTTP_ACCESS_PHASE 阶段生效,默认没有编译进 nginx ,需要通过 --with-http_auth_request_module 编译。

  • auth_request
    • 作用: 配置鉴权 uri ,或者关闭鉴权功能
    • 基本语法: auth_request ${uri} / off;
    • 默认值: auth_request off;
    • 作用域: httpserverlocation
  • auth_request_set
    • 作用: 在鉴权时设置变量值
    • 基本语法: auth_request_set ${variable} ${value};
    • 默认值: 无
    • 作用域: httpserverlocation

satisfy 指令

access 阶段内,如果配置了多种鉴权方式,需要有一个汇总策略,决定是否放行,也就是 satisfy 指令。

  • satisfy
    • 作用: 配置 access 认证规则
    • 基本语法: satisfy all | any;
      • all: 必须所有 access 阶段的模块同意放行,才能结束 access 阶段到下一阶段
      • any: 只要有一个 access 阶段的模块同意放行,就结束 access 阶段到下一阶段
    • 默认值: satisfy all;
    • 作用域: httpserverlocation

各个模块的执行顺序在 ngx_modules.c 文件中:

  1. ngx_http_auth_request_module
  2. ngx_http_auth_basic_module
  3. ngx_http_access_module
location / {
  # 只要一条匹配就放行
  satisfy any;

  # 等待鉴权,鉴权成功放行,否则不放行
  authbasic "test authn_basic";
  authbasic userfile examples/auth.pass;

  # access 直接拒绝
  deny all;
}
location / {
  # 只要一条匹配就放行
  satisfy any;

  # 不需要鉴权,直接放行,因为有模块已经放行了
  authbasic "test authn_basic";
  authbasic userfile examples/auth.pass;

  # access 直接放行
  allow all;
}

precontent 阶段

ngx_http_try_files_module 模块

依次试图访问多个 url 对应的文件(由 root 或者 alias 指令指定)。

当文件存在时直接返回文件内容,如果所有文件都不存在,则按最后一个 URL 结果或者 code 返回

  • try_files
    • 作用: 配置鉴权 uri ,或者关闭鉴权功能
    • 基本语法: try_files ${uri1} [... ${uri N}];
    • 默认值: 无
    • 作用域: serverlocation
server {
  root html/;
  default_type text/plain;

  location /first {
    # 先匹配维护模式展示的静态资源
    try_files /system/maintenance.html
      # 再匹配默认首页
      $uri $uri/index.html $uri.html
      # 最后走到内置的处理流程
      @lasturl;
  }

  location @lasturl {
    return 200 'lasturl!\n';
  }

  location /second {
    # 如果都找不到文件,会返回最后一个 url 以及对应的状态码
    # 也就是返回 /second.html ,状态码为 404
    try_files $uri $uri/index.html $uri.html =404;
  }
}

ngx_http_mirror_module 模块

处理请求时,生成子请求访问其他服务,对子请求的返回值不做处理

  • mirror
    • 作用: nginx 接收到请求后,是否需要上报给指定请求
    • 基本语法: mirror ${uri} / off;
    • 默认值: mirror off;
    • 作用域: httpserverlocation
  • mirror_request_body
    • 作用: 上报时,是否携带请求 body
    • 基本语法: mirror_request_body on / off;
    • 默认值: mirror_request_body on;
    • 作用域: httpserverlocation
location / {
  # 转发到用户日志接口,记录用户访问数据
  mirror /log-access;
  mirror_request_body off;
}

location = /log-access {
  # 对请求头进行装饰,表明记录数据来源,转发给内网的记录服务
  internal;
  proxy_pass http://127.0.0.1:10020$request_uri;
  proxy_pass_request_body off;
  proxy_set_header Content-Length "";
  proxy_set_header X-Original-URI $request_uri;
}

content 阶段

ngx_http_concat_module 模块

阿里开发的 小文件合并模块 ,可以在一次请求中将多个小文件合并成一个响应内容。

使用时,在 URI 后加上 ?? ,通过多个 , 分隔文件,如果还有参数则在末尾通过 ? 添加

例如: http://example.com/static/??foo.css,bar/foobaz.js

  • concat
    • 作用: 是否开启文件合并功能
    • 基本语法: concat on / off;
    • 默认值: concat off;
    • 作用域: httpserverlocation
  • concat_types
    • 作用: 指定允许合并的文件类型
    • 基本语法: concat_types ${MIME-type} ...;
    • 默认值: concat_types text/css application/x-javascript;
    • 作用域: httpserverlocation
  • concat_unique
    • 作用: 是否只允许合并相同类型的文件
    • 基本语法: concat_unique on / off;
    • 默认值: concat_unique on;
    • 作用域: httpserverlocation
  • concat_max_files
    • 作用: 设置一次请求中最多可以合并的文件数量
    • 基本语法: concat_max_files ${number};
    • 默认值: concat_max_files 10;
    • 作用域: httpserverlocation
  • concat_delimiter
    • 作用: 设置合并文件之间的分隔符
    • 基本语法: concat_delimiter ${string};
    • 默认值: NONE
    • 作用域: httpserverlocation
  • concat_ignore_file_error
    • 作用: 是否忽略文件不存在的错误
    • 基本语法: concat_ignore_file_error on / off;
    • 默认值: concat_ignore_file_error off;
    • 作用域: httpserverlocation

ngx_http_index_module 模块

指定访问时返回 index 文件内容

  • index
    • 作用: 配置默认首页文件
    • 基本语法: index ${file 1} [... ${file N}];
    • 默认值: index index.html;
    • 作用域: httpserverlocation

ngx_http_autoindex_module 模块

当 URL 以 / 结尾,并且没有对应的默认首页时,尝试以 htmlxmljsonjsonp 等格式返回目录的目录结构

  • autoindex
    • 作用: 是否开启 autoindex 功能,自动为目录资源生成对应的展示内容
    • 基本语法: autoindex on / off;
    • 默认值: autoindex off;
    • 作用域: httpserverlocation
  • autoindex_extra_size
    • 作用: 是否显示格式化后的文件大小
    • 基本语法: autoindex_extra_size on / off;
    • 默认值: autoindex_extra_size on;
    • 作用域: httpserverlocation
  • autoindex_format
    • 作用: 默认返回的 autoindex 静态资源格式
    • 基本语法: autoindex_format html / xml / json / jsonp;
    • 默认值: autoindex_format html;
    • 作用域: httpserverlocation
  • autoindex_localtime
    • 作用: 静态资源内的时间使用本地时间还是 UTC 时间
    • 基本语法: autoindex_localtime on / off;
    • 默认值: autoindex_localtime off;
    • 作用域: httpserverlocation

static 模块

处理静态资源请求,将 uri 映射到本地的文件路径,读取对应的文件作为 uri 响应结果。

  • alias
    • 作用: 去掉 location 匹配的部分,剩下的部分映射到文件路径中
    • 基本语法: alias ${path};
    • 默认值: 无
    • 作用域: location
  • root
    • 作用: 映射完整的 uri 到文件路径中
    • 基本语法: root ${path};
    • 默认值: root html;
    • 作用域: httpserverlocationlocation 内的 if
  • types
    • 作用: 配置响应的 Content-Type 与文件扩展名的映射
    • 基本语法: types { ${mine-type} ${extension}; };
    • 默认值: types { text/html html; image/gif gif; image/jpeg jpg };
    • 作用域: httpserverlocation
  • default_type
    • 作用: 文件扩展名不匹配时,返回的默认的 Content-Type
    • 基本语法: default_type ${mine-type};
    • 默认值: default_type text/plain;
    • 作用域: httpserverlocation
  • types_hash_bucket_size
    • 作用: 配置存储 types 的哈希表每个元素的大小
    • 基本语法: types_hash_bucket_size ${size};
    • 默认值: types_hash_bucket_size 64;
    • 作用域: httpserverlocation
  • types_hash_max_size
    • 作用: 设置存储 MIME 类型哈希表的最大容量。
    • 基本语法: types_hash_max_size ${size};
    • 默认值: types_hash_max_size 1024;
    • 作用域: httpserverlocation
  • log_not_fount
    • 作用: 找不到文件时,是否输出日志。
    • 基本语法: log_not_fount on / off;
    • 默认值: log_not_fount on;
    • 作用域: httpserverlocation
  • absolute_redirect
    • 作用: 控制重定向时是否使用绝对路径(包含协议、主机名和端口),在访问目录,并且没有以 / 结尾时, Nginx 会自动触发一个 301 重定向
    • 基本语法: absolute_redirect on / off;
    • 默认值: absolute_redirect on;
    • 作用域: httpserverlocation
  • server_name_in_redirect
    • 作用: 控制重定向时是否使用 server_name 指令指定的第一个域名作为重定向地址,未开启时,使用请求头的 Host 字段。
    • 基本语法: server_name_in_redirect on / off;
    • 默认值: server_name_in_redirect off;
    • 作用域: httpserverlocation
  • port_in_redirect
    • 作用: 控制重定向时是否在重定向地址中包含端口号
    • 基本语法: port_in_redirect on / off;
    • 默认值: port_in_redirect on;
    • 作用域: httpserverlocation

static 模块还会提供以下变量:

  • request_filename: 待访问文件的完整路径
  • document_root: 由 uri 和 root/alias 规则生成的目录地址
  • realpath_root: 将 document_root 映射出真实地址,会解析软连接等。

log 阶段

ngx_http_log_module 模块

将 http 请求相关信息记录到日志中。

  • log_format
    • 作用: 定义日志文件格式
    • 基本语法: log_format ${name} [escape=default / json / none] ${string};
    • 默认值: log_format combined "..."; ,默认值太长了,直接看 Nginx 配置文件吧
    • 作用域: http
  • access_log
    • 作用: 定义日志文件存储位置,如果没有配置日志格式名,默认使用默认格式,也就是 combined
    • 基本语法:
      • access_log ${path} [${format} [buffer=${size}] [gzip[=${level}]] [flush=${time}] [if=${condition}]];
        • gzip: 是否开启 gzip 压缩,以及压缩级别,默认为 1
        • buffer: 内存缓冲区的大小,缓冲区满会将日志数据一次性写入磁盘文件, buffer 默认为 64KB
        • flush: 内存缓冲区定时写入磁盘的时间间隔,到达指定时间间隔也会把缓冲区内容写入磁盘
        • if: 满足条件才记录日志
      • access_log off;
    • 默认值: access_log logs/access.log combined;
    • 作用域: httpserverlocationlocation 中的 iflimit_except
  • open_log_file_cache
    • 作用: 配置日志写入功能
    • 基本语法: open_log_file_cache max=${number} [inactive=time] [min_uses=${number}] [valid=${time}];
      • max: 缓存内的最大文件句柄数,超出后进行 LRU 淘汰
      • inactive: 写入日志文化后,保持多久暂不关闭文件句柄,默认 10 秒。
      • min_uses: 在 inactive 时间内,使用次数超过 min_uses 次的,继续持有文件句柄,不关闭文件,默认 1。
      • valid: 超出 valid 事件后,对缓存的日志文件检查是否存在,默认 60 秒。
      • off: 关闭缓存功能。
    • 默认值: open_log_file_cache off;
    • 作用域: httpserverlocation

其他模块

ngx_http_core_module 模块

http 核心模块,处理 http 协议的基础配置和功能

  • resolver

    • 作用: resolver 用于指定 Nginx 进行域名解析时使用的 DNS 服务器地址。当配置上游服务器为域名时,将使用 resolver 配置的 DNS 解析。
    • 基本语法: resolver ${address} ... [valid=${time}] [ipv6=on/off];
      • ${address}: 指定域名解析的 DNS ,支持配置多个 DNS 服务器
      • valid=${time}: 指定 DNS 结果需要缓存多长时间
    • 默认值: 无
    • 作用域: httpserverlocation
  • resolver_timeout

    • 作用: 设置 DNS 解析的超时时间。
    • 基本语法: resolver_timeout ${timeout};
    • 默认值: resolver_timeout 30s;
    • 作用域: httpserverlocation
  • keepalive_requests

    • 作用: 在一个连接中最多跑多少个请求,超出请求数后自动关闭连接
    • 基本语法: keepalive_requests ${number};
    • 默认值: keepalive_requests 100;
    • 作用域: upstream
  • keepalive_timeout

    • 作用: 配置长连接在空闲多长时间自动关闭
    • 基本语法: keepalive_timeout ${timeout};
    • 默认值: keepalive_timeout 75s;
    • 作用域: upstream

HTTP 过滤模块

ngx_http_sub_filter_module 模块

将响应中指定的字符串,替换为新的字符串

  • sub_filter
    • 作用: 声明需要将什么字符串替换为什么,替换时忽略大小写
    • 基本语法: sub_filter ${string} ${replacement};
    • 默认值: 无
    • 作用域: httpserverlocation
  • sub_filter_last_modified
    • 作用: 替换后是否保留 Last-Modified 配置,由于文件已经被修改,默认不会返回 Last-Modified
    • 基本语法: sub_filter_last_modified on / off;
    • 默认值: sub_filter_last_modified off;
    • 作用域: httpserverlocation
  • sub_filter_once
    • 作用: 是否只需要替换一次
    • 基本语法: sub_filter_once on / off;
    • 默认值: sub_filter_once on;
    • 作用域: httpserverlocation
  • sub_filter_types
    • 作用: 针对哪些 Mime-type 进行替换
    • 基本语法: sub_filter_types ${Mime-type} ...; ,支持使用 sub_filter_types *; 进行全量匹配
    • 默认值: sub_filter_types text/html;
    • 作用域: httpserverlocation

ngx_http_addition_filter_module 模块

在响应前或响应后,发起一个子请求,获取需要添加的新内容。

  • add_before_body
    • 作用: 需要在 response body 前插入什么内容
    • 基本语法: add_before_body ${uri};
    • 默认值: 无
    • 作用域: httpserverlocation
  • add_after_body
    • 作用: 需要在 response body 后插入什么内容
    • 基本语法: add_after_body ${uri};
    • 默认值: 无
    • 作用域: httpserverlocation
  • addition_types
    • 作用: 添加文件的 Mime 类型
    • 基本语法: addition_types ${Mime-type} ...;
    • 默认值: addition_types text/html;
    • 作用域: httpserverlocation

ngx_http_referer_module 模块

浏览器会自动发送 Referer 字段,告知服务端目前客户访问的是哪个 url , ngx_http_referer_module 模块可以拒绝不合法的网站访问系统资源,也就是防盗链。

模块提供了 invalid_referer 变量,允许访问时变量值为空,否则为 1 。

  • valid_referers
    • 作用: 哪些 Referer 是合法的
    • 基本语法: valid_referers none / blocked / server_names / ${string} ...;
      • none: 不带 Referer
      • blocked: Referer 缺少对应值
      • server_names: 域名与本机配置的任意 server_name 匹配,就允许访问
      • ${string}: 自定义 Referer ,支持字符串、正则、 *.he110.sitehe110.site/*
    • 默认值: 无
    • 作用域: serverlocation
  • referer_hash_bucket_size
    • 作用: 记录 Referer 的哈希表块大小
    • 基本语法: referer_hash_bucket_size ${size};
    • 默认值: referer_hash_bucket_size 64;
    • 作用域: serverlocation
  • referer_hash_max_size
    • 作用: 记录 Referer 的哈希表最大容量
    • 基本语法: referer_hash_max_size ${size};
    • 默认值: referer_hash_max_size 2048;
    • 作用域: serverlocation

ngx_http_secure_link_module 模块

Referer 可以在手动发出请求时在客户端修改,所以依据 Referer 进行防盗链并不可靠, Nginx 提供了 secure_link 模块,能够完成更加可靠的防盗链功能。

secure_link 工作过程为: 服务器或 Nginx 生成加密后的安全链接 url ,返回给客户端。客户端使用安全链接 url 访问 Nginx , Nginx 用 secure_link 变量判断是否验证通过。

secure_link 会将 URI 、 用户信息(比如源 IP ) 、 时间戳 、 密钥等信息,进行 Hash 计算,获得一个安全的 URL ,该 URL 对应的原始内容仅加密服务器以及 Nginx 持有,通过解密可以验证访客身份是否匹配,如果不匹配则拒绝访问。

  • secure_link

    • 作用: 配置从哪些参数中取得 Hash 值与过期时间
    • 基本语法: secure_link ${hash},${expires};
    • 默认值: 无
    • 作用域: httpserverlocation
  • secure_link_md5

    • 作用: 定义生成哈希值的规则,一般包含时间戳和密钥
    • 基本语法: secure_link_md5 "${加密的参数}";
    • 默认值: 无
    • 作用域: httpserverlocation
  • secure_link_secret

    • 作用: 简单的安全链接生成,无法进行过期判断
    • 基本语法: secure_link_secret ${secret_key};
    • 默认值: 无
    • 作用域: location
  • $secure_link: 记录 secure_link 校验结果

    • "": 验证不通过
    • 0: URL 过期
    • 1: 通过
  • $secure_link_expires: 哈希中记录的时间戳的值

ngx_http_map_module 模块

基于已有变量,映射出新的变量,类似于 switch 语句

  • map
    • 作用: 定义变量的映射规则,将 ${string} 映射成 $variable 变量
    • 基本语法: map ${string} ${$variable} { };
    • 默认值: 无
    • 作用域: http
  • map_hash_bucket_size
    • 作用: 记录 Map 的哈希表块大小
    • 基本语法: map_hash_bucket_size ${size};
    • 默认值: map_hash_bucket_size 32|64|128;
    • 作用域: http
  • map_hash_max_size
    • 作用: 记录 Map 的哈希表最大容量
    • 基本语法: map_hash_max_size ${size};
    • 默认值: map_hash_max_size 2048;
    • 作用域: http
map $http_host $name {
  hostnames;

  # 类似 case 中的 default 语句
  default 0;

  # $http_host 变量匹配了左边的表达式,就设置 $name 为右边的值
  # 匹配规则可以参考 server_name 的匹配顺序
  ~map\.test\w+\.org.cn 1;
  *.example.org.cn 2;
  map.example.tech 3;
  map.example.* 4;
}

map $http_user_agent $mobile {
  default 0;
  "~Opera Mini" 1;
}

ngx_http_split_clients_module 模块

与 map 一样,可以基于已有变量创建新变量,主要用于分流功能,也就是 AB 测试、灰度上线。

具体行为是根据已有的字符串、变量、变量 + 字符串,基于 MurmurHash2 算法,生成一个 32 位整型的 Hash 数字。按照 32 位整型最大值为 2 ^ 32 - 1 ,求 Hash 除以最大值,所得的百分比值。

在 case 规则中,声明不同的百分比值范围,根据上述规则计算出来的百分比值落在哪个区间,就将新变量设置为对应的参数,而 * 意味着匹配剩余所有范围,也就是 default

  • split_clients
    • 作用: 将用户进行分流,主要用于 AB 测试、灰度体验等,所有百分比值之和不能超过 100%
    • 基本语法: split ${string} ${$variable} { };
    • 默认值: 无
    • 作用域: http
# 按照请求头的 username 参数进行 Hash 计算
split_clients "${http_username}" $variant {
  # 统计区间 [0, 0.5%]
  0.5% A;
  # 统计区间 [0.5%, 1%]
  0.5% B;
  # 统计区间 [1%, 10%]
  9% C;
  # 统计区间 [10%, 30%]
  20% D;
  # 统计区间 [30%, 100%]
  * E;
}

ngx_http_geo_module 模块

geo 模块需要基于客户端的 IP 地址与端口,计算出新变量

  • geo
    • 作用: 使用 IP + 掩码确定 IP 范围,基于不同的 IP 范围使用不同的变量。
    • 基本语法: geo: [${address}] $variable {}
      • ${address}: 如果省略了,默认使用 realip 模块提供的 $remote_addr 作为 IP 地址。如果网段重叠了,按照最长匹配选择网段。
    • 默认值: 无
    • 作用域: http
geo $country {
  # 配置默认值
  default CN;

  # 在 remote_addr 中跳过可信地址
  # 指定可信地址。
  proxy 1.1.1.1;
  # 开启递归查找。此时 remote_addr 在解析 X-Forwarded-For 时会跳过可信地址
  proxy_recursive on;

  127.0.0.0/24 US;
  127.0.0.1/32 RU;
  10.0.0.1/32 RU;
  192.168.0.1/32 UK;
}

ngx_http_geoip_module 模块

基于 MaxMind 数据库,将客户端 IP 地址映射到具体的地理位置,需要先 安装 MaxMind

  • geoip_contry

    • 作用: 配置 MaxMind 国家数据库文件地址
    • 基本语法: geoip_contry: ${file}
    • 默认值: 无
    • 作用域: http
  • geoip_proxy

    • 作用: 提供可信地址
    • 基本语法: geoip_proxy: ${address} / ${CIDR}
    • 默认值: 无
    • 作用域: http
  • geoip_city

    • 作用: 配置 MaxMind 城市数据库文件地址
    • 基本语法: geoip_city: ${file}
    • 默认值: 无
    • 作用域: http
  • $geoip_contry_code: 两位字母的国家代码,比如 CN 、 US 等。

  • $geoip_contry_code3: 三位字母的国家代码,比如 CHN 、 USA 等。

  • $geoip_contry_name: 国家名称,比如 “China” 、 “United States” 等。

geoip_city 除了上面三个变量之外,还支持:

  • $geoip_latitude: 纬度
  • $geoip_longitude: 经度
  • $geoip_city_continent_code: 属于哪个州,比如 AS 、 RU
  • $geoip_region: 州或者省的编码
  • $geoip_region_name: 州或者省的名称,比如 Zhejiang
  • $geoip_city: 城市名
  • $geoip_portal_code: 邮编号码
  • $geoip_area_code: 仅美国使用的电话区号
  • $geoip_dma_code: 仅美国使用的 DMA 编号

Nginx 变量

模块变量

preconfiguration 中,模块会定义模块变量,提供给自身或者其他模块使用。

本质上来说,定义的模块变量只是一套获取变量值的规则,当请求到来时,根据规则解析出变量值,再传递给各个模块。

由此衍生出变量的几个特性:

  • 惰性求值: 变量本身只是一套获取数据的规则,在变量没有被使用时,不会求值。
  • 时刻变化: 在一个请求处理过程中,每个使用的模块都会按照获取变量值的方式去获取值,所以该值随时可能变化。

为了提升性能, Nginx 提供了缓存变量值的 Hash 表:

  • variables_hash_bucket_size ${size};: 配置变量缓存哈希表的每个值的大小,默认为 64 字节,只能用于 http 模块。
  • variables_hash_max_size ${size};: 配置变量缓存哈希表的容量,默认为 1024 ,只能用于 http 模块。

http 框架提供的变量

HTTP 请求相关的变量

  • arg_参数名: URL中某个具体参数的值
  • args: 全部 URL 参数,也就是 query 不分
  • query_string: 与 args 变量完全相同
  • is_args: 如果请求 URL 中有参数则返回,否则返回空
  • content_length: HTTP 请求中标识包体长度的 Content-Length 头部的值
  • content_type: 标识请求包体类型的 Content-Type 头部的值
  • uri: 请求的 URI ( 不同于 URL ,不包括 ? 后的参数 )
  • document_uri: 与 uri 完全相同
  • request_uri: 请求的 URL ( 包括 URI 以及完整的参数 )
  • scheme: 协议名,例如 HTTP 或者 HTTPS
  • request_method: 请求方法,例如 GET 或者 POST
  • request_length: 所有请求内容的大小,包括请求行、头部、包体等
  • remote_user: 由 HTTP Basic Authentication 协议传入的用户名
  • request_body_file: 临时存放请求包体的文件。 如果包体非常小则不会存文件,配置 client_body_in_file_only 强制所有包体存入文件,且可决定是否删除。
  • request_body: 请求中的包体,这个变量当且仅当使用反向代理,且设定用内存暂存包体时才有效
  • request: 原始的 url 请求,含有方法与协议版本,例如 GET /?a=1&b=22 HTTP/1.1
  • Host:
    1. 从请求行中获取
    2. 如果含有 Host 请求头,使用请求头的值替代请求行的值
    3. 如果都没有值,使用匹配的 server_name
  • http_${header}: 获取具体的请求头的值,以下请求头例外,会被 Nginx 额外处理,非原始值:
    • http_host
    • http_user_agent
    • http_referer
    • http_via
    • http_x_forwarded_for
    • http_cookie

TCP 连接相关的变量

  • binary_remote_addr: 客户端地址的整型格式,对于 IPv4 是 4 字节,对于 IPv6 是 16 字节
  • connection: 递增的连接序号
  • connection_requests: 当前连接上执行过的请求数,对 keepalive 连接有意义
  • remote_addr: 客户端地址,字符串格式
  • remote_port: 客户端端口
  • proxy_protocol_addr: 若使用了 proxy_protocol 协议则返回协议中的地址,否则返回空
  • proxy_protocol_port: 若使用了 proxy_protocol 协议则返回协议中的端口,否则返回空
  • server_addr: 服务器端地址
  • server_port: 服务器端端口
  • TCP_INFO: tcp 内核层参数,包括 $tcpinfo_rtt, $tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space
  • server_protocol: 服务器端协议,例如 HTTP/1.1

Nginx 处理请求过程中产生的变量

  • request_time: 请求处理到现在的耗时,单位为秒,可以利用小数点,精确到毫秒
  • server_name: 匹配上请求的 server_name 值
  • https: 如果开启了 TLS/SSL ,则返回 on ,否则返回空
  • request_completion: 若请求处理完则返回 OK ,否则返回空
  • request_id: 以 16 进制输出的请求标识 id ,该 id 共含有 16 个字节,是随机生成的
  • request_filename: 待访问文件的完整路径
  • document_root: 由 URI 和 root / alias 规则生成的文件夹路径
  • realpath_root: 将 document_root 中的软链接等换成真实路径
  • limit_rate: 返回客户端响应时的速度上限,单位为每秒字节数。可以通过 set 指令修改对请求产生效果

发送 HTTP 响应相关的变量

  • body_bytes_sent: 响应中 body 包体的长度特殊处理
  • bytes_sent: 全部 http 响应的长度,包含 header
  • status: http 响应中的返回码
  • sent_trailer_${name}: 把响应结尾内容里值返回
  • sent_http_${header}: 响应中某个具体头部的值,以下字段被特殊处理过
    • sent_http_content_type
    • sent_http_content_length
    • sent_http_location
    • sent_http_last_modified
    • sent_http_connection
    • sent_http_keep_alive
    • sent_http_transfer_encoding
    • sent_http_cache_control
    • sent_http_link

Nginx 系统变量

  • time_local: 以本地时间标准输出的当前时间,例如 18/Apr/2025:17:26:31+0800
  • time_iso8601: 使用 ISO8601 标准输出的当前时间,例如 2025-04-18T17:26:31+08:00
  • nginx_version: Nginx 版本号
  • pid: 所属 worker 进程的进程 id
  • pipe: 使用了管道则返回 p ,否则返回 .
  • hostname: 所在服务器的主机名,与 hostname 命令输出一致
  • msec: 1970 年 1 月 1 日到现在的时间,单位为秒,小数点后精确到毫秒

负载均衡

由于对于可靠性的要求,往往需要额外冗余一部分提供相同服务的服务器,可以保证在设备升级、维护、扩容等情况下依旧不需要停机。

Nginx 作为应用网关程序,也内置了负载均衡需要的各种能力:

  • 水平扩容: 即增加冗余服务器,减轻各个服务器的压力,一般基于 Round-Robin 或者 least-connected 等算法进行请求的分发。
    • 用途: 一般在业务扩张的初期阶段,通过增加服务器成本快速解决问题,满足业务需要
    • 优点: 操作简单,初期能有不错的效果
    • 缺点: 当数据量上来了,单个操作的时间需要大量耗时后,单台服务器难以及时处理完成该请求
  • 垂直扩展: 即将服务按照功能,拆分到不同的服务器或者集群上,一般基于 URL 进行识别处理即可,类似于 FaaS 或者 Serverless
    • 用途: 一般在业务较为稳定阶段,作为架构优化的时候进行的保障性能的解决方案
    • 优点: 可以应对大数据量下的并发操作
    • 缺点: 函数拆分与调用较为复杂,底层还涉及到数据库的分库分表设计
  • Z 轴扩展: 即将访问的用户按照 IP 地址或者其他指纹信息,映射到某个特定的服务或者集群
    • 用途: 将用户按照指纹划分群体,一般用于数据隔离场景,比如国内外隔离、内测版本等
    • 优点: 操作难度适中,能缓解服务器压力与数据压力
    • 缺点: 更像是妥协方案,数据不可能永远隔离着

以上三种方式往往组合使用,并不会单纯应用某一技术,例如: 通过用户群切分( z 轴扩展)转发到不同的集群(水平扩展),将通用的,数据量大的模块单独拆分为 FaaS (垂直扩展), FaaS 内部还是一个集群(水平扩展)

负载均衡依靠的就是反向代理技术, Nginx 支持 七层反向代理四层反向代理 ,可以按照业务需求选择。

upstream

提供了 Round-Robin 算法

  • upstream
    • 作用: 配置一组上游服务器
    • 基本语法: upstream ${name} {}
    • 默认值: 无
    • 作用域: http
  • server
    • 作用: 指定 upstream 包含哪些地址
    • 基本语法: server ${address} [${paramters}]
      • ${address}: 包含域名、 IP 、 unix socket 等。 域名 、 IP 如果不加端口,默认使用 80 端口。
      • ${paramters}:
        • backup: 指定当前 server 为备份服务,仅当非备份 server 不可用时,才会转发给该 server
        • down: 标记该服务已经下线,不再服务
    • 默认值: 无
    • 作用域: upstrea,

上游服务器长连接

可以使用 ngx_http_upstream_keepalive_module 模块,完成对上游服务的长连接配置,在处理大量请求时,会有比较明显的性能提升。

一般会先通过以下配置,预先准备好开启长连接的条件:

# http 1.1 开始才支持长连接,所以指定要求使用 1.1
proxy_http_version 1.1;
# 手动指定一定要长连接,避免客户端发来的请求没有指定长连接
proxy_set_header Connection "";
  • keepalive
    • 作用: 最多保持多少个空闲长连接
    • 基本语法: keepalive ${connections};
    • 默认值: 无
    • 作用域: upstream
  • keepalive_requests
    • 作用: 在一个连接中最多跑多少个请求,超出请求数后自动关闭连接
    • 基本语法: keepalive_requests ${number};
    • 默认值: keepalive_requests 100;
    • 作用域: upstream
  • keepalive_timeout
    • 作用: 配置长连接在空闲多长时间自动关闭
    • 基本语法: keepalive_timeout ${timeout};
    • 默认值: keepalive_timeout 60s;
    • 作用域: upstream

加权 Round-Robin 算法

使用加权轮询的方式访问 server 指令指定的上游服务,简单理解就是所有权重加起来作为一个周期,权重即为该服务在这个周期内需要处理多少个请求。

一般其他算法在失效之后,也会转为 加权 Round-Robin 算法。

该算法默认集成在 Nginx 的 upstream 中,不可移除或者更改,提供了以下指令:

  • weight: 服务访问的权重,默认为 1
  • max_conns: server 的最大并发连接数,仅作用于单 worker 进程。默认是 0 ,表示没有限制。
  • fail_timeout: 单位为秒,默认值为 10 秒。具有 2 个功能:
    • 指定记录 faile 的时间窗口。
    • 将 server 标记为 fail 后,将 server 断开多久的时间
  • max_fails: 在 fail_timeout 时间段内,最大的失败次数。当达到最大失败时,会在 fail_timeout 秒内这台 server 不允许再次被选择。

Hash 算法

IP Hash 算法

该算法位于 ngx_http_upstream_ip_hash_module

使用客户端 IP 地址作为 Hash 算法的参数,将不同的 IP 地址映射给不同的上游服务器处理,此时 server 配置的权重会失效,不再按照权重轮询。

如果对应的上游服务已经挂起,依旧会转发给该服务,因为选择的唯一标准就是 Hash 值。

如果移除该服务,则会导致 Hash 算法变更,导致用户访问了新的服务,随之而来的就是大量的缓存失效,上游服务可能直接被击穿。

所以相对应的,需要水平扩容时,也会导致 Hash 算法修改,导致用户访问了新的服务,随之而来的就是大量的缓存失效,上游服务可能直接被击穿。

对于 IPv4 地址使用前 3 个字节作为关键字,对 IPv6 则使用完整地址。

可以基于 realip 修改 Hash 算法的参数,因为 Hash 算法的地址取的事 realip 提供的变量。

  • ip_hash
    • 作用: 开启 IP Hash 算法
    • 基本语法: ip_hash;
    • 默认值: 无
    • 作用域: upstream

upstream Hash 算法

该算法位于 ngx_http_upstream_hash_module

参考 IP Hash 算法,但是参数不局限在 IP 地址,可以使用任意变量、字符串作为 Hash 参数

  • hash
    • 作用: 开启 Hash 算法,并配置其参数( ${key}
    • 基本语法: hash ${key} [consistent];
      • consistent: 开启一致性 Hash 算法
    • 默认值: 无
    • 作用域: upstream

一致性 Hash 算法

一致性 Hash 算法可以缓解前面提到的水平扩容导致的 Hash 算法变更,随之而来的用户访问服务器变化问题。

一致性 Hash 算法将 Hash 后的值域,设置的比较大,比如 [0, 2 ^ 32] 这个区间,用户参数 Hash 后将落到这个区间内,同时对上游服务,也进行 Hash 。

在用户 Hash 与服务 Hash 不匹配时,会沿着区间往数据增大的方向查找,找到的第一个服务,就为这个用户提供服务。

当查找越过区间最大值,也就是 2 ^ 32 时,重新从 0 开始,构成一个循环区间。

当某台服务器挂起时,只有该服务器上的用户被影响,缓存失效,所有用户会移交给下一个服务器,所以单点服务器的负载不能太高,否则可能单点被击穿。

当扩容时,可以很方便的进行扩容,新的用户会在区间查找时,自动索引到新的服务器上。

最少连接算法

该算法位于 ngx_http_upstream_least_conn_module

会从所有上游服务器中,找出当前并发连接数最少的一台服务器,将请求转发给它。

如果找到了多台服务器,按照 加权 Round-Robin 算法处理。

  • least_conn
    • 作用: 开启最少连接算法
    • 基本语法: least_conn;
    • 默认值: 无
    • 作用域: upstream

使用共享内存共享负载均衡相关信息

以上介绍的算法,均只对单个 worker 生效,默认负载均衡策略没有写入共享内存中。

需要使用 ngx_http_upstream_zone_module 提供的 zone 指令,才能将信息放到共享内存中,对所有 worker 进程生效。

  • zone
    • 作用: 分配出共享内存,将其他 upstream 模块定义的负载均衡策略数据、运行时每个上游服务的状态数据存放到共享内存中,让所有 Nginx worker 共享。
    • 基本语法: least_conn ${name} [${size}];
    • 默认值: 无
    • 作用域: upstream

upstream 中各个模块的执行顺序

  1. ngx_http_upstream_hash_module
  2. ngx_http_upstream_ip_hash_module
  3. ngx_http_upstream_least_conn_module
  4. ngx_http_upstream_random_module
  5. ngx_http_upstream_keepalive_module
  6. ngx_http_upstream_zone_module

upstream 提供的变量

upstream_addr: 上游服务器的IP地址,格式为可读的字符串,例如 127.0.0.1:8012 upstream_connect_time: 与上游服务建立连接消耗的时间,单位为秒,精确到毫秒 upstream_header_time: 接收上游服务发回响应中 http 头部所消耗的时间,单位为秒,精确到毫秒 upstream_response_time: 接收完整的上游服务响应所消耗的时间,单位为秒,精确到毫秒 upstream_http_${name}: 从上游服务返回的响应头部的值

反向代理

反向代理模块相关功能都在 ngx_http_proxy_module 模块中,整个反向代理的流程如下图:

content 阶段

指定哪些内容需要被反向代理

  • proxy_pass
    • 作用: 开启反向代理
    • 基本语法: proxy_pass ${url};
    • 默认值: 无
    • 作用域: locationlocation 中的 iflimit_except

${url} 需要满足以下条件:

  1. 必须以 http:// / https:// 开头
  2. 协议之后跟 域名 、 IP 、 unix socket 、 upstream name 等,域名与 IP 后可以接端口,如果省略默认为 80
  3. 最后是可选的 uri ,也就是浏览器地址中的 path 以及之后的部分,可以通过携带 uri 将 Nginx 参数传递给上游服务,比如 request id 等。
    • 如果不携带 uri ,请求中的 URL 会直接发给上游服务,一般在 location 采用正则、 @${name} 时使用
    • 如果携带 uri ,请求中的 URL 匹配的部分,会被替换为 uri

生成 http 头与包体阶段

修改请求行,可以使用以下命令:

  • proxy_method
    • 作用: 修改请求的方法
    • 基本语法: proxy_method ${method};
    • 默认值: 无
    • 作用域: httpserverlocation
  • proxy_http_version
    • 作用: 指定 http 版本号,不支持将 http2 / http3 代理到上游服务器中。
    • 基本语法: proxy_http_version 1.0 / 1.1;
    • 默认值: 无
    • 作用域: httpserverlocation

修改请求头,可以使用以下命令:

  • proxy_set_header
    • 作用: 设置请求头,将会覆盖用户发来的请求头信息
    • 基本语法: proxy_set_header ${filed} ${value};
    • 默认值: 默认会修改 Host 与 Connection ,默认会更正 Host 的值,同时关闭 keepalive
      • proxy_set_header Host $proxy_host;: 如果 $proxy_host 值为空,该命令不会添加,还是保留请求头中的 Host
      • proxy_set_header Connection close;
    • 作用域: httpserverlocation
  • proxy_pass_request_headers
    • 作用: 是否要将用户发来的请求头发送给上游服务
    • 基本语法: proxy_pass_request_headers on / off;
    • 默认值: proxy_pass_request_headers on;
    • 作用域: httpserverlocation

修改请求包体,可以使用以下命令:

  • proxy_set_body
    • 作用: 设置请求包体值,将会覆盖用户发来的请求头信息
    • 基本语法: proxy_set_body ${value};
    • 默认值: 无
    • 作用域: httpserverlocation
  • proxy_pass_request_body
    • 作用: 是否要将用户发来的请求包体发送给上游服务
    • 基本语法: proxy_pass_request_body on / off;
    • 默认值: proxy_pass_request_body on;
    • 作用域: httpserverlocation

读取请求包体阶段

请求包体读取有两种方式:

  • 通过流式传输,边读边给上游服务发

  • 一次性读入,再传递给上游服务

  • proxy_request_buffering

    • 作用: 是否要接收完用户发来的请求包体,再传递给上游服务
    • 基本语法: proxy_request_buffering on / off;
      • on: 用户网速较慢、上游处理并发能力较差、高吞吐量场景下建议开启
      • off: 需要及时响应、降低 Nginx 磁盘 I/O 则关闭该功能,同时关闭后对 proxy_next_upstream 指令会有影响
    • 默认值: proxy_request_buffering on;
    • 作用域: httpserverlocation

读取包体相关配置

  • client_body_buffer_size
    • 作用: 分配接收包体的 buffer 块大小
    • 基本语法: client_body_buffer_size ${size};
      • 如果处理请求头的时候已经接收完包体了,就不需要额外分配
      • 如果剩余包体长度小于 client_body_buffer_size ,需要多少分配多少
      • 如果超出,以 client_body_buffer_size 为单位,创建不同的 buffer 块读取包体
    • 默认值: client_body_buffer_size 8k|16k;
    • 作用域: httpserverlocation
  • client_body_in_single_buffer
    • 作用: 是否将所有 body 都塞到一个缓冲区中,需要考虑包体体积,开启后才能使用 $request_body 变量
    • 基本语法: client_body_in_single_buffer on / off;
    • 默认值: client_body_in_single_buffer off;
    • 作用域: httpserverlocation
  • client_max_body_size
    • 作用: 限制包体的最大长度,通过 Content-Length 字段校验,校验失败返回 413 状态码
    • 基本语法: client_max_body_size ${size};
    • 默认值: client_max_body_size 1m;
    • 作用域: httpserverlocation

读取包体后,会通过 I/O 缓存到磁盘,在后续需要时再从磁盘上读,缓存的文件就是临时文件,通过以下命令控制:

  • client_body_temp_path
    • 作用: 配置临时文件路径,通过 level 将文件 Hash 按位分割,存放到不同子目录中。由于 Linux 文件名都放到目录中记录,所以在一个目录中放大量文件会影响读写性能。
    • 基本语法: client_body_temp_path ${path} [level1 [level2 [level3]]];
    • 默认值: client_body_temp_path client_body_temp;
    • 作用域: httpserverlocation
  • client_body_in_file_only
    • 作用: 包体是否必须放到文件中
    • 基本语法: client_body_in_file_only on | clean | off;
      • on: 必须写入文件,在处理完成后不删除
      • clean: 必须写入文件,在处理完成后自动删除
      • off: 在内存操作即可,不需要写入文件
    • 默认值: client_body_in_file_only off;
    • 作用域: httpserverlocation
  • client_body_timeout
    • 作用: 配置读取包体的最长时间。如果一个包体在指定时间内都读取不完,返回 408 超时错误
    • 基本语法: client_body_timeout ${time};
    • 默认值: client_body_timeout 60s;
    • 作用域: httpserverlocation

与上游服务建立连接

  • proxy_connect_timeout
    • 用法: 与上游服务器建立连接的超时时间,只要连接建立超时,都是返回 502 错误码
    • 基本语法: proxy_connect_timeout ${time};
    • 默认值: proxy_connect_timeout 60s;
    • 作用域: httpserverlocation
  • proxy_next_upstream
    • 用法: 在与上游服务器建立连接失败后,更换上游服务器
    • 基本语法: proxy_next_upstream ${http_502} | ..;
    • 默认值: proxy_next_upstream error timeout;
    • 作用域: httpserverlocation
  • proxy_socket_keepalive
    • 用法: 是否开启 TCP keepalive ,通过探测包机制,在空闲时与上游服务通信探活,在上游服务关闭或者挂起时自动关闭连接
    • 基本语法: proxy_socket_keepalive on | off;
    • 默认值: proxy_socket_keepalive off;
    • 作用域: httpserverlocation
  • keepalive
    • 作用: 最多保持多少个空闲长连接
    • 基本语法: keepalive ${connections};
    • 默认值: 无
    • 作用域: upstream
  • keepalive_requests
    • 作用: 在一个连接中最多跑多少个请求,超出请求数后自动关闭连接
    • 基本语法: keepalive_requests ${number};
    • 默认值: keepalive_requests 100;
    • 作用域: upstream
  • proxy_bind
    • 作用: 修改 TCP 连接中,包中的源 IP
      • 有多个 IP 时,指定通信使用的 IP 地址,避免使用网络隔离的 IP 导致通信异常,例如: proxy_bind $remote_addr;
      • 使用非本机地址时需要添加 transparent ,例如: proxy_bind $remote_addr transparent;
    • 基本语法: proxy_bind ${address} [transparent] | off;
    • 默认值: 无
    • 作用域: httpserverlocation
  • proxy_ignore_client_abort
    • 用法: 在客户端 abort 请求后,是否不同步给上游服务,默认关闭,也就是同步
    • 基本语法: proxy_ignore_client_abort on | off;
    • 默认值: proxy_ignore_client_abort off;
    • 作用域: httpserverlocation
  • proxy_send_timeout
    • 用法: 向上游服务器发送请求时的超时时间
    • 基本语法: proxy_send_timeout ${time};
    • 默认值: proxy_send_timeout 60s;
    • 作用域: httpserverlocation

接收上游服务响应

接收上游的响应头

  • proxy_buffer_size
    • 作用: 限制了接收响应头的最大值
    • 基本语法: proxy_buffer_size ${size};
    • 默认值: proxy_buffer_size 4k|8k;
    • 作用域: http, server, location

接收上游的响应包体

  • proxy_buffers

    • 作用: 内存中能够存储的 body 大小,超出大小后会写入磁盘中
    • 基本语法: proxy_buffers ${number} ${size};
    • 默认值: proxy_buffers 8 4k|8k;
    • 作用域: httpserverlocation
  • proxy_buffering

    • 作用: 是否要接收完成上游发来的请求包体,再返回给客户端
    • 基本语法: proxy_buffering on | off;
    • 默认值: proxy_buffering on;
    • 作用域: httpserverlocation
  • proxy_max_temp_file_size

    • 作用: 限制写入文件的最大值,会限制响应包体积
    • 基本语法: proxy_max_temp_file_size ${size};
    • 默认值: proxy_max_temp_file_size 1024m;
    • 作用域: httpserverlocation
  • proxy_temp_file_write_size

    • 作用: 每次向磁盘中写入多少内容
    • 基本语法: proxy_temp_file_write_size ${size};
    • 默认值: proxy_temp_file_write_size 8k|16k;
    • 作用域: httpserverlocation
  • proxy_temp_path

    • 作用: 临时存放响应包体的目录
    • 基本语法: proxy_temp_path ${path} [level1 [level2 [level3]]];
    • 默认值: proxy_temp_path proxy_temp;
    • 作用域: httpserverlocation
  • proxy_busy_buffers_size

    • 作用: 在接收指定大小后立即将该包体传输给客户端
    • 基本语法: proxy_busy_buffers_size ${size};
    • 默认值: proxy_busy_buffers_size 8k|16k;
    • 作用域: httpserverlocation

限制与上游服务的传输速度的命令有:

  • proxy_read_timeout
    • 作用: TCP 两次读取超时时间,在接收响应时,并不是一次性就能接收到,需要多次读取。该配置用于约束每次读取之间的时间间隔,超时则返回 502 状态码。
    • 基本语法: proxy_read_timeout ${time};
    • 默认值: proxy_read_timeout 60s;
    • 作用域: httpserverlocation
  • proxy_limit_rate
    • 作用: 限制读取上游服务响应的速度,为 0 不限制
    • 基本语法: proxy_limit_rate ${rate};
    • 默认值: proxy_limit_rate 0;
    • 作用域: httpserverlocation

持久化响应文件:

  • proxy_store_access
    • 作用: 存储文件时,使用什么样的所属组、权限
    • 基本语法: proxy_store_access users:permissions ...;
    • 默认值: proxy_store_access user:rw;
    • 作用域: httpserverlocation
  • proxy_store
    • 作用: 是否开启持久化文件,配置为 string 会指定存放位置
    • 基本语法: proxy_store on | off | ${string};
    • 默认值: proxy_store off;
    • 作用域: httpserverlocation

返回响应

关于响应头的部分:

  • proxy_ignore_headers
    • 作用: 某些响应头部可以改变 Nginx 的行为,使用 proxy_ignore_headers 可以禁止它们生效
    • 基本语法: proxy_ignore_headers ${field} ...;
    • 默认值: 无
    • 作用域: httpserverlocation

涉及的响应头有:

  • X-Accel-Redirect: 由上游服务指定在 Nginx 内部重定向,控制请求的执行
  • X-Accel-Limit-Rate: 由上游设置发往客户端的速度限制,等同 limit_rate 指令
  • X-Accel-Buffering: 由上游控制是否缓存上游的响应
  • X-Accel-Charset: 由上游控制 Content-Type 中的 Charset

缓存相关:

  • X-Accel-Expires: 设置响应在 Nginx 中的缓存时间,单位秒; @ 开头表示一天内某时刻
  • Expires: 控制 Nginx 缓存时间,优先级低于 X-Accel-Expires
  • Cache-Control: 控制 Nginx 缓存时间,优先级低于 X-Accel-Expires
  • Set-Cookie: 响应中出现 Set-Cookie 则不缓存,可通过 proxy_ignore_headers 禁止生效
  • Vary: 响应中出现 Vary: * 则不缓存,同样可禁止生效

隐藏部分客户端用不到的响应头:

  • proxy_hide_header
    • 作用: 某些响应头部客户端用不上,使用 proxy_hide_header 可以将它们隐藏
    • 基本语法: proxy_hide_header ${field} ...;
    • 默认值: 默认忽略以下内容
      • Date: 由 ngx_http_header_filter_module 过滤模块填写,值为 Nginx 发送响应头部时的时间
      • Server: 由 ngx_http_header_filter_module 过滤模块填写,值为 Nginx 版本
      • X-Pad: 通常是 Apache 为避免浏览器 BUG 生成的头部,默认忽略
      • X-Accel-${xxx}: 用于控制 Nginx 行为的响应,不需要向客户端转发
    • 作用域: httpserverlocation
  • proxy_pass_header
    • 作用: 对于被 proxy_hide_header 过滤掉的字段,如果希望发送给客户端,就通过此命令覆盖
    • 基本语法: proxy_pass_header ${field};
    • 默认值: 无
    • 作用域: httpserverlocation

修改 Set-Cookie:

  • proxy_cookie_domain
    • 作用: 修改 Set-Cookie 的域名
    • 基本语法:
      • proxy_cookie_domain off;
      • proxy_cookie_domain ${domain} ${replacement};
    • 默认值: proxy_cookie_domain off;
    • 作用域: httpserverlocation
  • proxy_cookie_path
    • 作用: 修改 Set-Cookie 的子路径
    • 基本语法:
      • proxy_cookie_path off;
      • proxy_cookie_path ${path} ${replacement};
    • 默认值: proxy_cookie_path off;
    • 作用域: httpserverlocation

修改 location:

  • proxy_redirect
    • 作用: 修改 location 重定向的位置
    • 基本语法:
      • proxy_redirect default;
      • proxy_redirect off;
      • proxy_redirect ${redirect} ${replacement};
    • 默认值: proxy_redirect default;
    • 作用域: httpserverlocation

处理上游错误

上游响应失败时,只要没有开始向客户端发送内容,就可以在内部对错误进行处理:

  • proxy_next_upstream
    • 作用: 在上游响应失败后,可以拦截对应错误,换个服务重试
    • 基本语法: proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...;
      • error: Nginx 与上游服务通信的全链路中,发生错误时生效
      • timeout: 通信超时时生效
      • invalid_header: 后端返回头部不合法时生效
      • http_${code}: 后端返回指定状态码时生效
      • off: 不开启功能
    • 默认值: proxy_next_upstream error timeout;
    • 作用域: httpserverlocation
  • proxy_next_upstream_timeout
    • 作用: 重选上游服务处理时的超时时间
    • 基本语法: proxy_next_upstream_timeout time;
    • 默认值: proxy_next_upstream_timeout 0;
    • 作用域: httpserverlocation
  • proxy_next_upstream_tries
    • 作用: 最多重试次数
    • 基本语法: proxy_next_upstream_tries number;
    • 默认值: proxy_next_upstream_tries 0;
    • 作用域: httpserverlocation

使用 error_page 处理上游返回的错误码:

  • proxy_intercept_errors
    • 作用: 默认 Nginx 不会使用 error_page 处理上游服务的错误码,通过该命令可以修改这点
    • 基本语法: proxy_intercept_errors on | off;
    • 默认值: proxy_intercept_errors off;
    • 作用域: httpserverlocation

双向认证

Nginx 上游为 https 时,会使用双向认证,两端互相进行 https 校验。

Nginx 作为上游时

指定自身使用的证书:

  • ssl_certificate
    • 作用: 配置自身在通信时使用的 TLS 证书
    • 基本语法: ssl_certificate ${file};
    • 默认值: 无
    • 作用域: httpserver
  • ssl_certificate_key
    • 作用: 配置自身在通信时使用的 TLS 私钥
    • 基本语法: ssl_certificate_key ${file};
    • 默认值: 无
    • 作用域: httpserver

对下游证书进行验证:

  • ssl_verify_client
    • 作用: 是否对下游证书进行验证
    • 基本语法: ssl_verify_client on | off | optional | optional_no_ca;
      • optional: 传了证书才验证
    • 默认值: ssl_verify_client off;
    • 作用域: httpserver
  • ssl_client_certificate
    • 作用:
    • 基本语法: ssl_client_certificate ${file};
    • 默认值: 无
    • 作用域: httpserver

Nginx 作为下游时

指定自身使用的证书:

  • proxy_ssl_certificate
    • 作用: 配置自身在通信时使用的 TLS 证书
    • 基本语法: proxy_ssl_certificate ${file};
    • 默认值: 无
    • 作用域: httpserver
  • proxy_ssl_certificate_key
    • 作用: 配置自身在通信时使用的 TLS 私钥
    • 基本语法: proxy_ssl_certificate_key ${file};
    • 默认值: 无
    • 作用域: httpserver

对上游证书进行验证:

  • proxy_ssl_verify
    • 作用: 是否对上游证书进行验证
    • 基本语法: proxy_ssl_verify on | off;
    • 默认值: proxy_ssl_verify off;
    • 作用域: httpserver
  • proxy_ssl_trusted_certificate
    • 作用:
    • 基本语法: proxy_ssl_trusted_certificate ${file};
    • 默认值: 无
    • 作用域: httpserver

ssl 模块提供的变量

安全套件:

  • ssl_cipher: 本次通讯选用的安全套件,例如 ECDHE-RSA-AES128-GCM-SHA256
  • ssl_ciphers: 客户端支持的所有安全套件
  • ssl_protocol: 本次通讯选用的 TLS 版本,例如 TLSv1.2
  • ssl_curves: 客户端支持的椭圆曲线,例如 secp384r1:secp521r1

证书:

  • ssl_client_raw_cert: 原始客户端证书内容
  • ssl_client_escaped_cert: 返回客户端证书做 urlencode 编码后的内容
  • ssl_client_cert: 对客户端证书每一行内容前加 tab 制表符空白,增强可读性。
  • ssl_client_fingerprint: 客户端证书的 SHA1 指纹

证书结构化信息:

  • ssl_server_name: 通过 TLS 插件 SNI(ServerNameIndication) 获取到的服务域名
  • ssl_client_i_dn: 依据 RFC2253 获取到证书 issuer dn 信息,例如: CN=..,O=..,L=..,C=..
  • ssl_client_i_dn_legacy: 依据 RFC2253 获取到证书 issuer dn 信息,例如: /C=.../L=../O=.../CN=..
  • ssl_client_s_dn: 依据 RFC2253 获取到证书 subject dn 信息,例如: CN=..,OU=..,O=..,L=..,ST=..,C=.
  • ssl_client_s_dn_legacy: 同样获取 subject dn 信息,格式为: /C=...ST=.../L=...O=.../OU=...CN=..

证书有效期:

  • ssl_client_V_end: 返回客户端证书的过期时间,例如 Dec 4 11:56:11 2028 GMT
  • ssl_client_V_remain: 返回还有多少天客户端证书过期,例如 3649
  • ssl_client_V_start: 客户端证书的颁发日期,例如 Dec 4 11:56:11 2018 GMT

连接有效性:

  • ssl_client_serial: 返回连接上客户端证书的序列号,例如 8BE947674841BD44
  • ssl_early_data: 在 TLS 1.3 协议中使用了 early data 且握手未完成返回 1 ,否则返回 ""
  • ssl_client_verify: 如果验证失败为 FAILED: ${reason} ,如果没有验证证书则为 NONE ,验证成功则为 SUCCESS
  • ssl_session_id: 已建立连接的 sessionid
  • ssl_session_reused: 如果 session 被复用(参考 session 缓存)则为 r ,否则为 .

缓存

网络链路中的缓存

  • 浏览器缓存:
    • 优点:
      • 使用有效缓存时,没有网络消耗,速度最快
      • 即使有网络消耗,但对失效缓存使用 304 响应做到网络流量消耗最小化
    • 缺点:
      • 仅提升一个用户的体验
  • Nginx 缓存
    • 优点
      • 提升所有用户的体验
      • 相比浏览器缓存,有效降低上游服务的负载
      • 通过 304 响应减少 Nginx 与上游服务间的流量消耗
    • 缺点:
      • 用户仍然保持网络消耗
      • 同时使用浏览器与 Nginx 缓存

浏览器缓存

浏览器缓存流程如下图:

关于 强缓存协商缓存 在浏览器部分有详细介绍,这里主要记录服务端视角下的协商缓存。

Nginx 配置 ETag:

  • etag
    • 作用: 控制响应是否携带 ETag 信息, ETag 生成规则为: ${last_modified_time}-${content_length}
    • 语法: etag: on / off;
    • 默认值: etag: on;
    • 作用域: httpserverlocation

全程通过 expires 指令来控制过期时间

  • expires
    • 作用: 设置静态资源的缓存策略
    • 基本语法:
      • expires [modified] ${time};
      • expires epoch | max | off;
        • max: 设置为最大值,一般是 10 年
        • off: 不添加或者修改 ExpiresCache-Control 字段
        • epoch: 设置为不缓存,过期时间为时间戳起始时间
          • Expires: Thu, 01 Jan 1970 00:00:01 GMT
          • Cache-Control: no-cache
        • ${time}: 设定具体时间,可以带单位
          • 一天内的具体时刻可以加@,比如下午六点半: @18h30m
        • 正数: 设定 Cache-Control 时间,计算出 Expires ,例如 1h
        • 负数: Cache-Control: no-cache ,计算出 Expires ,例如 -1h
    • 默认值: expires off;
    • 作用域: httpserverlocationlocation 中的 if

验证资源是否过期

针对浏览器协商缓存的校验功能都在 ngx_http_not_modified_module 模块中,如果资源已经 404 了,直接返回错误码,不会决策返回 200 还是 304

验证缓存是否过期流程如下:

  • if_modified_since
    • 作用: 声明比较过期时间时,如何进行比较
    • 基本语法: if_modified_since off | exact | before;
      • off: 忽略请求中的 if_modified_since 头部
      • exact: 精确匹配 if_modified_since 头部与 last_modified 的值
      • before: 若 if_modified_since 大于等于 last_modified 的值,则返回 304
    • 默认值: if_modified_since exact;
    • 作用域: httpserverlocation

Nginx 配置上游服务缓存

完整缓存流程如下图:

定义缓存载体

  • proxy_cache
    • 作用: 定义缓存使用的共享内存空间
    • 基本语法: proxy_cache zone | off;
    • 默认值: proxy_cache off;
    • 作用域: httpserverlocation
  • proxy_cache_path
    • 作用: 定义缓存到磁盘中的哪个位置
    • 基本语法: proxy_cache_path ${path};
    • 默认值: 无
    • 作用域: http

缓存哪些值

  • proxy_cache_key
    • 作用: 设置缓存时使用的 key 值,例如静态资源可以只使用 uri 缓存, SSR 资源需要加上用户指纹缓存
    • 基本语法: proxy_cache_key ${key};
    • 默认值: proxy_cache_key $scheme$proxy_host$request_uri;
    • 作用域: httpserverlocation

缓存哪些响应

  • proxy_cache_valid
    • 作用: 声明哪些响应需要被缓存起来
    • 基本语法: proxy_cache_valid [code ...] ${time};
      • 如果未配置状态码时,默认缓存 200 、 301 、 302 的请求
      • X-Accel-Expires 也能控制 Nginx 缓存
      • 如果响应头含有 Set-Cookie 或者 Vary: * 则不缓存
    • 默认值: 无
    • 作用域: httpserverlocation

哪些内容不缓存

  • proxy_no_cache

    • 作用: 指定参数为真时,不进行缓存
    • 基本语法: proxy_no_cache ${string} ...;
    • 默认值: 无
    • 作用域: httpserverlocation
  • proxy_cache_bypass

    • 作用: 指定参数为真时,有缓存也不使用缓存
    • 基本语法: proxy_cache_bypass ${string} ...;
    • 默认值: 无
    • 作用域: httpserverlocation

缓存回源

当缓存失效时,需要从上游服务中获取新的结果,这个过程称为回源,对应的上游服务称为源站。

如果频繁回源,会导致源站压力升高,可能直接导致源站挂起。

Nginx 提供了一些优化回源的能力。

合并回源请求

同一时间,合并多个相同 key 的请求,只向上游服务发送一条请求,其他请求等待上游服务响应后,使用缓存数据。

主要处理的是热点文件的请求的回源问题,非热点文件基本无效果

  • proxy_cache_lock
    • 作用: 同一时间,仅第 1 个请求发向上游,其他请求等待第 1 个响应返回或者超时后,使用缓存响应客户端
    • 基本用法: proxy_cache_lock on | off;
    • 默认值: proxy_cache_lock off;
    • 作用域: httpserverlocation
  • proxy_cache_lock_timeout
    • 作用: 被阻塞的其他缓存在等待第 1 个请求返回响应的最大时间,到达后直接向上游发送请求,但不缓存响应
    • 基本用法: proxy_cache_lock_timeout time;
    • 默认值: proxy_cache_lock_timeout 5s;
    • 作用域: httpserverlocation
  • proxy_cache_lock_age
    • 作用: 上一个请求返回响应的超时时间,到达后再放行一个请求发向上游,而不是一次性所有阻塞请求都超时
    • 基本用法: proxy_cache_lock_age ${time};
    • 默认值: proxy_cache_lock_age 5s;
    • 作用域: httpserverlocation

减少回源请求

当前已有缓存,但是失效了,可以先发送陈旧的缓存给用户,同时进行缓存更新操作。

  • proxy_cache_use_stale
    • 作用: 配置是否使用陈旧缓存
    • 基本语法: proxy_cache_use_stale error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | off ...;
      • updating: 当缓存内容过期,有一个请求正在访问上游试图更新缓存时,其他请求直接使用过期内容返回客户端。
        • stale-while-revalidate: Cache-Control 中的配置信息,声明在缓存过期后还可以容忍多长时间,这段时间内可以使用陈旧缓存,否则 updating 设置无效。例如: Cache-Control: max-age=600, stale-while-revalidate=30
        • stale-if-error: Cache-Control 中的配置信息,声明在缓存过期后,在这段时间内如果上游服务报错,就继续使用陈旧缓存,否则会请求上游获取新数据。例如: Cache-Control: max-age=600, stale-if-error=1200
      • error: 当与上游建立连接、发送请求、读取响应头部等情况出错时,使用缓存
      • timeout: 当与上游建立连接、发送请求、读取响应头部等情况出现定时器超时,使用缓存
      • http_(500|502|503|504|403|404|429): 缓存以上错误响应码的内容
    • 默认值: proxy_cache_use_stale off;
    • 作用域: httpserverlocation
  • proxy_cache_background_update
    • 作用: 当使用 proxy_cache_use_stale 允许使用过期响应时,将同步生成一个子请求,通过访问上游服务更新过期的缓存
    • 基本用法: proxy_cache_background_update on | off;
    • 默认值: proxy_cache_background_update off;
    • 作用域: httpserverlocation
  • proxy_cache_revalidate
    • 作用: 更新缓存时,使用 If-Modified-Since 和 If-None-Match 作为请求头部,预期内容未发生变更时通过 304 来减少传输的内容
    • 基本用法: proxy_cache_revalidate on | off;
    • 默认值: proxy_cache_revalidate off;
    • 作用域: httpserverlocation

清除缓存

开源版 Nginx 没有这项能力,商业版 Nginx 有提供,或者使用免费的第三方模块 ngx_cache_purge

  • proxy_cache_purge
    • 作用: 声明在什么条件下进行缓存请求,在命令中定义匹配条件
    • 基本用法: proxy_cache_purge on | off | ${method} [from all | ${ip} ... ]
    • 默认值: 无
    • 作用域: httpserverlocation
  • proxy_cache_purge
    • 作用: 只要 location 匹配,就清除缓存,只需要定义声明清除哪个共享内存中的哪些 key ,比如匹配用户登录/登出
    • 基本用法: proxy_cache_purge ${zone_name} ${key}
    • 默认值: 无
    • 作用域: httpserverlocation

性能优化

从软件层面提升硬件使用效率

  • 增大 CPU 的利用率
  • 增大内存的利用率
  • 增大磁盘 I/0 的利用率
  • 增大网络带宽的利用率

提升硬件规格

  • 网卡: 万兆网卡,例如 10G 、 25G 、 40G 等
  • 磁盘: 固态硬盘,关注 IOPS 和 BPS 指标
  • CPU: 更快的主频,更多的核心,更大的缓存,更优的架构
  • 内存: 更快的访问速度

优化 CPU 利用率

整体思路:

  • 能够使用 全部 CPU 资源
    • master-worker 多进程架构
    • worker 进程数量 应当大于等于 CPU 核数
  • Nginx 进程间不做无用功浪费 CPU 资源
    • worker 进程不应在繁忙时, 主动 让出 CPU ,有以下两种情况:
      • worker 进程间不应由于争抢造成资源耗散
        • worker 进程数量应当等于 CPU 核数
        • 使用 worker_cpu_affinity ${cpumask} 为 worker 进程绑定 CPU
      • worker 进程不应调用一些 API 导致主动让出 CPU
        • 拒绝会导致进程阻塞的第三方模块
  • 不被其他进程争抢资源
    • 提升进程优先级,占用 CPU 更长的时间
      • 使用 worker_priority -20; 配置,设置为最高优先级,默认为 worker_priority 0;
    • 减少操作系统上耗资源的非 Nginx 进程

优化网络传输

  • 超时重试次数减少,避免占用资源
  • 限制 TCP 握手各个阶段的队列数量,比如 SYN_RECV 、 ACCEPT 等状态
  • 设置 worker 最大连接数(包含 Nginx 与上下游之间的连接)
  • 限制丢包重传次数
  • 压缩响应体积
  • 升级 HTTP 协议