Nginx 学习笔记(四):深入原理

写在前面

本文是 Nginx 学习笔记系列的最后一篇,深入 Nginx 的底层实现:Master/Worker 进程模型、事件驱动机制、请求处理的 11 个阶段、内存管理,以及 Nginx 为什么这么快。最后对比 Nginx、OpenResty 和 Envoy。


一、Master/Worker 进程模型

1.1 进程架构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Nginx 启动后的进程结构:

  PID  PPID COMMAND
  1001  1   nginx: master process        Master 进程(以 root 运行)
  1002 1001 nginx: worker process        Worker 进程(以 www-data 运行)
  1003 1001 nginx: worker process
  1004 1001 nginx: worker process
  1005 1001 nginx: worker process        4  Worker = 4  CPU
  1006 1001 nginx: cache manager process  缓存管理
  1007 1001 nginx: cache loader process   缓存加载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
进程分工:

  Master 进程:
    - 读取和验证配置文件
    - 创建、绑定、监听 Socket
    - 管理 Worker 进程(fork、监控、重启)
    - 处理信号(reloadreopenstop
    - 不处理任何业务请求

  Worker 进程:
    - 接收和处理客户端请求
    - 每个 Worker 独立、互不干扰
    - 一个 Worker 崩溃不影响其他 Worker
    - Worker 之间通过共享内存通信

  Cache Manager
    - 管理磁盘缓存
    - 检查缓存有效期,删除过期缓存

  Cache Loader
    - Nginx 启动时加载磁盘缓存到内存索引
    - 加载完成后退出

1.2 为什么不用多线程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
多进程 vs 多线程:

  Nginx 选择多进程:
    ✓ Worker 崩溃不影响其他 Worker(隔离性好)
    ✓ 无锁设计,无死锁风险
    ✓ 配合 epoll 事件驱动,单进程处理万级并发
    ✓ 方便利用多核(每个 Worker 绑定一个 CPU 核心)

  多线程的问题:
    ✗ 线程间共享内存,需要大量锁
    ✗ 锁竞争导致性能下降
    ✗ 一个线程崩溃可能导致整个进程崩溃
    ✗ 调试困难

  实际上 Nginx 的 Worker 内部也有辅助线程(file aio),
  但核心请求处理是单线程事件驱动。

1.3 进程管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 查看 Nginx 进程
ps aux | grep nginx

# Worker 数量配置
# nginx.conf
worker_processes auto;                # 自动 = CPU 核心数
# 或手动指定
worker_processes 4;

# 绑定 Worker 到 CPU 核心(减少 CPU 切换开销)
worker_cpu_affinity auto;

# Worker 进程优先级
worker_priority -5;                   # 值越小优先级越高(-20 到 19)

# Worker 最大打开文件数
worker_rlimit_nofile 65535;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Master 处理信号:

  TERM, INT     优雅停止
  QUIT          优雅停止(等请求处理完)
  HUP           重新加载配置
  USR1          重新打开日志文件
  USR2          升级可执行文件(热升级)
  WINCH         优雅停止 Worker

reload 时发生了什么:
  1. Master 收到 HUP 信号
  2. 重新读取配置文件
  3. 如果配置合法,fork 新的 Worker 进程
  4. 向旧 Worker 发送 QUIT 信号
  5.  Worker 处理完当前请求后退出
  6. 全程无中断(零停机 reload

二、事件驱动机制(epoll)

2.1 为什么需要事件驱动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
传统方式(多进程/线程模型):
  每个连接分配一个线程
  连接空闲时,线程阻塞在 read() 上
  1万个连接 = 1万个线程

  问题:
    ✗ 线程占用内存(每个线程约 8MB 栈空间)
    ✗ 线程切换开销大(上下文切换、CPU 缓存失效)
    ✗ 难以扩展到 C10K(万级并发)

事件驱动模型:
  一个线程管理所有连接
  只在连接有事件(可读/可写)时才处理
  空闲连接不消耗 CPU

  优势:
    ✓ 少量线程即可处理万级并发
    ✓ 空闲连接零开销
    ✓ CPU 只在做有用的工作

2.2 I/O 多路复用技术

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Linux I/O 多路复用的演进:

1. select
   - 最多监听 1024 个文件描述符(FD)
   - 每次调用需要传入所有 FD,内核遍历全部
   - O(n) 复杂度,性能差

2. poll
   - 没有连接数限制
   - 仍然需要传入所有 FD,内核遍历全部
   - O(n) 复杂度

3. epoll(Linux 2.6+)
   - 没有连接数限制
   - 只返回就绪的 FD,不需要遍历全部
   - O(1) 复杂度,性能极好
   - Nginx 在 Linux 上的默认选择

4. kqueue(FreeBSD / macOS)
   - 类似 epoll,性能优秀
   - Nginx 在 macOS 上的默认选择

2.3 epoll 工作原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
epoll 的三个系统调用:

1. epoll_create()
   创建 epoll 实例(红黑树 + 就绪链表)

2. epoll_ctl()
   注册/修改/删除要监听的 FD
   内核会为每个 FD 注册回调

3. epoll_wait()
   阻塞等待就绪事件
   只返回有事件的 FD(不需要遍历全部)

工作流程:
  ┌─────────────┐
  │  epoll 实例   │
  │ ┌───────────┐ │
  │ │ 红黑树     │ │  ← 存储所有注册的 FD
  │ │ (FD 集合)  │ │
  │ └───────────┘ │
  │ ┌───────────┐ │
  │ │ 就绪链表   │ │  ← 有事件的 FD(由内核回调自动添加)
  │ │ (活跃 FD)  │ │
  │ └───────────┘ │
  └─────────────┘

  网络数据到达 → 网卡中断 → 内核回调 → FD 加入就绪链表
  epoll_wait() 返回就绪链表 → Nginx 处理活跃连接

  关键优势:
    不需要遍历所有连接,只处理有事件的
    1万个连接中可能只有 100 个活跃 → 只处理 100 个

2.4 Nginx 的事件模型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# nginx.conf events 块
events {
    # 使用 epoll(Linux 默认)
    use epoll;

    # 每个 Worker 的最大连接数
    worker_connections 65535;

    # 尽可能多地接收连接
    multi_accept on;

    # 禁用 accept 互斥锁(Worker 数少时)
    accept_mutex off;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Nginx Worker 的事件循环(简化版):

  while (true) {
      events = epoll_wait(epoll_fd, ...);     // 等待事件

      for (event in events) {
          if (event 是新连接) {
              accept 新连接;
              将新连接注册到 epoll;
          }
          else if (event 可读) {
              读取请求数据;
              处理请求;
              生成响应;
          }
          else if (event 可写) {
              发送响应数据;
          }
      }
  }

  注意:
    - 事件循环中不执行阻塞操作
    - 文件 I/O 用线程池(file aio)
    - DNS 解析有缓存,避免阻塞

2.5 连接处理流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
一个 HTTP 请求的完整生命周期:

  1. 客户端发起 TCP 连接
     → 三次握手完成,epoll 通知有新连接

  2. Worker accept 连接
     → 创建 connection 对象
     → 注册到 epoll 监听读事件

  3. 客户端发送 HTTP 请求
     → epoll 通知可读事件
     → Worker 读取请求数据
     → 解析 HTTP 请求行、头部、请求体

  4. 处理请求
     → 匹配 location
     → 执行 rewrite、access、content 等阶段
     → 生成响应

  5. 发送响应
     → 注册可写事件到 epoll
     → epoll 通知可写时发送数据

  6. keepalive 复用或关闭连接
     → keepalive:连接保持,等待下一个请求
     → 超时或 Connection: close:关闭连接

  整个过程:一个 Worker 线程处理,不创建新线程

三、请求处理完整流程(11 个阶段)

3.1 HTTP 请求处理管道

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Nginx 把 HTTP 请求处理分为 11 个阶段,模块按阶段执行:

  ┌─────────────────────────────────────────┐
  │          HTTP Request                    │
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  1. POST_READ        读取请求后          │  ngx_http_realip_module
  │     (获取真实 IP)                      │
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  2. SERVER_REWRITE    server 级重写       │  ngx_http_rewrite_module
  │     (server 块中的 rewrite)             │
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  3. FIND_CONFIG       查找配置           │  Nginx 核心
  │     (匹配 location)                    │
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  4. REWRITE           location 级重写     │  ngx_http_rewrite_module
  │     (location 块中的 rewrite)           │
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  5. POST_REWRITE      重写后处理         │  Nginx 核心
  │     (检查是否需要重新匹配 location)      │
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  6. PREACCESS         访问前检查         │  ngx_http_limit_conn_module
  │     (限流、连接数限制)                   │  ngx_http_limit_req_module
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  7. ACCESS            访问控制           │  ngx_http_access_module
  │     (IP 黑白名单、认证)                 │  ngx_http_auth_basic_module
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  8. POST_ACCESS       访问后处理         │  Nginx 核心
  │     (satisfy 配合 access 阶段)          │
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  9. PRECONTENT        内容前处理         │  ngx_http_try_files_module
  │     (try_files)                        │
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  10. CONTENT          生成内容           │  ngx_http_proxy_module
  │      (代理、静态文件、FastCGI)           │  ngx_http_static_module
  └──────────────┬──────────────────────────┘
  ┌─────────────────────────────────────────┐
  │  11. LOG              记录日志           │  ngx_http_log_module
  │      (access_log)                      │
  └─────────────────────────────────────────┘

3.2 各阶段详解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 以下配置展示了各阶段对应的指令

server {
    listen 80;
    server_name example.com;

    # 阶段 1: POST_READ — 获取真实 IP
    set_real_ip_from 10.0.0.0/8;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    # 阶段 2: SERVER_REWRITE — server 级重写
    rewrite ^/old-api/(.*)$ /api/$1 last;

    location /api/ {
        # 阶段 4: REWRITE — location 级重写
        rewrite ^/api/v1/(.*)$ /api/v2/$1 break;

        # 阶段 6: PREACCESS — 限流
        limit_req zone=api_limit burst=50 nodelay;
        limit_conn conn_limit 20;

        # 阶段 7: ACCESS — 访问控制
        allow 10.0.0.0/8;
        allow 192.168.0.0/16;
        deny all;
        auth_basic "Restricted";
        auth_basic_user_file /etc/nginx/.htpasswd;

        # 阶段 9: PRECONTENT — try_files
        try_files $uri $uri/ @proxy;

        # 阶段 10: CONTENT — 生成内容
        proxy_pass http://backend;
    }

    location @proxy {
        proxy_pass http://backend;
    }

    # 阶段 11: LOG — 日志
    access_log /var/log/nginx/example.com.access.log detailed;
}

3.3 阶段执行的特点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
1. 顺序执行
   请求按阶段 1→2→3→...→11 顺序经过各阶段

2. 模块注册
   每个模块可以注册到特定阶段
   同一阶段可以有多个模块,按配置顺序执行

3. 阶段跳转
   rewrite 的 last 会跳回阶段 3(FIND_CONFIG)
   rewrite 的 break 跳过后续 rewrite,进入下一阶段
   return 直接跳到阶段 11(LOG)

4. 内容阶段只有一个执行
   proxy_pass、fastcgi_pass、root/alias 只能选一个
   先匹配到的执行

5. 日志阶段总是执行
   无论前面的阶段返回什么状态码,日志阶段都会执行

四、内存管理

4.1 Nginx 的内存管理策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
为什么不直接用 malloc/free:

  问题:
    ✗ 频繁的 malloc/free 导致内存碎片
    ✗ 每次分配有系统调用开销
    ✗ 容易忘记 free 导致内存泄漏
    ✗ 多线程环境下需要锁

  Nginx 的方案:
    ✓ 内存池(pool)— 批量分配,一次性释放
    ✓ 共享内存 — Worker 间通信
    ✓ slab 分配器 — 管理共享内存中的固定大小对象

4.2 内存池(ngx_pool_t)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
内存池结构:

  ngx_pool_t
  ├── d: last, end, next, failed     ← 数据区链表
  ├── max: 最大可分配大小
  ├── current: 当前工作的小块内存池
  ├── chain: 缓冲区链表
  ├── large: 大块内存链表
  ├── cleanup: 清理回调链表
  └── hostnet: 子内存池

  ┌──────────────────────────────┐
  │          pool                 │ ← 一次 malloc 分配一整块(如 4KB)
  │  ┌────────────────────────┐  │
  │  │  已使用  │    空闲       │  │ ← last 指针标记空闲起始位置
  │  └────────────────────────┘  │
  │          next pool            │ ← 空间不够时,再 malloc 一块链上
  │  ┌────────────────────────┐  │
  │  │        空闲             │  │
  │  └────────────────────────┘  │
  └──────────────────────────────┘

  小块分配(≤ max,通常 ≤ 4096 字节):
    从当前 pool 的空闲区域直接切出
    移动 last 指针即可,极快
    不需要 free,整个 pool 一起释放

  大块分配(> max):
    直接 malloc 分配
    加入 large 链表管理
    pool 销毁时一起释放
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
内存池的分配和释放:

  创建:
    pool = ngx_create_pool(4096, log)     // 分配 4KB 内存池

  小块分配:
    p = ngx_palloc(pool, 128)             // 从池中切 128B,极快
    p = ngx_pnalloc(pool, 256)            // 同上,不要求对齐

  大块分配:
    p = ngx_palloc(pool, 8192)            // > max,直接 malloc

  使用完毕:
    ngx_destroy_pool(pool)                // 一次性释放所有内存

  重置(复用):
    ngx_reset_pool(pool)                  // 重置 last 指针,不释放内存

优势:
  ✓ 小块分配只需要移动指针,O(1) 复杂度
  ✓ 没有内存碎片(同一 pool 中连续分配)
  ✓ 不需要逐个 free,一键销毁
  ✓ 每个请求一个 pool,请求结束销毁,不会泄漏

4.3 请求与内存池的生命周期

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
一个 HTTP 请求的内存管理:

  请求开始
    → 创建请求级内存池(request pool)

  请求处理过程中
    → 解析请求行 → palloc 分配
    → 解析请求头 → palloc 分配
    → 读取请求体 → palloc 分配
    → 生成响应   → palloc 分配
    → 所有分配都从池中获取,不需要 free

  请求结束
    → 销毁请求级内存池
    → 所有内存一次性释放
    → 不会泄漏任何字节

  对比传统方式:
    传统:malloc/free 配对,容易漏 free
    Nginx:只管分配,不管释放,请求结束统一释放

4.4 共享内存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Worker 进程之间需要共享数据(如限流的计数器):

  Nginx 共享内存:

    ngx_shm_zone_t
    ├── shm_zone: 共享内存区域名称
    ├── shm_size: 共享内存大小
    ├── data:     自定义数据
    └── init:     初始化回调

  使用场景:
    limit_req_zone  — 限流计数器
    limit_conn_zone — 连接数计数器
    ssl_session_cache — SSL 会话缓存
    proxy_cache     — 代理缓存
    upstream        — 负载均衡状态

  配置示例:
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
    #                     ↑ IP              ↑ 名称 ↑ 10MB 共享内存

4.5 slab 分配器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
共享内存中使用 slab 分配器管理固定大小的对象:

  slab 分配器结构:
    ┌─────────────────────────────────────────────┐
    │              共享内存(如 10MB)               │
    │  ┌─────────┐ ┌─────────┐ ┌─────────┐       │
    │  │ Page 0  │ │ Page 1  │ │ Page 2  │ ...   │
    │  └─────────┘ └─────────┘ └─────────┘       │
    │  每个 Page 4KB                               │
    │                                              │
    │  Page 内部按 slot 大小分割:                   │
    │  slot 大小:8, 16, 32, 64, 128, 256, ...     │
    │                                              │
    │  如 slot_size = 64:                          │
    │  ┌───┬───┬───┬───┬───┬───┬───┬───┐         │
    │  │64B│64B│64B│64B│64B│64B│64B│64B│  ← 8个slot│
    │  └───┴───┴───┴───┴───┴───┴───┴───┘         │
    └─────────────────────────────────────────────┘

  工作方式:
    分配:根据对象大小选择合适的 slot,从 page 中分配一个 slot
    释放:标记 slot 为空闲,可以复用
    无碎片:固定大小分配,不会产生碎片

  应用场景:
    限流:每个 IP 的计数器是固定大小(如 64B),用 slab 分配
    SSL 缓存:每个会话条目大小固定
    负载均衡:upstream 的状态信息

五、为什么 Nginx 这么快

5.1 性能数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
典型性能基准:

  静态文件服务:
    简单 HTML:50,000+ req/s(单机)
    小文件(<10KB):30,000+ req/s

  反向代理:
    简单代理:20,000+ req/s
    带 SSL:10,000+ req/s

  对比 Apache(prefork 模式):
    同等硬件:Nginx 快 2-10 倍
    内存占用:Nginx 低 5-20 倍

5.2 快的原因总结

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1. 事件驱动 + epoll
   单线程处理万级并发
   只处理活跃连接,空闲连接零开销
   epoll O(1) 复杂度,不随连接数增长而变慢

2. 零拷贝(sendfile)
   静态文件不需要经过用户空间
   内核直接从文件描述符拷贝到 Socket
   减少两次内存拷贝和上下文切换

   传统方式:
     磁盘 → 内核缓冲区 → 用户空间 → Socket 缓冲区 → 网卡
   sendfile:
     猖盘 → 内核缓冲区 → 网卡(直接 DMA 传输)

3. 内存池
   批量分配,一次性释放
   小块内存分配只需移动指针(O(1))
   无内存碎片,无泄漏

4. 无锁设计
   Worker 进程独立,互不干扰
   不需要互斥锁,无锁竞争
   共享内存用原子操作和 slab 分配器

5. 高效的 I/O 处理
   sendfile 零拷贝
   tcp_nopush 优化数据包发送
   tcp_nodelay 禁用 Nagle 算法
   文件 AIO(异步 I/O)处理大文件

6. 精心优化的数据结构
   红黑树:定时器、location 匹配
   单/双链表:缓冲区管理
   哈希表:变量查找
   基数树:IP 匹配

7. 模块化架构
   只编译需要的模块
   减少不必要的代码路径

5.3 Nginx 优化配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 综合性能优化配置
user www-data;
worker_processes auto;                    # 自动匹配 CPU 核心数
worker_cpu_affinity auto;                 # 自动绑定 CPU
worker_rlimit_nofile 100000;              # 提高文件描述符限制

events {
    worker_connections 65535;             # 每个Worker最大连接数
    use epoll;                            # 使用 epoll
    multi_accept on;                      # 一次性接收所有新连接
    accept_mutex off;                     # Worker多时关闭互斥锁
}

http {
    sendfile on;                          # 零拷贝
    tcp_nopush on;                        # 优化数据包发送
    tcp_nodelay on;                       # 禁用 Nagle
    keepalive_timeout 65;
    keepalive_requests 100000;            # 一个长连接最多处理多少请求

    # 文件描述符缓存
    open_file_cache max=10000 inactive=60s;
    open_file_cache_valid 90s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # 连接优化
    reset_timedout_connection on;         # 超时连接直接 reset
    client_body_timeout 12;
    send_timeout 10;

    # 压缩
    gzip on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_types text/plain text/css application/javascript application/json;

    # 输出缓冲
    output_buffers 1 32k;
    postpone_output 1460;                 # 累积到 MSS 大小再发送
}

六、Nginx vs OpenResty vs Envoy

6.1 对比总览

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
特性              Nginx            OpenResty         Envoy
─────────────────────────────────────────────────────────────
基础              Nginx 核心        Nginx + LuaJIT    C++ 自研
语言              C                C + Lua           C++
配置              静态配置文件       静态 + Lua 动态    xDS 动态 API
可编程性          模块(C         Lua 脚本          WASM / Lua / C++
动态配置           reload         运行时动态         实时推送
负载均衡          基础策略丰富        Nginx + Lua     高级(区域感知、加权等)
可观测性          基础日志          Lua 扩展          内置(MetricsTracingLogging
服务发现          手动配置          Lua 脚本          原生支持(DNSxDSEDS
gRPC 支持         基础             基础              原生(双向流)
HTTP/3            实验性           实验性            支持
管理 API          信号(reload    Lua API           REST gRPC API
定位              Web/代理服务器    可编程 Web 平台    服务网格代理
诞生年份          2004             2011              2016

6.2 OpenResty

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
OpenResty = Nginx + LuaJIT + 大量 Lua 

核心特点:
   Nginx 中嵌入 Lua 虚拟机
  可以用 Lua 脚本处理请求(不限于静态配置)
  几乎可以访问 Nginx 所有内部 API

典型用途:
  - API 网关(Kong 基于 OpenResty
  - WAFModSecurity 替代)
  - 动态路由
  - 限流、认证(复杂逻辑)
  - 缓存(Redis + Lua

示例:
  location /api {
      content_by_lua_block {
          local redis = require "resty.redis"
          local red = redis:new()
          red:set_timeouts(1000, 1000, 1000)
          local ok, err = red:connect("127.0.0.1", 6379)
          local res, err = red:get("cache:" .. ngx.var.uri)
          if res then
              ngx.say(res)
              return
          end
          -- 缓存未命中,请求后端
          local res = ngx.location.capture("/backend" .. ngx.var.uri)
          red:setex("cache:" .. ngx.var.uri, 300, res.body)
          ngx.say(res.body)
      }
  }

适用场景:
  需要动态逻辑、API 网关、复杂认证
  不想写 C 模块但需要超越静态配置

6.3 Envoy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Envoy = Lyft 开源的 L4/L7 代理,CNCF 项目

核心特点:
  面向微服务和云原生设计
  动态配置 APIxDS 协议)
  内置可观测性(MetricsTracingLogging
  Istio 的数据平面

优势:
  - 动态配置:通过 xDS API 实时更新,不需要 reload
  - 服务发现:原生支持 DNSEDSConsul 
  - 高级负载均衡:区域感知、故障注入、灰度发布
  - 可观测性:原生 Prometheus 指标、分布式追踪
  - gRPC 一等公民
  - WASM 扩展

xDS 协议:
  LDSListener    监听器配置
  RDSRoute       路由配置
  CDSCluster     集群(upstream)配置
  EDSEndpoint    集群成员(服务发现)
  SDSSecret      证书配置

适用场景:
  微服务架构、服务网格
  Kubernetes Ingress / Gateway
  需要动态配置和高级可观测性的场景

6.4 如何选择

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
选 Nginx:
  ✓ 传统 Web 服务、反向代理、负载均衡
  ✓ 静态文件服务
  ✓ 配置变更不频繁
  ✓ 团队熟悉 Nginx
  ✓ 社区最成熟,资料最多

选 OpenResty:
  ✓ 需要动态逻辑(API 网关、WAF)
  ✓ 想用脚本扩展 Nginx
  ✓ 已有 Nginx 基础设施
  ✓ 使用 Kong API 网关

选 Envoy:
  ✓ 微服务架构 / 服务网格
  ✓ 需要动态配置(Kubernetes 环境)
  ✓ Istio 服务网格
  ✓ 需要高级可观测性
  ✓ gRPC 为主的服务

很多公司的架构:
  边缘层:Nginx(HTTPS 终端、静态资源、基础代理)
  内部层:Envoy(微服务间通信、服务网格)

七、小结

本文深入了 Nginx 的底层原理:

  • 进程模型:Master 管理 Worker,Worker 独立处理请求,崩溃不扩散
  • 事件驱动:epoll 实现高效 I/O 多路复用,单线程处理万级并发
  • 请求处理:11 个阶段按顺序执行,模块注册到特定阶段
  • 内存管理:内存池批量分配/释放,slab 管理共享内存
  • 为什么快:事件驱动 + 零拷贝 + 内存池 + 无锁 + 高效数据结构
  • 技术选型:Nginx(传统 Web)、OpenResty(可编程代理)、Envoy(云原生/服务网格)

系列总结

四篇 Nginx 学习笔记到此结束:

  1. 基础与核心配置 — 安装、配置结构、静态文件、虚拟主机、location
  2. 反向代理与负载均衡 — 代理配置、负载策略、健康检查、四层/七层
  3. HTTPS、性能优化与实战 — 证书、压缩、缓存、限流、.NET 部署
  4. 深入原理 — 进程模型、epoll、11 阶段、内存管理、技术对比

Nginx 的学习曲线:配置入门不难,但深入理解原理需要时间。建议边学边练,在自己的服务器上实际配置和调优。