# 《深入理解Nginx:模块开发与架构解析(第2版)》知识点全解 > AI生成|2026年10月29日|codebuddy|原书:陶辉《深入理解Nginx:模块开发与架构解析(第2版)》,机械工业出版社,2016-02-01 > **我的阅读**:进度 100%|累计 2小时54分钟|开始 2020-04-02|最近 2020-07-19|读完 2020-04-07 **一句话总结**:这本书回答两个问题——Nginx 为什么能用一个 master 加若干 worker 撑住高并发,以及如何按它的框架写出自己的 HTTP 模块(含过滤、upstream、subrequest),从而把 Nginx 从"会配"推进到"会改、会扩"。 **适合谁 / 前置知识**:适合已能独立写 nginx.conf、想读源码或做二次开发的后端/运维/SRE;需要 C 语言(指针、结构体、函数指针、宏)、Linux 系统编程(fork、epoll、文件描述符、sendfile)与 HTTP 协议基础。只想学"怎么配反向代理、怎么调缓存"的读者不必读本书,官方文档和 cookbook 更快;不熟悉 C 的读者会卡在第 3 章之后的代码与结构体定义上。 ## 一、全书地图 | 章节 | 主题 | 在全书中的位置 | |---|---|---| | 第1章 | Nginx 是什么、为什么选它、准备工作、编译安装、configure 详解、命令行控制 | 全景与动手入口:先能编译出带调试信息的 nginx | | 第2章 | 进程间关系、配置通用语法、基本配置、静态 Web 服务器、反向代理 | 使用层:把配置项挂到"进程模型 + 模块"上理解 | | 第3章 | 如何调用 HTTP 模块、准备工作、编译进 Nginx、数据结构、定义模块、处理请求、发送响应、发送磁盘文件、用 C++ 写模块 | 开发层起点:一个最小可用模块的完整生命周期 | | 第4章 | http 配置项的使用场景、怎样使用 http 配置、HTTP 配置模型、error 日志、请求上下文 | 把第3章的"能跑"升级为"配置可继承、日志可定位、状态可保存" | | 第5章 | upstream 使用方式、回调执行场景、示例;subrequest 使用方式、主要场景、示例 | 横向扩展:让 Nginx 主动访问第三方服务 | | 第6章 | 过滤模块的意义、调用顺序、开发步骤、简单例子 | 纵向切入:在响应链路上做无侵入加工 | | 第7章 | 高级数据结构概述、ngx_queue_t、ngx_array_t、ngx_list_t、ngx_rbtree_t、ngx_radix_tree_t、通配符散列表 | 工具箱:后面所有模块都建立在这些容器上 | | 第8章 | Web 服务器设计的关键约束、Nginx 架构设计、ngx_cycle_t、启动流程、worker 与 master 如何工作、ngx_pool_t | 骨架:进程、配置、内存三条主线收束 | | 第9章 | 事件处理框架、事件与连接的定义、ngx_events_module、ngx_event_core_module、epoll、定时器、事件驱动流程、文件异步 I/O、TCP 与 Nginx | 心脏:高并发的真正来源 | | 第10章 | HTTP 框架的初始化 | 收口:HTTP 模块赖以生存的框架如何搭起来 | 逻辑是"两条腿走路":第 2→3→4→5→6 章是**开发者路线**(先会用,再写模块,再处理配置与上下文,再做 upstream/subrequest 与过滤);第 7→8→9→10 章是**架构路线**(先有容器,再有进程与内存池,再有事件驱动,最后回到 HTTP 框架初始化)。两条线在第 3、4 章交汇:不懂配置模型写不出可配置的模块,不懂内存池与事件就写不出不泄漏、不阻塞的模块。目录止于第 10 章,其后内容未在给定目录中列出,不做推断。 ## 二、核心知识点 **1. master-worker 进程模型** 是什么:master 负责读配置、绑定监听端口、fork 并管理 worker、处理 reload/升级/退出信号;worker 各自独立地 accept 连接、跑事件循环,彼此不共享内存。为什么重要:这是 Nginx 能用多核又不牺牲稳定性的根因——worker 崩溃不拖垮 master,reload 只需换一批 worker。怎么用:`worker_processes auto`(或按核数)、`worker_connections` 决定单 worker 并发上限,两者相乘是理论连接天花板。常见误区:以为 worker 越多吞吐越高;实际受内存、CPU 缓存、上下文切换限制,且每个连接都占 fd 与内存池。 **2. 配置语法与"配置块即上下文"** 是什么:一条指令 = 名字 + 参数 + 分号;`{}` 形成配置块(main/http/server/location/upstream 等);支持 `include`、变量 `$host`、单位后缀(`10m`、`30s`)。为什么重要:Nginx 的配置不是"参数表",而是一棵与模块上下文一一对应的树,解析结果直接变成内存中的 conf 结构。怎么用:看到 `location` 就想"这是 loc_conf 的作用域",看到 `http` 就想 main_conf。常见误区:把 `if` 当通用编程语句;它属于 rewrite 模块,语义受限且有陷阱,能不用就不用。 **3. 配置模型:ngx_command_t、conf_ctx 与 merge** 是什么:每个指令用 `ngx_command_t` 描述(名字、可出现的上下文位掩码、回调 set、目标 conf、偏移量);解析时 `ngx_conf_parse` 递归下降,`ngx_http_block` 依次建立 main/server/loc 三级数组;最后 merge:server 继承 main,location 继承 server,缺省值在 merge 阶段补齐。为什么重要:这是"为什么 location 里不写某指令也能生效"的答案,也是自定义指令必须遵守的契约。怎么用:`NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1` 控制可写位置,`set` 回调里用 `ngx_conf_set_str_slot` 等现成函数。常见误区:只在 create 里初始化、忘了在 merge 里处理"未配置"的哨兵值,导致继承失效或值被覆盖成 0。 **4. ngx_module_t 与模块上下文** 是什么:模块是一个 `ngx_module_t` 全局变量,带 `ctx`(HTTP 模块用 `ngx_http_module_t`:preconfiguration、postconfiguration、create/init main_conf、create/merge srv_conf、create/merge loc_conf)、`commands`、`type`,以及 `init_module/init_process/exit_process` 等钩子。为什么重要:Nginx 没有"注册中心",靠编译期生成的 `ngx_modules.c` 顺序 + 这些钩子完成装配。怎么用:postconfiguration 是挂 phase handler 和 filter 的标准位置;init_process 在每个 worker 启动时执行一次。常见误区:以为模块顺序无关紧要;`ngx_modules.c` 的顺序直接影响过滤链与 phase handler 的先后。 **5. HTTP 的 11 个阶段与 phase handler** 是什么:一次请求依次经过 POST_READ、SERVER_REWRITE、FIND_CONFIG、REWRITE、POST_REWRITE、PREACCESS、ACCESS、POST_ACCESS、PRECONTENT、CONTENT、LOG 十一个阶段;模块把 handler 压入 `phases[phase].handlers` 数组。为什么重要:它把"鉴权、限流、rewrite、生成内容、记日志"解耦成可插拔的横切点,是 Nginx 模块化最直接的体现。怎么用:内容型模块挂 CONTENT(如 static、proxy),访问控制挂 ACCESS(如 auth_basic、allow/deny)。常见误区:在 CONTENT 阶段注册却期待它在 rewrite 之前跑;或把 LOG 阶段当"请求结束回调"做耗时操作。 **6. ngx_http_request_t 与返回值状态机** 是什么:请求对象持有请求行、headers_in/headers_out、uri/args、connection、`ctx`(按模块 ctx_index 索引的上下文数组)与 `count` 引用计数;handler 用返回值表达意图:NGX_OK(本阶段通过)、NGX_DECLINED(交给同阶段下一个 handler)、NGX_AGAIN(需等事件,稍后回调)、NGX_DONE、NGX_ERROR。为什么重要:Nginx 是非阻塞状态机,任何"等"都必须以 NGX_AGAIN + 回调表达,直接阻塞会拖死整个 worker。怎么用:异步操作完成后重新调用 `ngx_http_core_run_phases(r)` 或直接调 content handler。常见误区:把 NGX_AGAIN 当错误返回给客户端;或在异步回调里重复发送响应头。 **7. 响应发送:header filter 与 body filter** 是什么:`ngx_http_send_header(r)` 走一遍响应头过滤链,`ngx_http_output_filter(r, out)` 走响应体过滤链;包体用 `ngx_chain_t` 串起 `ngx_buf_t`,缓冲区可指向内存(pos/last)或文件(file_pos/file_last + in_file)。为什么重要:这是所有"输出内容"模块的唯一正道,也是 gzip、chunked、sub_filter、addition 等模块的插入点。怎么用:构造 buf → 设 `last_buf=1` 表示结束 → 逐级 filter 传递,返回 NGX_AGAIN 时挂写事件等可写再继续。常见误区:自己 write() socket 绕过过滤链,导致 gzip/日志/keepalive 全部失效;或忘记置 last_buf 让客户端一直等。 **8. 发送磁盘文件与 sendfile** 是什么:打开文件得到 fd,构造指向文件的 buf(`in_file=1`),交给 output filter;开启 `sendfile on` 时由内核零拷贝送出,必要时配合 `tcp_nopush`。为什么重要:静态资源是 Nginx 的主战场,零拷贝能显著减少用户态/内核态复制与内存带宽。怎么用:用 `ngx_pool_cleanup_add` 注册 close 回调,保证请求结束或异常时 fd 一定关闭;大文件用 `ngx_http_send_special` 或多次 output。常见误区:认为 sendfile 永远更快——需要改写内容(如 gzip、sub_filter)时它会被绕过;小文件收益有限;忘记 cleanup 会泄漏 fd。 **9. 过滤模块与调用顺序** 是什么:过滤模块在 postconfiguration 里把自己的 handler 存进 `ngx_http_next_body_filter`(头过滤同理),形成单向链;新模块插在链头,因此**注册越晚越先执行**,实际执行顺序与 `ngx_modules.c` 中的排列相反。为什么重要:顺序错了会出现"先压缩后加广告"这类逻辑错误,且很难从配置看出来。怎么用:明确自己要在压缩前还是压缩后处理;处理 `NGX_AGAIN` 与 `last_buf`,不要把未完成的 buf 丢掉。常见误区:以为"写在配置文件里的模块顺序"决定过滤顺序;或在过滤模块里修改 `r->headers_out.content_length_n` 却不重算。 **10. upstream:把请求转给第三方服务** 是什么:`ngx_http_upstream_t` 定义了一次后端交互,核心回调是 create_request(构造发往后端的请求)、reinit_request(重试时重置)、process_header(解析后端响应头)、input_filter(可选,处理包体)、finalize_request(收尾);由 `ngx_http_upstream_init(r)` 启动,连接、发送、读响应都由框架驱动。为什么重要:proxy、fastcgi、memcached 全建在这套抽象上,理解了它才能写自定义协议模块或排查 502/504。怎么用:在 content handler 里准备好 upstream、设置回调与 `request_bufs`,然后 init;peer 选择交给 upstream 块的负载均衡模块。常见误区:在 process_header 里只读一行就认为头结束;不处理后端提前关连接;重试时忘记 reinit 导致状态串味。 **11. subrequest:一个请求派生出多个子请求** 是什么:`ngx_http_subrequest(r, &uri, &args, &sr, ps, flags)` 在主请求内发起子请求,子请求共享连接与输出缓冲,`count` 加一,父请求等所有子请求结束才收尾;加 `NGX_HTTP_SUBREQUEST_IN_MEMORY` 可把子响应留到内存里供父请求加工。为什么重要:SSI、auth_request、mirror、addition 这类"一个入口聚合多份内容"的功能都靠它,且不额外占用客户端连接。怎么用:只能在 content 阶段发起;父请求用 postpone_output 暂缓输出,子请求完成后在回调里合并再发。常见误区:以为子请求会新开一条客户端连接或能直接回包给用户;在子请求里再无限嵌套导致递归。 **12. ngx_pool_t 内存池** 是什么:请求/连接级别的分配器,小内存从当前块切分,大块单独挂到 large 链表,统一在池销毁时释放;`ngx_pool_cleanup_add` 可注册任意清理回调(关 fd、释放外部资源)。为什么重要:Nginx 的"每请求一池"把内存生命周期简化为"请求结束即全部回收",既避免泄漏又省去逐块 free 的开销。怎么用:模块里尽量用 `ngx_palloc(r->pool, size)`;跨请求存活的数据才用 `ngx_cycle->pool` 或自己管理。常见误区:以为可以 free 单块内存(不支持);把大对象长期挂在请求池上导致内存峰值偏高。 **13. 高级容器:queue / array / list / rbtree / radix tree / 通配符哈希** 是什么:`ngx_queue_t` 是侵入式双向链表(用 `ngx_queue_data` 从节点反推宿主);`ngx_array_t` 是池上分配的动态数组;`ngx_list_t` 是"数组 + 链表"的分段存储;`ngx_rbtree_t` 是带哨兵的红黑树(定时器、变量、geo 都用);`ngx_radix_tree_t` 是基数树,适合网段/IP 前缀匹配;通配符散列表支持 `*.example.com` 形式的前后缀匹配。为什么重要:这些结构决定了 Nginx 在十万级 key 下仍能 O(log n) 或 O(1) 查询。怎么用:定时器用红黑树按超时排序,server_name 通配符用哈希,geo/限流用基数树。常见误区:把 `ngx_list_t` 当普通链表按 index 随机访问(跨段代价高);红黑树节点未初始化就 insert。 **14. 事件模块:event、connection、epoll 与定时器** 是什么:`ngx_event_t` 描述一次读写/定时/accept 事件(handler、data、timer、timedout),`ngx_connection_t` 把 fd、读写事件、recv/send/send_chain 函数指针、pool 绑在一起;epoll 模块用 `epoll_ctl` 注册 EPOLLIN/EPOLLOUT(可边缘触发),`ngx_epoll_process_events` 批量取事件后回调;定时器用红黑树,`ngx_event_find_timer` 算出最近超时作为 epoll_wait 的超时。为什么重要:这就是"单线程扛万级连接"的全部秘密——没有阻塞调用,只有事件回调。怎么用:`ngx_handle_read_event/write_event` 管理关注点;accept 用 `ngx_accept_mutex` 或 reuseport 做多 worker 负载均衡。常见误区:在 handler 里做同步磁盘 I/O 或 DNS 解析,阻塞整个 worker;LT/ET 模式下读写未读到 EAGAIN。 **15. ngx_cycle_t、启动流程与请求上下文** 是什么:`ngx_cycle_t` 是"一次配置的生命周期",持有 conf_ctx、modules、listening 数组、connections、pool、old_cycle;启动时 `ngx_init_cycle` 解析配置、建池、初始化模块、打开监听,再由 master 循环 fork worker,worker 执行 `init_process` 后进入 `ngx_process_events_and_timers`。请求上下文用 `r->ctx[module.ctx_index]` 保存,配 `ngx_http_get_module_ctx/ngx_http_set_ctx` 读写。为什么重要:reload 的本质是新建一个 cycle、平滑替换旧 cycle;模块状态放错地方就会在 reload 后丢失或串请求。怎么用:请求级状态放 r->ctx,进程级放模块静态变量并在 init_process 初始化。常见误区:把每请求数据放在模块全局变量里,并发下互相覆盖。 ## 三、关键概念速查表 | 概念 | 一句话解释 | 用在哪儿 | |---|---|---| | master / worker | 管理进程 + 干活的进程,worker 相互独立 | 进程模型、reload、平滑升级 | | ngx_module_t | 模块的唯一入口结构,含 ctx、commands 与生命周期钩子 | 所有模块的定义与注册 | | ngx_command_t | 一条配置指令的元描述(名字、上下文、回调) | 自定义指令解析 | | conf_ctx | 按 main/server/loc 三级组织的配置数组 | 配置继承与 merge | | phase handler | 挂在 11 个请求阶段上的处理函数 | 鉴权、rewrite、内容生成 | | ngx_http_request_t | 一次请求的全部状态与输入输出 | 任何 handler 的入参 | | ngx_chain_t / ngx_buf_t | 包体的链式描述,可指向内存或文件 | 发送响应、过滤 | | header/body filter | 响应头、响应体的单向过滤链 | gzip、sub_filter、addition | | upstream | 与后端服务一次交互的回调集合 | proxy、fastcgi、自定义协议 | | subrequest | 主请求内部派生的子请求,共享连接 | SSI、auth_request、聚合页面 | | ngx_pool_t | 请求级内存池,整池释放 | 所有临时内存分配 | | ngx_queue_t | 侵入式双向链表 | 定时器队列、LRU、模块内部链表 | | ngx_rbtree_t | 带哨兵的红黑树 | 定时器、变量查找、geo | | ngx_radix_tree_t | 按二进制位分裂的基数树 | IP/网段匹配、限流 | | ngx_event_t / ngx_connection_t | 事件与连接的绑定单元 | epoll 回调、读写状态机 | | ngx_cycle_t | 一次配置从加载到退出的生命周期 | 启动、reload、共享内存 | ## 四、可复用的方法/框架 1. **读 Nginx 源码的三层法**:先看配置指令(它暴露了模块的输入)→ 再看模块结构体与钩子(它暴露了执行时机)→ 最后看核心回调(它暴露了状态机)。任何模块都能用这三层在半小时内画出骨架。 2. **写模块的固定清单**:定义 `ngx_module_t` 与 `ngx_http_module_t` → 写 `ngx_command_t` 与 set 回调 → 在 create/merge 里管理配置生命周期 → 在 postconfiguration 注册 phase 或 filter → 用请求池分配内存 → 用 cleanup 兜底资源 → 用 error 日志埋点。 3. **判断"该不该异步"**:凡是有可能等待的操作(磁盘、DNS、后端、锁),都必须拆成"发起 + 回调"两段,中间返回 NGX_AGAIN;不能拆的就不要放进 worker。 4. **配置继承心智模型**:create 给默认值 → parse 填用户值 → merge 补继承值。调试配置不生效时,按这三步定位,而不是猜。 5. **性能问题分层排查**:连接数(worker_connections/fd)→ 事件(是否阻塞、是否 ET 未读完)→ 内存(池峰值、大对象)→ 上游(超时、重试、keepalive)。逐层排除比调参有效。 ## 五、我的阅读轨迹 | 项目 | 数据 | |---|---| | 进度 | 100% | | 累计阅读 | 2小时54分钟 | | 开始阅读 | 2020-04-02 | | 最近阅读 | 2020-07-19 | | 读完时间 | 2020-04-07 | 2 小时 54 分钟读完一本以源码为主的书,实际是"通读 + 跳读代码",第 3、4、5、9 章的细节大概率只留了印象。读完(04-07)之后到最近阅读(07-19)仍有回看,说明当时在按需查目录。复习建议:**优先重读第 4 章(配置模型与请求上下文)、第 5 章(upstream 回调场景)、第 9 章(事件驱动流程)**——这三处是"看得懂但写不出"的高发区;第 7 章可作为工具书随用随查;第 1、2 章只需在动手前扫一遍。若只能投入两小时,按"第 4 章 → 第 3 章代码 → 第 9 章流程"的顺序,配合自己写一个最小模块,收益最高。 ## 六、行动清单 - 用 `--with-debug` 编译一份 Nginx,跑通 error_log 的 debug 级别,观察一次请求的完整阶段日志。 - 写一个最小 HTTP 模块:注册一条 `my_hello` 指令 + CONTENT 阶段 handler,返回固定字符串。 - 给该模块加一条 loc 级配置项,验证"不写时继承 server、server 不写时继承 http"的 merge 行为。 - 写一个 body filter,在响应末尾追加一行文本,观察它与 gzip 的先后顺序。 - 用 `ngx_http_subrequest` 实现一个聚合页:主请求拉两个子请求,合并后输出。 - 用 `ngx_http_upstream_t` 对接一个自定义协议后端,覆盖超时、重试、后端提前断开三种场景。 - 用 `ngx_pool_cleanup_add` 管理一个打开的 fd,故意制造异常路径验证不泄漏。 - 画一张自己项目的请求链路图:阶段 → 模块 → 上游 → 日志,标出每层的超时与重试。 ## 七、延伸阅读 1. **《HTTP权威指南》**(Gourley 等)——先读。补 HTTP 语义、缓存、连接管理;本书第 2、5 章的大量行为都以此为前提,缺了它容易把 Nginx 的配置当成"魔法"。 2. **《UNIX网络编程 卷1:套接字联网API》**(Stevens)——与本书第 9 章并行读。epoll、非阻塞 connect、TCP 状态迁移的底层解释在这里;读完再看 Nginx 的 connection 结构会顺很多。 3. **《Linux多线程服务端编程:使用muduo C++网络库》**(陈硕)——最后读。用另一个事件驱动框架对照 Nginx 的 reactor 设计与对象生命周期管理,能看清哪些是 Nginx 特色、哪些是通用范式。