核心是用请求级内存池替代逐块释放:每个请求独享 ngx_pool_t,处理完整批销毁;禁用 malloc/calloc/strdup,小块走线性分配,大块走 large 链表;复用 chain/buffer、预分配静态头、盯紧第三方模块与 Lua 脚本的非 pool 分配。

核心思路是放弃“逐块释放”,转而用请求生命周期对齐内存管理——每个请求独享一个内存池,处理完就整批销毁,从根源上绕过碎片产生条件。
用好 ngx_pool_t,别碰 malloc
Nginx 的内存池(ngx_pool_t)不是用来替代 malloc 的“更好 malloc”,而是彻底换了一套逻辑:不回收、只归还。所有请求级临时数据(headers、buf、结构体)必须用 ngx_palloc 或 ngx_pcalloc 分配,禁用 malloc/calloc/strdup 等 libc 调用。模块代码里出现这些函数,基本等于主动引入隐式堆碎片。
- 结构体优先用 ngx_pcalloc,自动清零,避免脏内存引发越界
- 小块内存(≤4095 字节)走线性分配,无元数据、无锁、O(1)
- 大块(如上传 body)走 large 链表,单独 malloc,不污染主池
管住 chain 和 buffer 的生命周期
HTTP 处理中大量使用 ngx_chain_t 和 ngx_buf_t,反复 new/delete 是碎片高频源头。
- 别每次调 ngx_alloc_chain_link,改用 ngx_chain_get_free_buf 复用空闲节点
- 固定长度头字段(如 "HTTP/1.1 200 OK\r\n")用静态 buffer 或模块级预分配池,不 per-request 分配
- 启用 sendfile 或 directio,让数据直通 socket,跳过用户态中间 buffer
盯紧第三方模块和 Lua 脚本
很多碎片问题不出在 Nginx 核心,而出在扩展模块的“无意识越界”。
- 检查 Lua-nginx-module、headers-more 等是否调用了 asprintf、strdup 或触发 Lua GC;优先选用提供 pool-aware 接口的写法,比如用 ngx.shared.DICT 替代 Lua table 缓存
- Lua 脚本里少用 string.format、table.concat,改用预分配 buffer + string.sub 拼接
- 编译时加 -DNGX_DEBUG_MALLOC,配合 addr2line 定位非 pool 分配点
运行期验证比设计更重要
再好的设计也要看效果。RSS 是否稳定、large 链表是否膨胀、chain 复用是否失效,得靠数据说话。
- 压测中用 pmap -x PID 定期采样,观察 anon-rss 是否阶梯上涨、mmap 区域是否增多
- 查 error log 中是否有 "ngx_chain_get_free_buf: no free buf",说明复用机制已崩
- 开启 -DNGX_DEBUG_POOL 后,看日志里 "pool: 0x..., used: XXX" 的平均占用率——若普遍偏低(如常低于 1KB),说明小块分配太碎,正在快速切新块


















