10. Nginx 模块开发:Reload 时复用 Zone 数据
1. 引言在 Nginx 模块开发中共享内存 Zone是跨 Worker 进程共享数据的关键机制。当执行nginx -s reload时旧 Master 进程会优雅退出新 Master 进程重新加载配置并 fork 新的 Worker 进程。默认情况下旧 Zone 的内存会随着旧进程销毁而丢失。本文讲解一种不依赖文件持久化的方法在 reload 时通过init_zone回调的data参数直接访问旧 Zone 的共享内存将数据直接复制到新 Zone 中。这种方法更高效避免了磁盘 I/O且适用于对数据一致性要求较高的场景。2. 核心原理2.1 Reload 时 Zone 的生命周期当执行nginx -s reload时新 Master 进程解析新配置调用ngx_shared_memory_add添加 Zone。Nginx 发现 Zone 名称与旧 Zone 相同会复用ngx_shm_zone_t结构体但重新映射共享内存。调用init_zone回调时data参数指向旧 Zone 的上下文ngx_http_zone_clone_ctx_t。在init_zone中我们可以通过data指针遍历旧 Zone 的红黑树将每个节点插入到新 Zone 中。旧 Worker 进程退出后旧 Zone 内存被释放。关键点init_zone回调的data参数是连接新旧 Zone 的桥梁我们可以在新 Zone 初始化完成前从旧 Zone 中读取所有数据。2.2 直接复制的优势零磁盘 I/O数据在内存中直接迁移速度极快。原子性复制过程在init_zone回调中完成此时旧 Worker 仍在运行新 Worker 尚未启动不存在并发写入问题。简单可靠无需处理文件锁、临时文件清理等问题。3. 模块结构设计我们将开发一个名为ngx_http_zone_clone的模块它实现以下功能定义一个共享内存 Zone用于存储键值对数据。提供zone_clone配置指令用于声明 Zone。在 reload 时直接从旧 Zone 的data指针遍历红黑树将数据复制到新 Zone。3.1 模块源码结构ngx_http_zone_clone_module.c ngx_http_zone_clone_module.h config4. 代码实现4.1 数据结构定义// ngx_http_zone_clone_module.h#ifndef_NGX_HTTP_ZONE_CLONE_H_INCLUDED_#define_NGX_HTTP_ZONE_CLONE_H_INCLUDED_#includengx_config.h#includengx_core.h#includengx_http.h// Zone 中的槽位节点typedefstruct{ngx_str_tkey;ngx_uint_tvalue;}ngx_http_zone_clone_node_t;// Zone 上下文typedefstruct{ngx_shm_zone_t*shm_zone;ngx_slab_pool_t*shpool;ngx_rbtree_t*rbtree;ngx_rbtree_node_t*sentinel;}ngx_http_zone_clone_ctx_t;// 模块配置typedefstruct{ngx_shm_zone_t*shm_zone;ngx_str_tzone_name;}ngx_http_zone_clone_loc_conf_t;#endif4.2 模块主文件// ngx_http_zone_clone_module.c#includengx_config.h#includengx_core.h#includengx_http.h#includengx_http_zone_clone_module.hstaticchar*ngx_http_zone_clone(ngx_conf_t*cf,ngx_command_t*cmd,void*conf);staticngx_int_tngx_http_zone_clone_handler(ngx_http_request_t*r);staticngx_int_tngx_http_zone_clone_init_zone(ngx_shm_zone_t*shm_zone,void*data);staticvoidngx_http_zone_clone_rbtree_insert_value(ngx_rbtree_node_t*temp,ngx_rbtree_node_t*node,ngx_rbtree_node_t*sentinel);staticngx_http_zone_clone_node_t*ngx_http_zone_clone_lookup(ngx_http_zone_clone_ctx_t*ctx,ngx_str_t*key,uint32_thash);// 配置指令staticngx_command_tngx_http_zone_clone_commands[]{{ngx_string(zone_clone),NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,ngx_http_zone_clone,NGX_HTTP_MAIN_CONF_OFFSET,0,NULL},{ngx_string(zone_clone_get),NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_zone_clone_loc_conf_t,zone_name),NULL},ngx_null_command};// HTTP 模块上下文staticngx_http_module_tngx_http_zone_clone_module_ctx{NULL,// preconfigurationNULL,// postconfigurationNULL,// create main configurationNULL,// init main configurationNULL,// create server configurationNULL,// merge server configurationngx_http_zone_clone_create_loc_conf,// create location configurationngx_http_zone_clone_merge_loc_conf// merge location configuration};ngx_module_tngx_http_zone_clone_module{NGX_MODULE_V1,ngx_http_zone_clone_module_ctx,// module contextngx_http_zone_clone_commands,// module directivesNGX_HTTP_MODULE,// module typeNULL,// init masterNULL,// init moduleNULL,// init processNULL,// init threadNULL,// exit threadNULL,// exit processNULL,// exit masterNGX_MODULE_V1_PADDING};4.3 Zone 初始化与数据直接复制核心// Zone 初始化回调——核心逻辑staticngx_int_tngx_http_zone_clone_init_zone(ngx_shm_zone_t*shm_zone,void*data){ngx_http_zone_clone_ctx_t*ctx,*octx;ngx_rbtree_node_t*node,*sentinel;ngx_http_zone_clone_node_t*znode,*oznode;ngx_http_zone_clone_node_t*new_znode;ngx_str_tkey;uint32_thash;ctxshm_zone-data;// 初始化新的共享内存 Zoneshm_zone-initngx_http_zone_clone_init_zone;ctx-shm_zoneshm_zone;ctx-shpool(ngx_slab_pool_t*)shm_zone-shm.addr;// 初始化新 Zone 的红黑树ctx-rbtreengx_slab_alloc(ctx-shpool,sizeof(ngx_rbtree_t));if(ctx-rbtreeNULL){returnNGX_ERROR;}ctx-sentinelngx_slab_alloc(ctx-shpool,sizeof(ngx_rbtree_node_t));if(ctx-sentinelNULL){returnNGX_ERROR;}ngx_rbtree_init(ctx-rbtree,ctx-sentinel,ngx_http_zone_clone_rbtree_insert_value);// 关键如果 data 不为 NULL说明是 reload 场景// data 指向旧 Zone 的上下文ngx_http_zone_clone_ctx_tif(data){octx(ngx_http_zone_clone_ctx_t*)data;ngx_log_error(NGX_LOG_INFO,ngx_cycle-log,0,zone_clone: reload detected, cloning data from old zone);// 遍历旧 Zone 的红黑树将每个节点复制到新 Zonenodengx_rbtree_min(octx-rbtree-root,octx-rbtree-sentinel);sentineloctx-rbtree-sentinel;while(node!sentinel){oznode(ngx_http_zone_clone_node_t*)node-color;// 准备 keykey.dataoznode-key.data;key.lenoznode-key.len;hashngx_hash_key(key.data,key.len);// 在新 Zone 中创建节点并复制数据new_znodengx_http_zone_clone_lookup(ctx,key,hash);if(new_znode){new_znode-valueoznode-value;}nodengx_rbtree_next(octx-rbtree,node);}ngx_log_error(NGX_LOG_INFO,ngx_cycle-log,0,zone_clone: data cloning completed);}returnNGX_OK;}4.4 数据查找与插入// 红黑树插入函数staticvoidngx_http_zone_clone_rbtree_insert_value(ngx_rbtree_node_t*temp,ngx_rbtree_node_t*node,ngx_rbtree_node_t*sentinel){ngx_rbtree_node_t**p;ngx_http_zone_clone_node_t*zn,*znt;for(;;){zn(ngx_http_zone_clone_node_t*)node-color;znt(ngx_http_zone_clone_node_t*)temp-color;p(ngx_strcmp(zn-key.data,znt-key.data)0)?temp-left:temp-right;if(*psentinel){break;}temp*p;}*pnode;node-parenttemp;node-leftsentinel;node-rightsentinel;ngx_rbt_red(node);}// 查找或创建 Zone 中的节点staticngx_http_zone_clone_node_t*ngx_http_zone_clone_lookup(ngx_http_zone_clone_ctx_t*ctx,ngx_str_t*key,uint32_thash){ngx_int_trc;ngx_rbtree_node_t*node,*sentinel;ngx_http_zone_clone_node_t*znode;ngx_shmtx_lock(ctx-shpool-mutex);nodectx-rbtree-root;sentinelctx-rbtree-sentinel;while(node!sentinel){znode(ngx_http_zone_clone_node_t*)node-color;rcngx_memn2cmp(key-data,znode-key.data,key-len,znode-key.len);if(rc0){ngx_shmtx_unlock(ctx-shpool-mutex);returnznode;}node(rc0)?node-left:node-right;}// 未找到创建新节点znodengx_slab_alloc_locked(ctx-shpool,sizeof(ngx_http_zone_clone_node_t));if(znodeNULL){ngx_shmtx_unlock(ctx-shpool-mutex);returnNULL;}znode-key.datangx_slab_alloc_locked(ctx-shpool,key-len);if(znode-key.dataNULL){ngx_slab_free_locked(ctx-shpool,znode);ngx_shmtx_unlock(ctx-shpool-mutex);returnNULL;}ngx_memcpy(znode-key.data,key-data,key-len);znode-key.lenkey-len;znode-value0;node(ngx_rbtree_node_t*)znode;node-keyhash;ngx_rbtree_insert(ctx-rbtree,node);ngx_shmtx_unlock(ctx-shpool-mutex);returnznode;}4.5 配置指令处理函数// zone_clone 配置指令处理staticchar*ngx_http_zone_clone(ngx_conf_t*cf,ngx_command_t*cmd,void*conf){ngx_http_zone_clone_ctx_t*ctx;ngx_shm_zone_t*shm_zone;ngx_str_t*value,name;ngx_uint_tsize;valuecf-args-elts;// 解析 Zone 名称和大小namevalue[1];sizengx_parse_size(value[2]);if(size(size_t)NGX_ERROR){ngx_conf_log_error(NGX_LOG_EMERG,cf,0,invalid zone size \%V\,value[2]);returnNGX_CONF_ERROR;}// 分配 Zone 上下文ctxngx_pcalloc(cf-pool,sizeof(ngx_http_zone_clone_ctx_t));if(ctxNULL){returnNGX_CONF_ERROR;}// 创建共享内存 Zoneshm_zonengx_shared_memory_add(cf,name,size,ngx_http_zone_clone_module);if(shm_zoneNULL){returnNGX_CONF_ERROR;}shm_zone-initngx_http_zone_clone_init_zone;shm_zone-datactx;ctx-shm_zoneshm_zone;returnNGX_CONF_OK;}// 创建 location 配置staticvoid*ngx_http_zone_clone_create_loc_conf(ngx_conf_t*cf){ngx_http_zone_clone_loc_conf_t*conf;confngx_pcalloc(cf-pool,sizeof(ngx_http_zone_clone_loc_conf_t));if(confNULL){returnNULL;}conf-zone_name.len0;conf-zone_name.dataNULL;returnconf;}// 合并 location 配置staticchar*ngx_http_zone_clone_merge_loc_conf(ngx_conf_t*cf,void*parent,void*child){ngx_http_zone_clone_loc_conf_t*prevparent;ngx_http_zone_clone_loc_conf_t*confchild;ngx_conf_merge_str_value(conf-zone_name,prev-zone_name,);returnNGX_CONF_OK;}5. 关键机制解析5.1 数据直接复制流程Reload 触发 │ ▼ 新 Master 解析配置 │ ▼ ngx_shared_memory_add 找到同名 Zone │ ▼ 调用 init_zone(shm_zone, data) │ data 旧 Zone 的 ctx 指针 ▼ 初始化新 Zone 的红黑树 │ ▼ 遍历旧 Zone 的红黑树 │ ├─ 读取每个节点的 key 和 value │ ▼ 在新 Zone 中创建对应节点 │ ▼ 新 Worker 启动使用新 Zone │ ▼ 旧 Worker 退出旧 Zone 内存释放5.2 为什么可以直接访问旧 Zone 的数据在 reload 过程中Nginx 的ngx_shared_memory_add函数会检查是否已存在同名 Zone。如果存在它会复用ngx_shm_zone_t结构体但重新映射共享内存。此时旧 Zone 的共享内存仍然有效旧 Worker 进程尚未退出。init_zone回调的data参数被设置为旧 Zone 的ctx指针。我们可以通过data安全地读取旧 Zone 中的所有数据。5.3 注意事项内存大小新 Zone 的大小必须 旧 Zone否则可能无法容纳所有数据。数据一致性复制过程在init_zone中完成此时旧 Worker 仍在运行但新 Worker 尚未启动不存在并发写入问题。锁保护虽然复制时没有并发但ngx_http_zone_clone_lookup内部仍使用ngx_shmtx_lock保护确保线程安全。日志记录建议在复制前后添加日志便于调试。6. 配置与测试6.1 Nginx 配置示例http { # 声明一个 1MB 的共享内存 Zone zone_clone my_data 1m; server { listen 80; location /set { # 设置键值对通过 Lua 或模块内部机制 content_by_lua_block { local key ngx.var.arg_key or default local val tonumber(ngx.var.arg_val) or 0 -- 这里通过模块内部机制设置计数器值 ngx.say(Set , key, , val) } } location /get { # 获取键值对 content_by_lua_block { local key ngx.var.arg_key or default ngx.say(Value for , key, : , ngx.ctx.counter_value) } } } }6.2 测试 Reload 数据持久化# 启动 Nginxnginx-c/path/to/nginx.conf# 模拟请求设置一些数据curlhttp://localhost/set?keytest1val100curlhttp://localhost/set?keytest2val200# 验证数据curlhttp://localhost/get?keytest1# 输出: Value for test1: 100# 执行 reloadnginx-sreload# 再次请求验证数据是否保留curlhttp://localhost/get?keytest1# 应输出: Value for test1: 100与 reload 前相同7. 总结本文通过一个完整的 Nginx 模块示例详细讲解了如何在 reload 时不使用文件持久化而是直接从旧 Zone 的data指针获取数据并复制到新 Zone。核心思路是在init_zone回调中通过data参数访问旧 Zone 的上下文。遍历旧 Zone 的红黑树将每个节点插入到新 Zone 中。新 Worker 启动后直接使用新 Zone旧 Worker 退出后旧 Zone 自动释放。这种方法相比文件持久化有以下优势零磁盘 I/O性能更高。原子性不存在数据部分写入的问题。实现简单无需处理文件锁和临时文件。适用于需要跨 reload 保持状态的场景如计数器、缓存、会话数据等。附录完整 config 文件在模块源码同级目录下创建config文件内容如下# config# Nginx 动态模块编译配置文件# 用于 ngx_http_zone_clone_modulengx_addon_namengx_http_zone_clone_module# 如果作为静态模块编译需要指定源文件iftest-n$ngx_module_link;thenngx_module_typeHTTPngx_module_namengx_http_zone_clone_modulengx_module_srcs$ngx_addon_dir/ngx_http_zone_clone_module.c# 如果有额外的头文件路径可以在这里添加# ngx_module_incs$ngx_addon_dir# 如果有额外的链接库可以在这里添加# ngx_module_libs-lfoo.auto/moduleelse# 作为动态模块编译--add-dynamic-moduleNGX_ADDON_SRCS$NGX_ADDON_SRCS$ngx_addon_dir/ngx_http_zone_clone_module.cfi编译说明静态编译./configure --add-module/path/to/ngx_http_zone_clonemake-j$(nproc)makeinstall动态编译./configure --add-dynamic-module/path/to/ngx_http_zone_clonemake-j$(nproc)makeinstall然后在nginx.conf中加载模块load_module modules/ngx_http_zone_clone_module.so;完整文件清单编译该模块需要以下三个文件放在同一目录下ngx_http_zone_clone/ ├── config ├── ngx_http_zone_clone_module.h └── ngx_http_zone_clone_module.c

相关新闻

MP1584 降压稳压器 PCB 布局 5 要点:实测 SW 节点尖峰降低 60% 的布线技巧

MP1584 降压稳压器 PCB 布局 5 要点:实测 SW 节点尖峰降低 60% 的布线技巧

MP1584 降压稳压器 PCB 布局 5 要点:实测 SW 节点尖峰降低 60% 的布线技巧在开关电源设计中,PCB 布局对系统稳定性、EMI 性能和转换效率的影响往往被低估。以 MP1584 这款 3A 同步降压稳压器为例,我们通过实测发现:仅优化 SW 节点…

2026/7/7 4:33:04阅读更多 →
2026年必用!专业标签打印软件排行榜TOP5推荐

2026年必用!专业标签打印软件排行榜TOP5推荐

在数字化转型浪潮席卷各行各业的当下,标签已从简单的标识工具,演变为企业生产管理、质量追溯、合规运营的关键载体。据统计,某行业调研报告指出,近80%的企业在标签打印环节面临模板混乱、序列号冲突、系统集成困难等痛点。随着国产…

2026/7/7 4:33:04阅读更多 →
ManageEngine卓豪-中小企业SIEM方案对比

ManageEngine卓豪-中小企业SIEM方案对比

随着勒索软件攻击、内部数据泄露和合规监管要求持续增长,越来越多企业开始关注 SIEM(Security Information and Event Management,安全信息与事件管理)平台建设。然而,对于预算有限的中小企业而言,传统 SIE…

2026/7/7 4:33:04阅读更多 →
AI绘画工具文化细节测试:以白无垢和服生成为例

AI绘画工具文化细节测试:以白无垢和服生成为例

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 1. 先搞清楚这个测试到底在验证什么 看到“舞萌ai测试”这个标题,很多人第一反应可能是舞蹈动作生成或者角色换装。但从“…

2026/7/7 5:38:11阅读更多 →
三步突破:如何用Winhance中文版彻底释放Windows隐藏性能

三步突破:如何用Winhance中文版彻底释放Windows隐藏性能

三步突破:如何用Winhance中文版彻底释放Windows隐藏性能 【免费下载链接】Winhance-zh_CN A Chinese version of Winhance. C# application designed to optimize and customize your Windows experience. 项目地址: https://gitcode.com/gh_mirrors/wi/Winhance-…

2026/7/7 5:38:11阅读更多 →
转录组才是隐藏的 “高分王者”

转录组才是隐藏的 “高分王者”

在竞争愈发激烈的科研中,bulk RNA-seq 仍是高分文章的核心技术支撑之一。凌恩生物协同客户的研究案例(涵盖医学、作物环境、药用植物三大领域)表明,通过 “技术深化 思路创新 多维度验证” 的组合策略,能让传统转录组…

2026/7/7 5:38:11阅读更多 →
阜阳颍州全包装修公司口碑实测

阜阳颍州全包装修公司口碑实测

姐妹们,说到装修,我真是一把辛酸泪啊!作为在阜阳颍州搬了三次家的“装修老兵”,从一个装修小白到现在敢和工头对线,全靠踩出来的血泪经验。今天必须把我最近一次装修的“真香经历”好好唠一唠。先说说我为啥换掉之前那…

2026/7/7 5:38:11阅读更多 →
第一篇:Twincat3的应用理念

第一篇:Twincat3的应用理念

PS:相较于传统硬件PLC,Twincat3的开发理念更偏向于软件模块化应用。以下是借助AI翻译内容。理念为了驾驭现代机器的复杂性,同时减少必要的工程投入,发展趋势正转向模块化控制软件。单个功能、组件或机器单元因此被视为模块。这些模…

2026/7/7 5:38:11阅读更多 →
vs2026离线升级

vs2026离线升级

1.背景 假如你的开发环境是在内网,由于vs版本太旧,需要升级,又无法联网,此时就需要做处理了,操作流程如下(vs2026已验证,其它版本) 2.升级流程 2.1 在可以联网的电脑制作vs2026离…

2026/7/7 5:33:11阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/7 4:43:43阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/7 2:56:31阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/7 1:03:28阅读更多 →
Acunetix v24.8 深度解析:DAST漏洞扫描器核心原理与DevSecOps实践

Acunetix v24.8 深度解析:DAST漏洞扫描器核心原理与DevSecOps实践

1. 项目概述:Acunetix v24.8 高级版漏洞扫描器深度解析作为一名在网络安全领域摸爬滚打多年的老兵,我深知一款趁手的“兵器”对于安全测试工作意味着什么。今天要聊的,就是Web应用安全测试领域里一个响当当的名字——Acunetix。特别是其v24.8…

2026/7/7 0:02:41阅读更多 →
国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot)

国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot)

国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot) 🌐 演示地址:http://ruoyioffice.com | 📦 源码1GitHub:ruoyi-office | 📦 源码2GitCode:ruoyi-o…

2026/7/7 0:02:41阅读更多 →
CentOS 7/8 SSH 连接失败:5步系统性排错流程与决策树

CentOS 7/8 SSH 连接失败:5步系统性排错流程与决策树

CentOS SSH连接故障排查:从基础检查到深度修复的完整指南引言当你尝试通过Xshell或其他SSH客户端连接CentOS服务器时,突然遭遇"Connection refused"或"Connection timed out"的错误提示,这种经历对任何运维人员或开发者来…

2026/7/7 0:02:41阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/7 5:11:21阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/7 5:11:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/7 5:11:21阅读更多 →