Nginx 服务自动发现与配置热更新架构
Nginx 服务自动发现与配置热更新架构
Nginx 采用 master-worker 多进程模型,其中:
- Master 进程:负责配置解析、进程管理和信号交互
- Worker 进程:执行网络 I/O、定时器和延迟队列的事件循环
本文深入分析 Nginx 如何与 ZooKeeper 集成实现服务自动发现和配置热更新,以及如何通过引入 Manage 进程优化架构设计。
1、Nginx 多进程模型原理
1.1、配置解析与端口初始化
Master 进程:解析配置与打开监听端口
sequenceDiagram
participant Master as Master 进程
participant Config as nginx.conf
participant Socket as 监听 Socket
Master->>Config: 1. ngx_init_cycle()
Master->>Config: 2. ngx_conf_parse() 逐行解析
Master->>Master: 3. 生成 cycle->conf_ctx
Master->>Master: 4. 构建 cycle->listening 数组
Master->>Socket: 5. ngx_open_listening_sockets()
Note over Socket: socket() → bind() → listen()
Master->>Master: 6. 设置 reuseport/backlog
详细流程:
配置解析:在
main()中,master 进程首先调用ngx_init_cycle(),该函数基于ngx_conf_parse()逐行读取并解析nginx.conf,生成模块级的配置上下文(cycle->conf_ctx)构建 listening 数组:解析阶段,core 模块会为每个
listen指令创建一个ngx_listening_t结构,并将其添加到cycle->listening数组中打开套接字:随后,master 调用
ngx_open_listening_sockets(),遍历cycle->listening:- 为每个条目创建 socket(
socket()) - 绑定地址(
bind()) - 监听(
listen()) - 设置常用选项(如
reuseport、backlog)
- 为每个条目创建 socket(
继承老进程 socket:若为平滑重启,通过
ngx_add_inherited_sockets()从环境变量获取老进程的 socket 描述符,避免中断服务
Worker 进程:继承与初始化
sequenceDiagram
participant Master as Master 进程
participant Worker as Worker 进程
participant Socket as 监听 Socket
Master->>Worker: 1. fork() 创建子进程
Master->>Worker: 2. 继承所有监听 Socket
Worker->>Worker: 3. ngx_worker_process_init()
Note over Worker: 初始化日志、事件模块、定时器
Worker->>Socket: 4. 注册事件回调(无需 bind/listen)
详细流程:
Fork 产生:master 通过
ngx_start_worker_processes()调用ngx_spawn_process()fork 出多个 worker,继承 master 打开的所有监听 socket初始化调用:每个 worker 在
ngx_worker_process_cycle()开始时调用ngx_worker_process_init()完成:- 设置进程类型标识
- 设置进程标题
- 初始化日志、事件模块、定时器与延迟队列
- 为业务端口注册可读/可写事件回调
不再 bind/listen:worker 直接使用继承自 master 的 socket,**无需再次调用
bind()或listen()**,确保端口监听唯一由 master 或平滑重启过程中创建一次
1.2、Worker 进程事件循环
Worker 进程只处理 3 种事件:
| 事件类型 | 说明 |
|---|---|
| 网络 I/O | 读写事件、新连接(accept) |
| 定时器 | 红黑树管理的定时器事件 |
| 延迟队列 | 延迟处理的事件(posted events) |
1 | |
核心处理函数:ngx_process_events_and_timers
1 | |
事件循环流程图
flowchart TD
Start[循环开始] --> CheckLock{是否启用 accept_mutex?}
CheckLock -->|是| TryLock[尝试获取 accept_mutex]
TryLock -->|成功| AddSocket[将监听 socket 加入 epoll]
TryLock -->|失败| ProcessEvents[ngx_process_events]
CheckLock -->|否| ProcessEvents
ProcessEvents[ngx_process_events<br/>epoll_wait 等待事件] --> HandleIO{事件类型}
HandleIO -->|已有连接读写| DirectHandler[直接调用 handler<br/>如 ngx_http_process_request]
HandleIO -->|新连接 accept| PostAccept[post 到 ngx_posted_accept_events]
PostAccept --> ProcessAccept[ngx_event_process_posted<br/>执行 accept]
DirectHandler --> ProcessAccept
AddSocket --> ProcessEvents
ProcessAccept --> ReleaseLock[释放 accept_mutex]
ReleaseLock --> ExpireTimers[ngx_event_expire_timers<br/>处理超时定时器]
ExpireTimers --> PostEvents[超时事件 post 到 ngx_posted_events]
PostEvents --> ProcessPosted[ngx_event_process_posted<br/>执行普通延迟事件]
ProcessPosted --> Start
关键步骤说明:
**
ngx_process_events()**:调用epoll_wait等待事件- 已有连接的读写事件:直接调用 handler(如
ngx_http_process_request) - 新连接事件(accept):post 到
ngx_posted_accept_events
- 已有连接的读写事件:直接调用 handler(如
**
ngx_event_process_posted(&ngx_posted_accept_events)**:执行 accept 操作释放锁:如果持有
accept_mutex,此时释放**
ngx_event_expire_timers()**:处理红黑树定时器,超时事件 post 到ngx_posted_events**
ngx_event_process_posted(&ngx_posted_events)**:执行普通延迟事件
2、与 ZooKeeper 的交互架构
2.0、问题背景
为了支持与 ZooKeeper 的动态配置交互,早期方案采用单 Worker 直连 ZooKeeper 进行配置变更,多进程通过共享内存同步配置。但这种方式会阻塞该 worker 处理其他事件,影响服务可用性与性能。
2.1、原始模式架构
架构设计
(1)单 Worker 负责注册 Watcher 与更新配置
- 所有 Worker 中仅有一个被选定(编号为 0)连接 ZooKeeper
- 该 Worker 注册 Watcher,并监听配置中心的变更事件
- 当监听到变更后,直接将最新配置写入本地文件,并通过共享内存设置一个”配置更新标志位”(实际为版本号)
(2)其他 Worker 通过轮询方式感知并应用配置
- 其他 Worker 启动定时器,定期比对本地进程内存和共享内存中的版本号是否一致
- 一旦发现不一致,抢锁,然后读取配置文件,加载新配置并完成本地内存更新
- 更新完成后,版本号修改为一致,释放锁
原始模式架构图
graph TB
subgraph Nginx 进程
Worker0[Worker 0<br/>连接 ZooKeeper]
Worker1[Worker 1]
Worker2[Worker 2]
WorkerN[Worker N]
end
ZK[ZooKeeper<br/>配置中心]
Shm[共享内存<br/>版本号标志位]
File[本地配置文件]
Worker0 -->|注册 Watcher| ZK
ZK -->|配置变更通知| Worker0
Worker0 -->|写入配置| File
Worker0 -->|设置版本号| Shm
Worker1 -->|定时轮询| Shm
Worker2 -->|定时轮询| Shm
WorkerN -->|定时轮询| Shm
Worker1 -->|读取配置| File
Worker2 -->|读取配置| File
WorkerN -->|读取配置| File
style Worker0 fill:#ffcccc
style Shm fill:#fff4e1
style File fill:#e1f5ff
存在的问题
| 问题类型 | 具体表现 |
|---|---|
| 性能问题 | Worker 0 需要处理 ZooKeeper 连接、监听、配置写入等操作,阻塞事件循环,延迟请求响应,高并发场景下性能下降明显 |
| 维护成本 | 每个 Worker 都需要包含 ZooKeeper 连接管理、会话重连、Watcher 重订阅、异常恢复等完整流程,增加维护成本与出错风险 |
| 更新效率 | 配置变更后,Worker 0 写入文件,其他 Worker 轮询后读取文件,需要加锁导致串行更新,且文件 I/O 效率低下 |
2.2、新模式设计:引入 Manage 进程
架构图
graph TB
Master[Master进程<br/>配置解析与端口初始化]
Manage[Manage进程<br/>编号0<br/>配置更新专用]
Worker1[Worker进程<br/>编号1]
Worker2[Worker进程<br/>编号2]
WorkerN[Worker进程<br/>编号N]
ZK[ZooKeeper<br/>配置中心]
Shm[共享内存<br/>配置数据与版本号]
Client[客户端<br/>业务请求]
Admin[管理端<br/>热更新请求]
Master -->|fork| Manage
Master -->|fork| Worker1
Master -->|fork| Worker2
Master -->|fork| WorkerN
Manage -->|监听管理端口| Admin
Manage -->|注册Watcher| ZK
Manage -->|读取配置| ZK
Manage -->|写入配置| ZK
Manage -->|更新配置| Shm
Worker1 -->|监听业务端口| Client
Worker2 -->|监听业务端口| Client
WorkerN -->|监听业务端口| Client
Worker1 -->|读取配置| Shm
Worker2 -->|读取配置| Shm
WorkerN -->|读取配置| Shm
style Manage fill:#e1f5ff
style Shm fill:#fff4e1
style ZK fill:#e8f5e9
优化点总结
| 优化项 | 说明 |
|---|---|
| 职责分离 | 将与 ZooKeeper 交互、动态配置管理等耗时操作与网络 I/O 分离,减轻 worker 负担,简化逻辑 |
| 避免阻塞 | 避免单个 worker 因配置更新而阻塞服务,有助于提高整体吞吐与可用性 |
| 内存更新 | 配置更新后存入共享内存,worker 从内存读取数据,而不是读文件,提高更新效率 |
| 并行更新 | 使用读写锁,worker 可以进行并行更新,提升性能 |
设计方案
2.1、主体目标
- 新增 Manage 进程:只处理配置更新,包括:
- 注册中心(ZooKeeper)的配置更新
- 热更新接口
- Redis 哨兵、RabbitMQ 的订阅消息
- 不处理实际的客户端请求
- Worker 专注业务:专注于处理客户端请求-响应
2.2、进程定位与初始化
进程创建策略:
- 如果不是
single模式,额外创建一个进程,修改进程名称为manage - 解析
"worker_processes"时,如果为auto或数量 > 1,则额外创建一个编号为 0 的 manage 进程 - 实际
"worker_processes"数量加 1
端口监听策略:
| 端口类型 | Manage 进程 | Worker 进程 | 说明 |
|---|---|---|---|
| 业务端口 | 关闭 | 监听 | 处理客户端请求-响应 |
| 管理端口 | 监听 | 关闭 | 接收热更新 API 调用 |
初始化要求:
- Manage 进程不监听业务端口,初始化时关闭业务端口
- Manage 进程不启用 accept 锁(因为只监听管理端口,无竞争关系)
2.3、功能层面的设计
Manage 进程职责:
- 向 ZooKeeper 注册 Watcher
- 创建 5s 定时器,扫描与 ZooKeeper 的数据交互结构体是否变更
- 如果有变更,说明有 ZooKeeper 的配置更新推送,开始获取数据更新到:
- 本地内存
- 共享内存
- 本地文件
Worker 进程职责:
- 创建 2s 定时器,扫描共享内存配置主版本号
- 针对功能的不同,细化为功能版本号:
- 路由版本号
- 流控版本号
- 访问控制版本号
- 主版本号发生变化时,获取更细致的功能配置变更,进行增量更新,抛弃原有的全量更新模式
Lua 层面处理:
- 由 Manage 进程处理 Redis 哨兵、RabbitMQ 的推送消息
- 处理节点变更以及实际的业务数据变更
2.4、竞争关系与锁机制
问题分析:
很明显会发生竞争关系,比如 worker 正在读取共享内存的新配置时,如果 manage 再次更新,那就会发生异常,需要上锁。
锁的设计要求:
- Manage 更新配置时,禁止 worker 读取
- Worker 更新配置时,需要先获取锁
- 理想场景:Manage 更新时,worker 禁止更新,但所有 worker 可以同时读取
解决方案:读写锁
使用读写锁 ngx_rwlock_rlock:
| 操作类型 | 锁类型 | 说明 |
|---|---|---|
| Manage | 写锁 | 更新配置时获取写锁 |
| Worker | 读锁 | 读取配置时获取读锁 |
| 特性 | - | 写时不可读,读时不可写,但可以一起读 |
优势:Worker 可以并行更新配置,提升性能。
热更新处理:
- Manage 还处理热更新,这是调用式触发(主动模式)
- 原理一致:接收客户端的数据,更新本地内存、共享内存、本地文件,然后修改共享内存的标识
- 对于热更新,还需要更新 ZooKeeper 的配置,满足集群多节点的配置同步更新
2.5、异常场景处理
问题:Watcher 连续推送
场景描述:
- Manage 进程更新完配置后,会将标志位恢复,然后重新创建定时器进行扫描标志位
- 如果 Manage 进程正在更新配置(更新部分),此时又有 Watcher 消息推送
- Manage 接着更新完配置恢复了标志位,就会丢失这期间所有的变更推送
问题分析:
- 加锁解决不了这种竞争关系(因为 Watcher 是异步推送的)
- 需要额外的机制来标识”更新期间再次发生更新”
解决方案:updating 标志位
新增 updating 标志位,标识更新期间再次发生更新:
- 配置更新完毕后,如果
updating为 1,直接恢复标志位 - 如果
updating为 0,说明更新期间发生了新的推送,不恢复更新标志位 - 等待下一个定时器再次触发,继续更新
2.6、方案实现
核心原则:所有配置统一在 master 解析完成后共享,子进程(worker/manage)在 init 阶段根据端口属性决定是否关闭 socket,并根据是否为 manage 调整是否参与 accept 竞争。
2.6.1、进程结构调整
- 如果不是
single模式,且"worker_processes"为auto或 > 1:- 实际创建
N + 1个进程:manage+N个worker manage进程编号固定为0,进程名修改为"manage process"
- 实际创建
2.6.2、端口绑定策略
- 所有进程都共享
master解析好的配置,监听 socket 初始化在 master 完成 - 每个进程初始化时,根据端口的
server_name.manage_server字段来判断要不要关闭该 fd - 每个进程都只会将应该处理的端口,加入自己的事件循环(抢到锁的 worker)并执行
accept()
| 端口 | Manage 进程 | Worker 进程 |
|---|---|---|
manage_server=1(管理端口) |
保留 | 关闭 |
manage_server=0(业务端口) |
关闭 | 保留 |
2.6.3、accept_mutex 设置
- 初始化阶段,若当前是
manage进程:- 设置
ngx_use_accept_mutex = 0,不参与锁竞争(Manage 进程监听自己的端口,没有竞争关系)
- 设置
2.6.4、配置解析阶段
server块中增加一个布尔字段manage_serverngx_http_core_srv_conf_t等结构体中加入该字段- Master 解析配置时不做分支判断,所有进程继承同一份配置
2.7、分布式锁机制
锁的必要性
- Manage 与 Worker 分别会读写配置文件,需互斥防止并发导致更新的数据不一致
- ZooKeeper 模块触发更新时,需等待上一次更新完成:采用
updating标志避免连续变更冲突
2.8、配置更新期间的问题分析
2.8.1、Worker 更新配置的特点
- 当某个 worker 进程执行配置更新,其 for 循环在定时器阶段进行,不会抢到 accept 锁,新连接自动落到其他空闲 worker 上,保证无中断接入
- 不同 worker 因版本更新不一致,可能出现:
- 已更新的 worker:请求正常
- 正在更新的 worker:请求略有延迟
- 未更新的 worker:因服务节点变更导致请求异常
2.8.2、Worker 更新配置时的阻塞问题
按照 worker 的工作模式,依次处理:
- 网络 I/O(如果拿到锁还会处理新连接)
- 定时器
- 延迟队列
此时处于处理定时器阶段,如果要更新 2s,那 2s 内其实处理不了其他的事件。
2.8.3、多进程场景下的业务处理
需要看每个 worker 处在 for 循环的哪个阶段,只要 worker 触发定时器的配置更新后,就无法处理其他事件,但是不同的 worker,处的阶段可能不一样。
同一时间,不同 worker 的状态:
- 有 worker 抢到了 accept 锁,处理新连接
- 有的 worker 处理自身的读写事件
- 有的 worker 触发了定时器的事件
- 有的 worker 处理延迟队列的新连接数据
结论:只要不是所有 worker 同时更新配置,Nginx 就能正常处理业务。
2.8.4、连续推送问题的解决方案
问题本质:
- Watcher 可以连续推送消息
- ZooKeeper 的 C 语言 SDK 会创建一个线程,导致 manage 进程和这个线程存在竞争关系
问题场景:
sequenceDiagram
participant Timer as Manage 定时器
participant Watcher as ZooKeeper Watcher 线程
participant ZK as ZooKeeper
participant Config as 配置数据
Timer->>ZK: 1. 检测标志位变更,获取数据(版本100,路由配置)
Watcher->>ZK: 2. 推送变更(版本101,流控配置)
Watcher->>Config: 3. 更改标志位(可能多次推送)
Timer->>ZK: 4. 再次获取数据(版本101,流控配置)
Timer->>Config: 5. 更新配置,恢复标志位
Note over Timer: 问题:路由配置是旧的,流控配置是新的!
问题分析:
- Manage 定时器检测标志位变更,开始处理本次变更,获取 ZooKeeper 的数据,更新配置(假设是配置的第 100 个版本,这里更新了路由)
- Watcher 又推送了变更,更改了标志位(这里有可能发生多次的推送)
- Manage 多次获取 ZooKeeper 数据,更新配置(有可能是 101 版本,或者更大,这里更新了流控)
- Manage 处理完毕本次变更,恢复标志位,然后创建定时器
- 定时器继续扫描标志位,发现没有变化,等待下一次定时器
核心问题:ZooKeeper 的数据是快照数据,步骤 1、3 获取的数据很可能是不同版本,因为一直在变更。第四步处理完毕后,虽然配置变更了很多次,但是 manage 只更新了一次,路由的配置是旧的,但是流控的是新的!
解决方案:updating 标志位
一般这种竞争关系,第一反应是加锁,但是这怎么加锁?Manage 更新的时候,Watcher 别更新?ZooKeeper 貌似没有这种机制,那只能是标志位了。
具体逻辑:
- Manage 定时器检测标志位变更,开始处理本次变更,首先将
updating设置为 1,标识正在更新(假设是配置的第 100 个版本,这里更新了路由) - Watcher 又推送了变更,更改了标志位(这里有可能发生多次的推送),同时将
updating设置为 0 - Manage 多次获取 ZooKeeper 数据,更新配置(有可能是 101 版本,或者更大,这里更新了流控)
- Manage 处理完毕本次变更:
- 如果
updating为 1:直接恢复标志位(说明更新期间没有新的推送) - 否则,不恢复标志位(
updating为 0 说明此时发生了多次推送) - 然后创建定时器
- 如果
- 定时器继续扫描标志位,发现标志位依旧需要更新,开始更新
3、Nginx 关于 Accept 锁的深入分析
3.1、是否存在”空白期”?
问题提出:
如果说只有抢到锁的进程,才能将监听 socket 放到自己的 epoll,从而处理新连接,那么当前 worker 释放锁之后,从 epoll 删除这个 socket,在下一次多个 worker 抢锁,并添加到 epoll 的时间差中,是否存在空白期,即没有一个 worker 将这个 socket 放到 epoll 里面?这期间的新连接会怎么样,会丢失吗?
分析角度:
- 内核如何处理 socket
- Nginx 如何处理 accept
结论:事实上完全不会
- 对于内核:即使在两次操作之间理论上会有时间差,内核不会丢失连接,因为未被 accept 的新连接一直保留在 socket 的 backlog 队列中,且 epoll 的事件会在重新启用时一次性报告所有待处理的连接(包括中间到达的)
- 对于 Worker:释放锁之后,不会立即从 epoll 删除这个 socket,所以不会存在空白期
总结:内核不会丢失连接(全连接队列未满的情况下),更何况,Nginx 通过机制,不会存在空白期,保持高性能处理新连接-accept。
3.2、内核的处理机制
3.2.1、Backlog 队列保证连接不丢失
- TCP 三次握手完成后的连接,会先进入内核的全连接队列(也称 backlog 队列,
accept()队列) - 用户态程序即使暂时没有调用
accept(),这些连接也会在内核中排队等待,不会立即丢失 - 只有在全连接队列已满的情况下,新来的连接才会被丢弃或收到 RST(具体行为取决于内核配置和协议栈实现)
3.2.2、Epoll 的事件语义
- 在水平触发(Level-triggered,默认)模式下,只要 backlog 队列中还有未处理的连接,
epoll_wait()就会持续报告事件 - 如果禁用了监听 fd,再重新启用,内核会一次性报告所有积压的连接事件,不会遗漏
- 在使用
EPOLLONESHOT、EPOLL_EXCLUSIVE等选项时,虽然唤醒机制不同,但当监听重新被 arm(重新添加或修改到 epoll)时,中间产生的事件仍然会被正确返回
3.2.3、半连接队列与全连接队列
| 队列类型 | 说明 | 大小控制 |
|---|---|---|
| 半连接队列 | 保存已收到客户端 SYN,但还未完成三次握手的请求 | tcp_max_syn_backlog |
| 全连接队列 | 保存已完成三次握手、等待用户态调用 accept() 的连接 |
listen(fd, backlog) + somaxconn |
队列满时的行为:
- 半连接队列满:新的 SYN 包可能会被丢弃,导致客户端需要重传
- 全连接队列满:新连接会被拒绝
结论:从内核的角度看,如果全连接队列不满时,不会出现连接丢失的情况。
3.3、Nginx 的处理机制
在 Nginx 的多 worker 模型下,只有拿到 accept_mutex 的 worker 才真正监听并处理新连接。没有拿到锁的 worker,即便手里还有监听 socket,也会在下一轮事件循环中将其从 epoll 删除,避免竞争。
3.3.1、示例:两轮事件循环
第 1 轮事件循环
| Worker | 操作步骤 |
|---|---|
| Worker 1 | 1. 获取 accept_mutex 成功2. 调用 ngx_enable_accept_events(),把监听 socket 加入自己的 epoll3. 设置 ngx_accept_mutex_held = 14. 进入 epoll_wait() 等待事件5. 收到新连接事件,执行 accept(),建立连接6. 处理完后,释放锁 ngx_shmtx_unlock()7. 释放锁时并没有立刻 epoll_ctl(DEL),监听 socket 依然留在 epoll 中 |
| Worker 2 | 1. 尝试获取锁失败 2. 因为没有锁,不会把监听 socket 添加到自己的 epoll 3. 直接进入 epoll_wait()4. 未收到连接事件,继续等待 |
第 2 轮事件循环
| Worker | 操作步骤 |
|---|---|
| Worker 2 | 1. 获取 accept_mutex 成功2. 调用 ngx_enable_accept_events(),把监听 socket 加入 epoll3. 设置 ngx_accept_mutex_held = 14. 进入 epoll_wait(),收到连接事件并 accept()5. 处理完连接后释放锁,但 socket 依然留在 epoll 中 |
| Worker 1 | 1. 尝试获取锁失败 2. 检测到自己上一轮曾经持有过锁( ngx_accept_mutex_held == 1)3. 调用 ngx_disable_accept_events(),把监听 socket 从 epoll 删除4. 将 ngx_accept_mutex_held 标记为 05. 进入 epoll_wait(),未收到新连接事件 |
3.3.2、为什么要”延迟删除”?
Nginx 并不是在释放锁的瞬间就删除监听 socket,而是等到下一轮事件循环确认自己没拿到锁时才删除。这样做有几个好处:
| 好处 | 说明 |
|---|---|
| 避免抖动 | 如果锁一释放就 DEL,下一轮又可能 ADD,系统调用频繁、开销大。延迟删除 → 只有真的”锁换人”了,才去删 |
| 逻辑简洁 | 代码路径简单:拿到锁就 enable,没拿到锁但之前拿过就 disable |
| 减少竞态 | 锁的释放和 epoll 修改解耦,减少竞态 |
关于”空白期”的说明:
实际上,Nginx 的延迟删除机制并不是为了防止”空白期”。内核的 backlog 队列会缓存未 accept 的连接,即使短暂没有 worker 监听,连接也不会丢失(除非 backlog 队列已满)。延迟删除主要是为了避免频繁的 ADD/DEL 操作,减少系统调用开销,让锁的交接更平滑。
1 | |
上面的代码执行完毕,每个进程才会执行 epoll_wait,那自然不会有问题了。
总结
本文深入分析了 Nginx 服务自动发现与配置热更新的架构设计:
核心要点
- Master-Worker 模型:Master 负责配置解析和端口初始化,Worker 继承 socket 并处理业务请求
- Manage 进程优化:引入独立的 Manage 进程处理配置更新,避免阻塞 Worker 进程
- 共享内存同步:通过共享内存和版本号机制实现多进程配置同步
- 读写锁机制:使用读写锁实现 Worker 并行读取配置,提升性能
- Accept 锁机制:通过延迟删除机制避免频繁的 ADD/DEL 操作,保证连接不丢失
架构优势
| 特性 | 原始模式 | 新模式(Manage 进程) |
|---|---|---|
| 性能 | Worker 阻塞 | Worker 专注业务 |
| 维护成本 | 高(逻辑耦合) | 低(职责分离) |
| 更新效率 | 文件 I/O,串行 | 内存读取,并行 |
| 可用性 | 配置更新时下降 | 配置更新时保持 |
最佳实践
- 使用 Manage 进程处理所有配置更新相关操作
- 通过共享内存实现高效配置同步
- 使用读写锁支持 Worker 并行读取
- 采用增量更新模式,提升更新效率