07-源码、模块与底层机制
07-源码、模块与底层机制
1. 阶段定位
第七阶段的目标,是从“会部署、会配置、会排障 Nginx”,进一步进入“能从源码层面理解 Nginx 为什么这样工作”。前六个阶段已经覆盖了部署、配置、请求匹配、反向代理、负载均衡、HTTPS、安全治理、缓存、性能优化、生产排障、高可用和运维体系;本阶段要把这些能力向下沉一层,理解它们在 Nginx 内部是如何被组织和执行的。
很多人在学习 Nginx 源码时容易陷入两个极端:一开始就想完整读完所有源码,结果很快被目录、宏、回调、结构体和平台差异淹没;或者只阅读几篇源码分析文章,知道一些名词,但无法把配置、请求、事件、upstream、filter、内存池和模块机制串成完整链路。
本阶段不追求逐行背源码,而是围绕核心主线建立可验证的源码理解:
1 | Nginx 如何启动 |
完成本阶段后,你应该能回答:
- Nginx 为什么能用较少 worker 承载大量并发连接。
- master 和 worker 的职责边界是什么。
- reload 为什么通常不会中断正在处理的请求。
- 配置文件是如何被解析、合并并交给模块使用的。
- 一次 HTTP 请求从连接建立到 access log 写入经历哪些内部阶段。
location、rewrite、access、content、proxy_pass、log在源码中分别对应哪些处理阶段。- upstream 是如何创建连接、发送请求、接收响应、处理失败重试和超时的。
- header filter 与 body filter 为什么可以组成过滤链。
ngx_pool_t、ngx_buf_t、ngx_chain_t分别解决什么问题。limit_req、proxy_cache等能力为什么需要 shared memory zone。- 如何编写一个最小 HTTP content handler 模块或 header filter 模块。
本阶段学完后,不一定意味着你要成为 Nginx 内核开发者,但你应该具备资深 Nginx 工程师的源码定位能力:遇到复杂线上问题时,知道该看哪个模块、哪个结构、哪个阶段,而不是只靠猜测和搜索配置模板。
2. 学习边界
2.1 本阶段重点
本阶段重点关注 Nginx 开源版本核心机制,主要包括:
- 源码目录结构和编译参数。
- debug 编译、调试符号和源码阅读环境。
- master/worker 启动流程。
- 配置解析、配置上下文和模块配置合并。
- 模块体系:core module、event module、http module、mail module、stream module。
- cycle、conf、module、connection、request、upstream 等核心结构。
- 事件驱动模型:epoll/kqueue/select、非阻塞 IO、accept、事件循环。
- HTTP 请求解析流程。
- phase handler:rewrite、access、content、log 等阶段。
- upstream 反向代理流程。
- header filter、body filter 响应过滤链。
- 内存池
ngx_pool_t。 - buffer、chain 和输出链路。
- timer、红黑树、超时事件。
- slab、shared memory、zone。
- reload、平滑退出、二进制热升级。
- 静态模块、动态模块和第三方模块开发基础。
- 最小 HTTP content handler 模块开发。
- 最小 header filter 或 log 阶段模块开发。
2.2 本阶段暂不深入
下面内容与源码有关,但不是本阶段主线:
- Nginx Plus 商业版私有能力源码。
- OpenResty Lua VM、cosocket、ngx_lua 的完整实现。
- Tengine、APISIX、Kong、Ingress Controller 的源码体系。
- HTTP/3、QUIC、BoringSSL、OpenSSL 内部源码细节。
- 自研高性能网络库与 Nginx 的完整性能对比。
- 操作系统内核网络协议栈源码。
- 复杂第三方模块的完整工业级实现。
- Nginx 所有指令的源码逐项分析。
本阶段的学习目标不是把 Nginx 所有源码读完,而是把“启动、配置、事件、请求、upstream、filter、内存、模块”这几条核心链路读通。
3. 学习准备
3.1 建议环境
建议使用 Linux 环境阅读和调试 Nginx 源码。可以使用物理机、虚拟机、云服务器或 WSL,但最好具备完整编译和调试能力。
| 环境项 | 建议 |
|---|---|
| 操作系统 | Ubuntu、Debian、CentOS、Rocky Linux、AlmaLinux 均可 |
| Nginx 版本 | 建议选择稳定版,例如 1.24.x 或较新的 mainline 版本 |
| 编译器 | GCC 或 Clang |
| 调试工具 | gdb、strace、ltrace、perf、addr2line |
| 阅读工具 | VS Code、CLion、vim、ctags、cscope、ripgrep |
| 压测工具 | wrk、ab、hey、curl |
| 抓包工具 | tcpdump、Wireshark |
建议先使用一个固定源码版本学习,不要频繁切换版本。不同版本内部结构会有差异,但核心思想长期稳定。
3.2 依赖安装示例
Ubuntu/Debian:
1 | sudo apt update |
CentOS/Rocky/AlmaLinux:
1 | sudo yum groupinstall -y "Development Tools" |
3.3 下载源码与 debug 编译
1 | wget https://nginx.org/download/nginx-1.24.0.tar.gz |
重点关注:
--prefix:安装目录。--with-debug:是否开启 debug 日志能力。--with-http_ssl_module:是否编译 SSL 模块。--with-http_stub_status_module:是否编译状态模块。--add-module:是否添加静态第三方模块。--add-dynamic-module:是否添加动态第三方模块。
3.4 开启 debug 日志
--with-debug 只是编译支持 debug,还需要在配置中开启 debug 级别日志:
1 | error_log logs/error.log debug; |
debug_connection 可以避免所有客户端都输出 debug 日志,适合实验或小范围定位。
4. 源码目录结构
Nginx 源码目录非常清晰,先理解目录职责,比直接打开某个 .c 文件更重要。
1 | nginx-1.24.0/ |
src/ 是源码阅读重点:
1 | src/ |
重点文件速查:
| 文件 | 重点内容 |
|---|---|
src/core/nginx.c |
main 入口、启动参数解析、cycle 初始化、进程模型入口 |
src/core/ngx_cycle.c |
cycle 初始化、打开日志、监听端口、模块初始化 |
src/core/ngx_conf_file.c |
配置文件解析框架 |
src/core/ngx_module.c |
模块编号、模块签名、动态模块支持 |
src/os/unix/ngx_process_cycle.c |
master/worker 进程循环 |
src/event/ngx_event.c |
事件模块初始化 |
src/event/modules/ngx_epoll_module.c |
Linux epoll 事件模块 |
src/event/ngx_event_accept.c |
accept 新连接 |
src/http/ngx_http.c |
HTTP 模块初始化、server/location 合并 |
src/http/ngx_http_request.c |
HTTP 请求创建、读取、处理、结束 |
src/http/ngx_http_core_module.c |
HTTP 核心模块、phase engine、location |
src/http/ngx_http_upstream.c |
upstream 通用框架 |
src/http/modules/ngx_http_proxy_module.c |
proxy_pass 模块实现 |
src/http/ngx_http_header_filter_module.c |
默认 header filter |
src/http/ngx_http_write_filter_module.c |
输出 write filter |
src/core/ngx_palloc.c |
内存池实现 |
src/core/ngx_buf.c |
buffer 和 chain 工具函数 |
src/core/ngx_slab.c |
slab 分配器 |
src/event/ngx_event_timer.c |
定时器红黑树 |
5. Nginx 核心设计思想
5.1 事件驱动
Nginx 不是为每个连接创建一个线程,而是使用事件驱动模型处理大量连接。
1 | 大量连接 fd |
事件驱动的优势是避免大量线程上下文切换,可以用少量 worker 处理大量空闲 keepalive 连接,网络 IO 不阻塞整个 worker,请求处理过程也能拆成多个事件回调。
5.2 非阻塞 IO
Nginx 的连接通常被设置为非阻塞。读不到数据时不会一直卡在 read,写不出去时不会一直卡在 write,当前操作暂时无法完成就返回 NGX_AGAIN,等待下一次事件通知。
| 返回值 | 含义 |
|---|---|
NGX_OK |
当前步骤成功完成 |
NGX_ERROR |
当前步骤失败 |
NGX_AGAIN |
暂时无法完成,需要等待后续事件 |
NGX_DONE |
当前流程已被接管或异步完成 |
NGX_DECLINED |
当前模块不处理,交给后续模块 |
5.3 多进程 worker
Nginx 默认采用 master/worker 多进程模型:
1 | master process |
多进程模型让 worker 之间相对隔离,可以利用多核 CPU,配合 reload 可以实现较平滑的配置切换,也避免了多线程共享复杂状态带来的锁竞争和内存安全问题。
5.4 模块化 pipeline
Nginx 的绝大多数能力都是模块实现的。配置指令属于模块,请求阶段由模块挂载 handler,响应输出由 filter 模块串成链。
1 | 配置指令 |
5.5 内存池
Nginx 高频使用内存池,而不是每次请求都频繁 malloc/free。
1 | 为连接或请求创建 pool |
内存池能减少频繁内存分配释放开销,降低内存碎片,简化错误路径清理逻辑,适合请求生命周期明确的场景。
6. 启动流程总览
Nginx 的入口在 src/core/nginx.c 的 main 函数。启动流程可以先按主干理解:
1 | main |
不要一开始纠结每个初始化函数的细节,先理解几个关键点:
ngx_get_options解析-c、-p、-s、-t、-V等命令行参数。ngx_preinit_modules给模块分配 index。ngx_init_cycle是非常关键的初始化函数,会读取配置、初始化模块、打开监听端口、创建共享内存等。ngx_process_cycle根据进程模式进入 master 或 single process 逻辑。
6.1 nginx -t 做了什么
nginx -t 并不是简单检查文件是否存在,而是会走一遍较完整的配置解析和初始化流程,只是不真正进入正式服务循环。
1 | 解析命令行参数 |
所以 nginx -t 能发现很多问题:指令拼写错误、指令放错上下文、include 文件不存在、server/location 配置不合法、监听端口配置冲突、日志路径权限问题、证书文件读取失败等。但它不能替代完整联调,因为它不能证明 upstream 一定可用,也不能证明业务请求一定正确。
6.2 ngx_cycle_t 的意义
ngx_cycle_t 可以理解为 Nginx 当前运行周期的全局上下文,它包含配置上下文、监听端口、打开的文件、共享内存 zone、连接数组、日志对象、模块信息、路径和前缀。
1 | 一次启动或 reload |
这也是 reload 相对安全的重要原因之一:新配置初始化失败时,旧 worker 可以继续工作。
7. master/worker 进程模型
7.1 master 进程职责
master 进程不直接处理普通 HTTP 请求,它主要负责管理生命周期:
- 读取和验证配置。
- 打开监听端口。
- 创建 worker 进程。
- 处理信号。
- reload 时启动新 worker 并通知旧 worker 退出。
- worker 异常退出时按需拉起。
- 日志 reopen。
- 二进制热升级。
源码重点:
1 | src/os/unix/ngx_process_cycle.c |
重点函数:
ngx_master_process_cyclengx_start_worker_processesngx_spawn_processngx_signal_worker_processesngx_reap_children
7.2 worker 进程职责
worker 进程负责真正处理连接和请求:初始化 worker 级别模块、进入事件循环、accept 新连接、读取请求、执行 HTTP phase、访问 upstream、写响应、写日志、处理 keepalive 和优雅退出。
重点函数:
ngx_worker_process_cyclengx_worker_process_initngx_process_events_and_timers
worker 主循环可以简化为:
1 | for ( ;; ) { |
7.3 reload 内部机制
执行:
1 | nginx -s reload |
本质是向 master 发送 HUP 信号。典型流程:
1 | master 收到 HUP |
注意事项:
- reload 不是 restart,通常不会直接杀掉正在处理的请求。
- 长连接、慢下载、WebSocket 可能导致旧 worker 长时间存在。
- 如果配置错误,新 worker 不会成功接管,旧 worker 继续服务。
- 如果修改了影响共享内存 zone 大小或名称的配置,要特别关注兼容性。
- reload 不能解决所有问题,例如二进制文件升级需要走热升级或重启流程。
7.4 worker 为什么不是越多越好
常见建议是:
1 | worker_processes auto; |
worker 数通常接近 CPU 核数即可。过多 worker 可能导致进程调度开销增加、CPU cache 命中率下降、accept 竞争加重、上游连接数放大、日志和磁盘 IO 压力增加。源码层面理解 worker 模型后,就不会简单认为“并发高就把 worker 调到很大”。
8. 配置解析与模块初始化
8.1 配置不是普通文本
Nginx 配置文件最终会被解析成各个模块自己的配置结构。每个模块可以定义支持哪些指令、指令能出现在哪些上下文、指令参数数量和格式、指令解析函数、创建配置结构的函数、合并配置结构的函数以及初始化阶段回调。
一个指令是否合法,不仅看语法,还看它所属模块声明的上下文。
8.2 ngx_command_t
模块通过 ngx_command_t 声明配置指令。
1 | typedef struct ngx_command_s ngx_command_t; |
关键字段:
| 字段 | 含义 |
|---|---|
name |
指令名称,例如 proxy_pass、root、limit_req |
type |
指令上下文、参数数量、类型限制 |
set |
指令解析函数 |
conf |
指令属于 main/server/location 哪类配置 |
offset |
写入模块配置结构的位置 |
post |
附加处理或校验 |
例如一个指令只能出现在 location 中,通常会通过 type 标记限制上下文。
8.3 配置上下文
HTTP 模块常见三层配置:
1 | main conf http 级别 |
对应模块回调通常包括:
create_main_confinit_main_confcreate_srv_confmerge_srv_confcreate_loc_confmerge_loc_conf
配置继承和覆盖的本质,是父级配置和子级配置在 merge 阶段被合并。
1 | http { |
源码层面会让 location /api/ 最终拿到合并后的配置。如果 location 没显式设置,就继承 server 或 http 的值。
8.4 HTTP 模块初始化
HTTP 模块初始化大致做几类事情:
- 创建 HTTP main/server/location 配置。
- 解析
http {}内部指令。 - 收集 server 块。
- 处理 listen 和 server_name。
- 构建 location 树。
- 合并 server/location 配置。
- 初始化 phase engine。
- 初始化 header/body filter 链。
重点文件:
1 | src/http/ngx_http.c |
8.5 学配置解析时的重点问题
阅读配置解析源码时,建议带着下面问题:
http、server、location是如何嵌套解析的。- 一个模块如何声明自己的配置指令。
- 指令为什么只能放在特定上下文。
- server/location 配置如何继承和合并。
include是如何展开的。nginx -t为什么能发现上下文错误。- 动态模块为什么也能提供新指令。
9. 模块体系
9.1 模块分类
Nginx 模块可以按层级分为:
| 模块类型 | 作用 | 示例 |
|---|---|---|
| core module | 核心框架和基础能力 | ngx_core_module |
| event module | 事件机制 | ngx_epoll_module、ngx_kqueue_module |
| http module | HTTP 请求处理 | ngx_http_core_module、ngx_http_proxy_module |
| mail module | 邮件代理 | POP3/IMAP/SMTP 相关模块 |
| stream module | TCP/UDP 四层代理 | ngx_stream_proxy_module |
HTTP 模块还可以按职责分为:
| HTTP 模块类别 | 作用 | 示例 |
|---|---|---|
| handler 模块 | 生成或处理响应 | static、proxy、fastcgi、stub_status |
| filter 模块 | 修改响应头或响应体 | gzip、headers、chunked、write |
| upstream 模块 | 与后端通信 | proxy、fastcgi、uwsgi、scgi |
| phase 模块 | 挂载到请求处理阶段 | rewrite、access、limit_req、auth_basic |
| variable 模块 | 提供变量 | geo、map、realip |
9.2 ngx_module_t
每个模块都会有一个 ngx_module_t 结构。
1 | ngx_module_t ngx_http_xxx_module = { |
其中:
ctx:模块上下文,定义模块初始化和配置创建/合并回调。commands:模块支持的配置指令。type:模块类型,例如NGX_HTTP_MODULE。- 其他回调用于 master/init process/init thread/exit 等生命周期。
9.3 静态模块与动态模块
静态模块:
1 | ./configure --add-module=/path/to/module |
特点是模块编译进 Nginx 二进制,启动时天然存在,部署简单,但升级模块通常需要重新编译 Nginx。
动态模块:
1 | ./configure --add-dynamic-module=/path/to/module |
配置加载:
1 | load_module modules/ngx_http_xxx_module.so; |
特点是模块以 .so 文件加载,更灵活,但需要 ABI、编译参数和版本兼容,且 load_module 必须放在 main 上下文。
9.4 模块学习顺序
建议按下面顺序读模块:
1 | ngx_http_static_module |
原因是 static 模块能帮助理解 content handler;stub_status 模块代码相对短,适合理解最小模块形态;access/rewrite 能帮助理解 phase handler;proxy 能帮助理解 upstream;headers/gzip 能帮助理解 filter;limit_req 能帮助理解 shared memory 和 slab。
10. 事件模型
10.1 事件模块做什么
事件模块负责告诉 Nginx:哪些连接现在可读、哪些连接现在可写、哪些定时器超时。在 Linux 上,核心通常是 epoll:
1 | socket fd |
重点源码:
1 | src/event/ngx_event.c |
10.2 ngx_event_t
事件通常用 ngx_event_t 表示,连接读写各有一个事件:
1 | ngx_connection_t |
事件里最重要的是 handler:
1 | ev->handler(ev); |
也就是说,当某个 fd 可读或可写时,Nginx 会调用对应事件上挂载的 handler。不同阶段会不断替换 handler,从而推进请求状态机。
10.3 连接接受流程
新连接接受大致流程:
1 | 监听 socket 可读 |
重点函数:
ngx_event_acceptngx_get_connectionngx_http_init_connection
10.4 accept mutex 与 reuseport
多 worker 同时监听同一个端口时,可能出现惊群问题。Nginx 历史上使用 accept mutex 降低多个 worker 同时竞争 accept 的问题。新版本 Linux epoll 与内核能力已经缓解很多惊群问题,accept_mutex 与 reuseport 又是两个不同方向的机制:reuseport 会让多个 worker 拥有独立监听 socket,由内核分发连接。
生产中是否开启相关参数,要结合系统版本、Nginx 版本和压测结果判断。
10.5 定时器
Nginx 使用红黑树管理事件定时器。典型超时包括:
client_header_timeoutclient_body_timeoutsend_timeoutproxy_connect_timeoutproxy_read_timeoutproxy_send_timeout- keepalive 超时
源码重点:
1 | src/event/ngx_event_timer.c |
定时器逻辑简化:
1 | 添加事件定时器 |
这解释了为什么 Nginx 的 timeout 不是单独线程逐个扫描,而是与事件循环统一管理。
11. HTTP 请求生命周期
11.1 从连接到请求
HTTP 请求处理入口大致是:
1 | ngx_event_accept |
重点文件:
1 | src/http/ngx_http_request.c |
11.2 请求行解析
请求行示例:
1 | GET /api/user?id=1 HTTP/1.1 |
Nginx 要解析出 method、URI、args、HTTP version 和 request line 原始内容。常见变量来源如下:
| 变量 | 来源理解 |
|---|---|
$request |
原始请求行 |
$request_method |
请求方法 |
$request_uri |
原始 URI,通常包含 query string |
$uri |
规范化后的 URI,可能被 rewrite 改写 |
$args |
query string |
$is_args |
有参数时为 ?,无参数时为空 |
11.3 请求头解析
Nginx 会解析 HTTP header,并把常用头放到结构体中。
| 请求头 | 源码学习关注点 |
|---|---|
Host |
决定虚拟主机匹配 |
Connection |
影响 keepalive |
Content-Length |
影响请求体读取 |
Transfer-Encoding |
影响 chunked 请求体 |
Expect |
可能触发 100 Continue |
X-Forwarded-For |
需要配合 realip 模块处理 |
请求头过大时,可能出现 400 Bad Request、414 Request-URI Too Large、494 Request Header Too Large。对应配置包括:
1 | client_header_buffer_size 1k; |
11.4 请求体读取
不是所有请求都会立即读取 body。不同模块有不同策略:静态文件 GET 请求通常不需要请求体;proxy 模块需要根据配置决定是否把请求体转发给 upstream;access 阶段通常只依赖 header 和变量。
请求体相关配置:
1 | client_max_body_size 100m; |
理解源码时要注意:请求体读取可能是异步的,读不完整时会返回,等待下一次可读事件。
11.5 请求结束
请求结束并不只是返回状态码,还包括释放请求资源、写 access log、处理 lingering close、判断连接是否 keepalive、复用连接等待下一个请求,或关闭连接并释放 connection。
重点函数:
ngx_http_finalize_requestngx_http_free_requestngx_http_close_requestngx_http_set_keepalivengx_http_close_connection
12. phase handler 请求阶段
12.1 phase engine 的意义
Nginx HTTP 请求不是进入一个大函数从头执行到尾,而是被拆成多个阶段。每个阶段可以挂载多个 handler。
1 | post read |
这就是很多配置指令“看起来顺序相近,但执行阶段不同”的根本原因。
12.2 主要阶段说明
| 阶段 | 作用 | 常见模块 |
|---|---|---|
| post read | 读取请求头后早期处理 | realip |
| server rewrite | server 级 rewrite | rewrite |
| find config | 查找 location | core |
| rewrite | location 级 rewrite | rewrite |
| post rewrite | 处理 URI 改写后的跳转 | core |
| preaccess | 访问控制前置处理 | limit_req、limit_conn |
| access | 权限控制 | access、auth_basic、auth_request |
| post access | access 结果统一处理 | core |
| precontent | content 前处理 | try_files、mirror 等 |
| content | 生成响应或发起 upstream | static、proxy、fastcgi、stub_status |
| log | 请求结束后写日志 | log |
12.3 rewrite 阶段
rewrite 模块运行在 rewrite 阶段,可能触发 URI 重新查找 location。典型风险包括:last 会触发新的 location 匹配;break 停止当前 rewrite,但不一定重新匹配 location;复杂正则会增加 CPU 开销和维护成本;多次内部跳转可能触发循环保护。
源码学习重点:
1 | src/http/modules/ngx_http_rewrite_module.c |
12.4 access 阶段
access 阶段常见模块包括 ngx_http_access_module、ngx_http_auth_basic_module、ngx_http_auth_request_module。allow/deny 是非常典型的 access 阶段处理。
1 | location /admin/ { |
源码阅读时关注:模块如何注册 access handler;多个 access handler 之间如何组合;satisfy any 和 satisfy all 如何影响结果;access 拒绝如何变成 403。
12.5 content 阶段
content 阶段负责真正生成响应或把请求交给 upstream。典型 content handler 包括 static、proxy、fastcgi、return、stub_status。一个 location 通常最终只有一个主要 content handler,否则会发生覆盖或冲突。
12.6 log 阶段
log 阶段在请求结束后执行,典型模块是:
1 | src/http/modules/ngx_http_log_module.c |
access log 中很多变量是在请求生命周期中逐步填充的,例如 $status、$body_bytes_sent、$request_time、$upstream_status、$upstream_response_time。理解 log 阶段有助于解释为什么某些请求虽然失败,也仍然能写 access log。
13. location 匹配的源码理解
13.1 配置阶段构建 location
Nginx 在配置解析阶段会收集 location,并构建适合查找的数据结构。运行时请求进来后,不是简单按配置文件行号逐行匹配。
1 | location = /exact { } |
运行时查找要结合精确匹配、最长前缀匹配、^~ 前缀短路正则、正则匹配、命名 location 和内部跳转。
13.2 为什么 URI 改写会重新匹配
当 rewrite 使用 last 或 try_files 内部跳转时,Nginx 可能重新进入 location 查找。
1 | 原始 URI /a |
Nginx 对内部跳转次数有限制,避免配置错误导致无限循环。
13.3 学习 location 源码时关注什么
不要只背匹配优先级,要从源码角度理解 location 在配置阶段如何分类、普通前缀 location 如何组织、正则 location 为什么在前缀匹配后处理、命名 location 为什么只能内部跳转、rewrite/try_files/error_page 如何触发内部跳转,以及 alias 为什么需要特别处理路径拼接。
重点文件:
1 | src/http/ngx_http_core_module.c |
14. upstream 机制
14.1 upstream 是什么
upstream 是 Nginx 与后端服务通信的通用框架。proxy_pass、fastcgi_pass、uwsgi_pass、scgi_pass 等都基于 upstream 思想。
1 | client |
重点文件:
1 | src/http/ngx_http_upstream.c |
14.2 proxy 模块与 upstream 框架分工
ngx_http_proxy_module 负责 proxy 语义:解析 proxy_pass、生成发往后端的请求行、设置发往后端的请求头、处理 proxy 相关配置、指定 upstream 回调。
ngx_http_upstream.c 负责通用 upstream 流程:选择 peer、建立连接、管理超时、发送请求、接收响应头、转发响应体、失败重试、finalize upstream。
14.3 upstream 关键结构
| 结构 | 作用 |
|---|---|
ngx_http_upstream_t |
单次请求的 upstream 状态 |
ngx_http_upstream_srv_conf_t |
upstream 配置块状态 |
ngx_http_upstream_peer_t |
peer 选择和连接相关回调 |
ngx_peer_connection_t |
与后端连接相关的数据 |
ngx_http_upstream_state_t |
upstream 状态、耗时、响应码统计 |
理解 $upstream_* 变量时,ngx_http_upstream_state_t 很重要。
14.4 upstream 请求流程
典型流程:
1 | ngx_http_proxy_handler |
proxy 模块提供的关键回调包括:create_request 构造发给后端的 HTTP 请求;reinit_request 在重试时重新初始化请求;process_header 解析后端响应头;abort_request 异常中止;finalize_request 收尾。
14.5 超时源码理解
常见 upstream 超时:
1 | proxy_connect_timeout 3s; |
| 配置 | 含义 |
|---|---|
proxy_connect_timeout |
建立后端连接的超时时间 |
proxy_send_timeout |
向后端发送请求过程中的写超时 |
proxy_read_timeout |
等待后端响应过程中的读超时,不是完整接口总耗时 |
proxy_read_timeout 通常表示两次读操作之间的最大等待时间,而不是“整个 upstream 响应必须在这个时间内完成”。
14.6 失败重试
1 | proxy_next_upstream error timeout http_502 http_503 http_504; |
源码层面需要关注:哪些错误会触发 next upstream;请求体已经发送后是否还能安全重试;非幂等请求重试风险;upstream 状态如何记录到 $upstream_status;多个 upstream response time 为什么用逗号分隔。
14.7 upstream keepalive
1 | upstream backend { |
upstream keepalive 是 Nginx 到后端的连接复用,不是客户端到 Nginx 的 keepalive。keepalive 连接按 worker 维护,worker 数量会放大后端潜在连接数。长时间空闲连接可能被后端关闭,Nginx 需要处理 stale connection。
15. filter 链
15.1 filter 的作用
响应返回客户端前,会经过一组 filter。filter 可以处理响应头,也可以处理响应体。
1 | upstream/static/content handler |
| filter | 作用 |
|---|---|
| header filter | 输出响应头 |
| chunked filter | HTTP/1.1 chunked 编码 |
| gzip filter | 响应体压缩 |
| range filter | 范围请求 |
| not_modified filter | 304 处理 |
| headers filter | 添加或修改响应头 |
| write filter | 最终写入连接 |
15.2 header filter
header filter 处理状态码、Content-Type、Content-Length、Transfer-Encoding、Last-Modified、ETag、Cache-Control 和自定义响应头。
1 | add_header X-Request-Id $request_id always; |
headers 模块会通过 header filter 修改输出头。
15.3 body filter
body filter 处理响应体,通常以 ngx_chain_t 链表传递 buffer。典型场景包括 gzip 压缩响应体、chunked 编码输出、range 截取部分内容、sub_filter 替换响应体内容,以及 write filter 最终发送数据。
15.4 filter 链顺序
filter 模块初始化时会把自己插入 filter 链。常见写法:
1 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; |
理解这个机制后,就能解释为什么 filter 顺序会影响输出、为什么某些模块必须在特定 filter 前后执行、为什么 body filter 必须正确处理链表中的多个 buffer,以及为什么 filter 不应该假设响应体一次性完整到达。
16. 内存池、buffer 与 chain
16.1 ngx_pool_t
Nginx 内存池用于高效管理生命周期明确的内存。
| pool | 生命周期 |
|---|---|
| cycle pool | 当前 cycle 生命周期 |
| connection pool | 当前连接生命周期 |
| request pool | 当前请求生命周期 |
| upstream pool | 通常依附请求或 upstream 流程 |
内存池分配函数:
1 | void *ngx_palloc(ngx_pool_t *pool, size_t size); |
| 函数 | 特点 |
|---|---|
ngx_palloc |
按对齐方式分配 |
ngx_pnalloc |
不强制按默认对齐分配 |
ngx_pcalloc |
分配后清零 |
ngx_pool_cleanup_add |
注册 pool 销毁时的清理回调 |
16.2 大块内存与 cleanup
小块内存通常从 pool 当前块中分配,大块内存会单独分配并挂到 large 链表。某些资源不是普通内存,例如打开的文件、临时文件、共享资源引用或特殊句柄,可以通过 cleanup 机制在 pool 销毁时释放。
16.3 ngx_buf_t
Nginx 使用 ngx_buf_t 表示一段数据。buffer 可以来自内存、文件、mmap、临时文件或 upstream 接收缓冲。
| 字段/标记 | 含义 |
|---|---|
pos / last |
内存 buffer 中可用数据范围 |
file_pos / file_last |
文件 buffer 中可用数据范围 |
temporary |
可修改的临时内存 |
memory |
只读内存 |
in_file |
数据来自文件 |
flush |
要求刷新输出 |
last_buf |
整个响应最后一个 buffer |
last_in_chain |
当前 chain 最后一个 buffer |
16.4 ngx_chain_t
ngx_chain_t 是 buffer 链表:
1 | typedef struct ngx_chain_s ngx_chain_t; |
chain 的意义在于:响应体可能由多个片段组成;部分数据来自内存,部分来自文件;filter 可以逐段处理响应;非阻塞写可能一次写不完,需要保留未发送部分。学习 body filter 时必须理解 chain,否则很容易写出只处理第一个 buffer 的错误模块。
17. shared memory、slab 与 zone
17.1 为什么需要共享内存
Nginx worker 是多进程模型,普通内存不在 worker 之间共享。如果多个 worker 需要共享状态,就需要 shared memory。典型场景包括限流计数、连接数统计、proxy cache 元数据、upstream zone、SSL session cache。
17.2 shared memory zone
1 | limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; |
这里的 api_limit:10m 就是一个共享内存 zone。源码层面关注 zone 名称、zone 大小、init 回调、worker 如何访问共享内存,以及 reload 时新旧 cycle 如何处理同名 zone。
17.3 slab 分配器
共享内存里不能直接使用普通进程堆分配方式。Nginx 使用 slab 分配器管理 shared memory 中的小块内存。
1 | src/core/ngx_slab.c |
slab 适合在共享内存中分配小对象,由多 worker 访问,并配合锁保护共享结构。
17.4 limit_req 的源码主线
limit_req 是学习 shared memory 的好例子。
1 | limit_req_zone $binary_remote_addr zone=req_zone:10m rate=5r/s; |
源码重点:
1 | src/http/modules/ngx_http_limit_req_module.c |
需要理解:limit_req_zone 在 http 级别定义共享状态;key 通常是客户端 IP 或业务标识;zone 中存储 key 对应的请求速率状态;红黑树和队列用于查找与淘汰;多 worker 访问共享 zone 时需要锁;burst 和 nodelay 影响排队或立即拒绝策略。
18. 变量系统
18.1 变量不是简单字符串替换
Nginx 变量例如 $uri、$host、$request_time、$upstream_addr,背后通常是变量索引、取值回调和缓存标记。
变量可能来自:
- 请求行解析结果。
- 请求头。
- 连接信息。
- upstream 状态。
- map/geo 等模块计算。
- rewrite 脚本引擎。
- 第三方模块自定义。
18.2 变量求值时机
变量不一定在请求一开始就全部计算。很多变量是用到时才求值。
例如:
$request_time通常在日志阶段才有最终意义。$upstream_response_time只有访问 upstream 后才有意义。$sent_http_*在响应头发送阶段后更有意义。- map 变量可能在被引用时才执行匹配。
这解释了为什么某些变量在不同阶段值不同,或者在没有 upstream 时为空。
18.3 map 模块源码理解
map 是理解变量系统的好入口。
1 | map $http_upgrade $connection_upgrade { |
源码学习重点:map 如何注册变量;map 如何在配置阶段构建匹配结构;运行时如何根据源变量计算目标变量;正则 map 和普通 hash map 的差异。
重点文件:
1 | src/http/modules/ngx_http_map_module.c |
19. 日志系统源码理解
19.1 error log
error log 是 Nginx 内部统一日志系统,常见级别包括:
1 | debug, info, notice, warn, error, crit, alert, emerg |
源码里常见:
1 | ngx_log_error(NGX_LOG_ERR, log, err, "message"); |
debug 日志需要编译时带 --with-debug,配置 error_log ... debug,并可选使用 debug_connection 限定客户端。
19.2 access log
access log 属于 HTTP log 模块,发生在 log 阶段。
1 | log_format main '$remote_addr [$time_local] "$request" ' |
源码重点:
1 | src/http/modules/ngx_http_log_module.c |
关注点:log_format 如何在配置阶段编译成操作数组;请求结束后如何按变量生成日志行;buffer 日志与 gzip 日志如何工作;open_log_file_cache 如何缓存日志文件句柄。
19.3 为什么源码学习要重视日志
源码阅读不只是看函数调用,还要学会把日志和源码对应起来。
1 | 开启 debug log |
这比单纯从 main 开始硬读效率高很多。
20. reload、退出与热升级
20.1 常见信号
Nginx 使用信号控制进程行为:
| 信号/命令 | 作用 |
|---|---|
nginx -s reload / HUP |
重新加载配置 |
nginx -s quit / QUIT |
优雅退出 |
nginx -s stop / TERM/INT |
快速退出 |
nginx -s reopen / USR1 |
重新打开日志文件 |
| USR2 | 二进制热升级 |
| WINCH | 优雅关闭 worker |
20.2 优雅退出
优雅退出的核心是:
1 | worker 收到退出指令 |
这也是为什么长连接可能拖慢 reload 后旧 worker 的退出。
20.3 日志 reopen
日志切割常见流程:
1 | mv access.log access.log.1 |
reopen 后,master 会通知 worker 重新打开日志文件。否则即使文件名被移动,worker 仍可能写入旧文件句柄。
20.4 二进制热升级
热升级大致流程:
1 | 替换 nginx 二进制文件 |
热升级风险高于普通 reload,生产中必须有 SOP、备份和回滚验证。需要特别注意新旧二进制编译参数兼容、动态模块 ABI 兼容、共享内存 zone 兼容性、长连接和监听 fd 继承,以及失败后能否明确回滚到旧 master。
21. 最小 HTTP 模块开发
21.1 模块目录结构
建议创建目录:
1 | ngx_http_hello_module/ |
config 文件示例:
1 | ngx_addon_name=ngx_http_hello_module |
21.2 最小 content handler 模块
下面示例实现一个 hello_nginx 指令,在 location 中返回固定文本。
1 | #include <ngx_config.h> |
21.3 编译模块
静态编译:
1 | ./configure --prefix=/opt/nginx-hello --with-debug --add-module=/path/to/ngx_http_hello_module |
配置:
1 | server { |
验证:
1 | curl -i http://127.0.0.1:8080/hello |
预期:
1 | HTTP/1.1 200 OK |
21.4 content handler 模块要点
必须理解:指令通过 ngx_command_t 注册;指令解析函数把 clcf->handler 设置为自己的 handler;handler 中要检查请求方法;HEAD 请求只发送响应头;响应体要通过 ngx_buf_t 和 ngx_chain_t 输出;内存从 r->pool 分配,请求结束后自动释放;不要在请求 handler 中执行阻塞操作。
22. 最小 header filter 模块开发
22.1 目标
实现一个 header filter,为响应添加:
1 | X-Learn-Nginx: phase-7 |
22.2 核心代码骨架
1 | #include <ngx_config.h> |
22.3 filter 模块要点
- filter 模块通常在 postconfiguration 阶段挂入 filter 链。
- 需要保存原来的 top filter,然后把自己设置为新的 top filter。
- 执行完成后通常调用 next filter。
- header filter 不应该随意修改 body。
- body filter 必须处理 chain 中每个 buffer。
- filter 模块要特别注意顺序和兼容性。
23. 调试方法
23.1 用 debug log 追踪请求
1 | error_log logs/error.log debug; |
发起请求:
1 | curl -H 'Host: test.local' http://127.0.0.1:8080/api/users |
建议在日志中搜索:accept、http wait request handler、http process request line、http process request header line、http init upstream、connect to、http upstream request、http output filter、http log handler。
23.2 用 gdb 调试 worker
1 | ps -ef | grep nginx |
常用 gdb 命令:
1 | bt |
注意:Nginx 是多进程,断点要打在处理请求的 worker 上;如果 worker 多个,建议实验时设置 worker_processes 1;;debug 编译更容易看到符号;gdb 会暂停 worker,不能直接在生产高峰使用。
23.3 用 strace 看系统调用
1 | sudo strace -f -p <worker_pid> -e trace=network,file,epoll_wait,read,write |
可观察 epoll_wait、accept4、recvfrom、sendfile、writev、connect、openat、close。strace 适合验证 Nginx 是否卡在系统调用层,但会影响性能,生产慎用。
23.4 用 perf 看热点
1 | sudo perf top -p <worker_pid> |
适合分析 CPU 是否耗在 TLS、gzip、正则匹配、日志格式化或异常第三方模块热点。
23.5 用源码搜索定位
1 | rg "proxy_pass" src/http |
技巧:用配置指令名搜索 ngx_command_t;用 error log 中的英文报错搜索源码;用变量名搜索变量注册表;用函数名前缀判断模块归属;用 objs/ngx_modules.c 查看实际编译进来的模块顺序。
24. 核心源码阅读路线
24.1 第一轮:建立地图
目标:知道目录和主链路,不追求细节。
阅读顺序:
1 | src/core/nginx.c |
输出要求:
- 画出启动流程图。
- 画出 master/worker 职责图。
- 画出请求生命周期图。
24.2 第二轮:配置和 phase
目标:理解配置如何变成 handler。
阅读顺序:
1 | src/core/ngx_conf_file.c |
输出要求:
- 能说明一个指令如何被解析。
- 能说明 server/location 如何合并。
- 能说明 phase handler 如何注册和执行。
24.3 第三轮:upstream
目标:读懂 proxy_pass 主链路。
阅读顺序:
1 | src/http/modules/ngx_http_proxy_module.c |
输出要求:
- 画出 proxy 请求转发流程。
- 能解释 connect/send/read timeout。
- 能解释 upstream retry。
- 能解释
$upstream_status和$upstream_response_time。
24.4 第四轮:filter
目标:理解响应如何输出。
阅读顺序:
1 | src/http/ngx_http_header_filter_module.c |
输出要求:
- 能说明 header filter 和 body filter 的区别。
- 能说明 filter 链如何串起来。
- 能说明
ngx_buf_t和ngx_chain_t在输出中的作用。
24.5 第五轮:内存和共享状态
目标:理解高并发下的资源管理。
阅读顺序:
1 | src/core/ngx_palloc.c |
输出要求:
- 能说明 pool 的生命周期。
- 能说明 buffer/chain 的数据传递方式。
- 能说明 shared memory zone 的用途。
- 能说明 limit_req 或 proxy_cache 为什么需要共享内存。
25. 关键结构体速查
| 结构体 | 所在方向 | 作用 |
|---|---|---|
ngx_cycle_t |
core | 当前运行周期的全局上下文 |
ngx_conf_t |
core | 配置解析上下文 |
ngx_module_t |
core | 模块定义 |
ngx_command_t |
core | 配置指令定义 |
ngx_pool_t |
core | 内存池 |
ngx_array_t |
core | 动态数组 |
ngx_list_t |
core | 链式数组,常用于 header |
ngx_hash_t |
core | hash 表 |
ngx_rbtree_t |
core | 红黑树,timer、限流等使用 |
ngx_connection_t |
event | TCP 连接对象 |
ngx_event_t |
event | 读写事件对象 |
ngx_listening_t |
event/core | 监听端口对象 |
ngx_http_request_t |
http | 单个 HTTP 请求对象 |
ngx_http_connection_t |
http | HTTP 连接上下文 |
ngx_http_core_main_conf_t |
http | HTTP main 配置 |
ngx_http_core_srv_conf_t |
http | server 级核心配置 |
ngx_http_core_loc_conf_t |
http | location 级核心配置 |
ngx_http_upstream_t |
upstream | 单次 upstream 请求上下文 |
ngx_http_upstream_srv_conf_t |
upstream | upstream server 配置 |
ngx_buf_t |
output | 一段内存或文件数据 |
ngx_chain_t |
output | buffer 链表 |
ngx_table_elt_t |
http | HTTP header 项 |
建议学习时自己维护一份结构体笔记,记录字段含义、生命周期和典型使用位置。
26. 从配置反查源码的方法
26.1 通过指令名找模块
例如要研究:
1 | proxy_read_timeout 30s; |
步骤:
1 | rg "proxy_read_timeout" src |
通常会找到 ngx_command_t 定义,然后继续看:指令属于哪个模块、允许出现在哪些上下文、写入哪个配置结构字段、字段在哪里被使用。
26.2 通过日志反查源码
例如 error log 出现:
1 | upstream timed out (110: Connection timed out) while reading response header from upstream |
可以搜索:
1 | rg "upstream timed out" src |
然后定位到 upstream 读取响应头的超时处理路径。
26.3 通过变量名反查源码
例如要研究:
1 | $upstream_response_time |
搜索:
1 | rg "upstream_response_time" src/http |
关注变量在哪里注册、变量取值函数是什么、数据来源结构体是什么、多 upstream 重试时如何拼接。
26.4 通过模块顺序反查 filter
查看编译模块:
1 | cat objs/ngx_modules.c |
关注哪些模块被编译、filter 模块初始化顺序、第三方模块插入位置、动态模块加载顺序。
27. 与前六阶段知识的对应关系
| 前面阶段知识 | 源码对应主线 |
|---|---|
nginx -t |
配置解析、模块指令、cycle 初始化 |
server_name |
HTTP server 配置、虚拟主机查找 |
location |
location 树、正则 location、phase find config |
rewrite |
rewrite phase、script engine、内部跳转 |
try_files |
precontent 阶段、内部跳转 |
proxy_pass |
proxy module、upstream framework |
| 负载均衡 | upstream peer 选择、round robin |
proxy_next_upstream |
upstream 失败重试 |
proxy_read_timeout |
upstream read event timer |
| HTTPS | SSL 模块、OpenSSL 集成、握手事件 |
| 限流 | preaccess phase、shared memory、rbtree |
| gzip | body filter、buffer chain |
add_header |
header filter |
| 静态资源 | static content handler、sendfile |
open_file_cache |
文件缓存、打开文件句柄缓存 |
| access log | log phase、变量系统 |
| reload | master 信号、cycle 重建、worker 优雅退出 |
| 高可用 | 进程模型、监听 fd、平滑切换基础 |
学习源码时要不断回到配置和生产问题,这样不会变成孤立的源码阅读。
28. 必做实验
28.1 实验一:debug 编译并启动
目标:获得可调试的 Nginx。
1 | ./configure --prefix=/opt/nginx-debug --with-debug --with-http_ssl_module --with-http_stub_status_module |
过关标准:能看到 --with-debug,能启动 Nginx,能通过 curl 访问默认页面。
28.2 实验二:跟踪启动流程
目标:理解 master/worker 如何启动。
1 | ps -ef | grep nginx |
观察 master PID、worker PID、listen socket 创建、fork worker、worker 进入 epoll_wait。
28.3 实验三:跟踪一次 HTTP 请求
配置:
1 | worker_processes 1; |
请求:
1 | curl -i http://127.0.0.1:8080/ |
任务:找到 accept 日志、request line 解析日志、header 解析日志、content handler 相关日志、output filter 相关日志、request finalize 和 log 阶段日志。
28.4 实验四:跟踪 location 匹配
配置多个 location:
1 | location = /exact { return 200 exact; } |
任务:请求不同 URI,对照 debug log 判断命中的 location,搜索 ngx_http_core_find_location,理解最长前缀和正则匹配的实际顺序。
28.5 实验五:跟踪 proxy_pass
配置:
1 | upstream backend { |
任务:启动两个简单后端,请求 /api/,观察 upstream 连接日志,停掉一个后端观察失败重试,对照 $upstream_addr、$upstream_status、$upstream_response_time,阅读 ngx_http_proxy_handler 到 ngx_http_upstream_init 的调用链。
28.6 实验六:观察 filter 链
任务:开启 gzip,添加 add_header,请求静态文本文件,对比开启/关闭 gzip 的响应头和 body,阅读 gzip filter 和 headers filter 初始化位置,理解 filter 插入顺序。
28.7 实验七:分析内存池
任务:在 gdb 中断到 ngx_http_create_request,打印 r->pool,在 content handler 中观察从 r->pool 分配 buffer,请求结束后观察 request pool 释放路径。
过关标准:能说明 request pool 生命周期,并能说明为什么模块不要把 request pool 中的指针保存到更长生命周期对象中。
28.8 实验八:观察 limit_req shared memory
配置:
1 | limit_req_zone $binary_remote_addr zone=req_zone:10m rate=1r/s; |
任务:连续快速请求触发限流,阅读 ngx_http_limit_req_module.c,找到 shared memory zone 初始化、key 查找、节点插入、过期淘汰逻辑。
28.9 实验九:编写 hello 模块
目标:实现最小 content handler 模块。任务包括创建模块目录,编写 config 和 .c 文件,静态编译进 Nginx,配置 hello_nginx;,使用 curl -i 验证响应。
28.10 实验十:编写 header filter 模块
目标:理解 filter 链。任务包括编写最小 header filter,添加 X-Learn-Nginx 响应头,编译并启动,验证响应头存在,与 add_header 模块对比实现方式。
29. 常见源码阅读误区
29.1 一开始逐行读
Nginx 源码包含大量平台兼容、宏、错误处理和性能优化细节。逐行读容易迷失。正确方式是:
1 | 先读主线 |
29.2 只读文章不跑实验
源码理解必须和运行现象对应。至少要配合 debug log、curl 请求、gdb 断点、strace 系统调用和 access log 变量,否则很容易只记住结论,不知道如何验证。
29.3 忽略配置合并
很多配置问题不是指令本身,而是继承和合并导致最终值不同。阅读模块时一定要看 create conf、merge conf、指令 set 函数,以及 handler 使用的最终 conf。
29.4 把 worker 当线程理解
worker 是进程,不是线程。不同 worker 的普通内存不共享。共享状态必须通过 shared memory、文件、socket 或外部系统实现。
29.5 在 handler 中写阻塞逻辑
自定义模块中不能随便做阻塞 DNS、阻塞 HTTP 调用、慢磁盘 IO 或长时间 CPU 计算。一个 worker 被阻塞,会影响该 worker 上所有连接。
29.6 错误处理不完整
Nginx 模块开发必须处理内存分配失败、非法配置、不支持的请求方法、请求提前关闭、upstream 失败、filter 链返回错误、reload 和退出场景。
29.7 错误使用 request pool 指针
从 r->pool 分配的内存只在请求生命周期内有效。不能把它保存到共享内存、全局变量或长生命周期结构中。
29.8 忽略 filter 的链式和异步特性
body filter 不能假设一次收到完整响应体,也不能只处理第一个 buffer。必须正确遍历 chain,并处理 last_buf、flush、sync 等标记。
30. 源码学习检查清单
30.1 启动与进程
- 能找到
main函数。 - 能说明
ngx_init_cycle做了什么。 - 能说明 master 如何 fork worker。
- 能说明 worker 如何进入事件循环。
- 能说明 reload 的新旧 cycle 切换逻辑。
30.2 配置与模块
- 能解释
ngx_command_t。 - 能解释
ngx_module_t。 - 能说明指令上下文校验。
- 能说明 main/server/location 配置创建与合并。
- 能说明静态模块和动态模块区别。
30.3 事件与连接
- 能说明
ngx_connection_t与ngx_event_t的关系。 - 能说明 epoll 如何驱动 read/write handler。
- 能说明 accept 新连接流程。
- 能说明定时器如何管理超时。
- 能说明非阻塞 IO 下
NGX_AGAIN的意义。
30.4 HTTP 请求
- 能说明请求行和请求头解析流程。
- 能说明 request pool 生命周期。
- 能说明 keepalive 请求如何复用连接。
- 能说明 request finalize 的作用。
- 能说明 access log 为什么在 log 阶段写入。
30.5 phase 与 location
- 能列出主要 phase。
- 能说明 rewrite/access/content/log 的执行位置。
- 能说明 location 查找过程。
- 能说明内部跳转为什么会重新匹配 location。
- 能说明 try_files 与 rewrite 的阶段差异。
30.6 upstream 与 filter
- 能说明 proxy 模块如何接入 upstream。
- 能说明 upstream connect/send/read 流程。
- 能说明 upstream retry 的触发条件。
- 能说明 header filter 和 body filter 的区别。
- 能说明
ngx_buf_t和ngx_chain_t的作用。
30.7 内存与共享状态
- 能说明 pool 小块和大块分配。
- 能说明 cleanup 回调用途。
- 能说明 shared memory zone 的用途。
- 能说明 slab 为什么用于共享内存。
- 能说明 limit_req 或 proxy_cache 的共享状态设计。
30.8 模块开发
- 能写最小 content handler。
- 能写最小 header filter。
- 能正确声明配置指令。
- 能正确处理 HEAD 请求。
- 能避免阻塞 worker。
- 能用 debug log 和 gdb 调试模块。
31. 推荐学习节奏
建议本阶段拆成 6 周完成:
| 周次 | 主题 | 产出 |
|---|---|---|
| 第 1 周 | 源码目录、编译、debug、启动流程 | 源码地图、debug 环境、启动流程图 |
| 第 2 周 | 配置解析、模块体系、server/location 合并 | 指令解析笔记、配置合并流程图 |
| 第 3 周 | event、connection、timer、HTTP 请求解析 | 请求从 accept 到 phase 的链路图 |
| 第 4 周 | phase handler、rewrite、access、content、log | phase 表格、location 匹配实验记录 |
| 第 5 周 | upstream、proxy、filter、buffer、chain | proxy_pass 源码链路图、filter 实验记录 |
| 第 6 周 | pool、shared memory、limit_req、模块开发 | hello 模块、header filter 模块、总结文档 |
如果时间有限,至少完成下面最小闭环:
1 | debug 编译 |
32. 面试与生产问题思考
32.1 为什么 Nginx 高并发能力强
推荐回答主线:
1 | master/worker 多进程 |
不要只回答“因为用了 epoll”。epoll 只是其中一部分。
32.2 reload 为什么通常不中断请求
推荐回答主线:
1 | master 收到 HUP |
同时补充边界:长连接、慢请求、WebSocket、模块兼容、共享内存变化都可能影响 reload 表现。
32.3 502 和 504 在源码链路上怎么理解
- 502 更偏 upstream 返回异常、连接失败、响应头非法、后端提前关闭等。
- 504 更偏 upstream 超时,例如连接超时、发送超时、读取响应超时。
- 源码关注
ngx_http_upstream.c中连接、读写事件、超时处理和 finalize 逻辑。
32.4 proxy_read_timeout 是总耗时吗
不是简单的接口总耗时。它更接近两次读取 upstream 响应之间允许等待的最大时间。只要后端持续有数据返回,整体响应时间可能超过该值。
32.5 为什么自定义模块不能随便阻塞
因为 worker 通常是单线程事件循环。如果模块 handler 中执行阻塞调用,该 worker 上的其他连接都会受影响。高并发场景下,这会导致延迟抖动甚至大面积超时。
32.6 为什么多 worker 需要 shared memory
worker 是多进程,普通内存互不共享。限流计数、连接统计、cache 元数据等跨 worker 状态必须放到 shared memory 或外部系统中。
33. 最终过关标准
完成第七阶段后,应达到下面标准:
- 能画出 Nginx 启动、配置解析、worker 事件循环的主流程。
- 能解释 master/worker、cycle、module、connection、request 的关系。
- 能从源码角度说明 Nginx 高并发能力来源。
- 能说明 HTTP 请求从 accept 到 access log 的完整内部路径。
- 能解释 server/location 匹配、rewrite、access、content、log 的阶段关系。
- 能解释
proxy_pass如何进入 upstream,并说明 connect/send/read/timeout/retry 流程。 - 能解释 header filter、body filter、write filter 的协作方式。
- 能说明内存池、buffer、chain、shared memory、slab 的用途和边界。
- 能使用 debug log、gdb、strace、perf 辅助定位源码路径。
- 能通过指令名、日志文本、变量名反查对应源码。
- 能读懂常见 HTTP 模块的基本结构。
- 能编写并编译一个最小 content handler 模块。
- 能编写一个最小 header filter 或 log 阶段模块。
- 能把源码理解反哺生产排障、性能优化和模块选型。
34. 本阶段总结
第七阶段是 Nginx 学习路线中最深入的一层。前六个阶段解决“如何正确使用 Nginx”,第七阶段解决“为什么 Nginx 会这样运行,以及复杂问题该从哪里定位”。
学习本阶段时要始终抓住几条主线:
1 | 启动主线:main -> cycle -> master -> worker |
真正掌握第七阶段后,看到一个 Nginx 配置项,不仅知道怎么写,还能大致知道它属于哪个模块、在哪个阶段执行、使用哪些核心结构、可能带来哪些性能和稳定性影响。这就是从熟练使用者走向专家级理解的关键一步。
