139 lines
6.8 KiB
Markdown
139 lines
6.8 KiB
Markdown
|
|
---
|
|||
|
|
tags: []
|
|||
|
|
date created: 星期五, 十一月 21日 2025, 3:40:21 下午
|
|||
|
|
date modified: 星期一, 十一月 24日 2025, 4:32:10 下午
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 2.3.6 两阶段配置热更新协议 (Two-Phase Configuration Hot-Reload Protocol)
|
|||
|
|
|
|||
|
|
遵循三阶段模型,我们深入探讨 **2.3.6 两阶段配置热更新协议 (Two-Phase Configuration Hot-Reload Protocol)**。
|
|||
|
|
|
|||
|
|
这是控制平面的“精细手术”机制。在雷达系统运行过程中,动态调整参数(如 CFAR 阈值、波束扫描范围)是一项高风险操作。如果配置变更导致某些模块崩溃,或者不同模块对同一参数的理解不一致,系统就会陷入“脑裂”或瘫痪。
|
|||
|
|
|
|||
|
|
因此,我们必须引入类似数据库事务的 **两阶段提交 (2PC)** 机制,确保配置更新要么**全员成功**,要么**全员回滚**。
|
|||
|
|
|
|||
|
|
## 一、 约束输入与对齐 (Constraints & Alignment)
|
|||
|
|
|
|||
|
|
基于系统的高可靠性要求,我们需对齐以下硬性约束:
|
|||
|
|
|
|||
|
|
1. **事务原子性 (Atomicity)**:配置变更必须是一个原子操作。严禁出现“模块 A 用了新参数,模块 B 还在用旧参数”的混合状态。
|
|||
|
|
2. **非阻塞验证 (Non-blocking Validation)**:验证阶段不能阻塞业务数据流。模块应在后台检查新配置的合法性(如内存是否足够、参数是否越界)。
|
|||
|
|
3. **无锁切换 (Lock-free Switch)**:配置生效的瞬间(Commit)必须极其迅速,不能让读取配置的业务线程(如 `SignalProcessor` 的 Worker)发生锁竞争。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
|||
|
|
|
|||
|
|
### 议题 1:更新协议模型 (Update Protocol)
|
|||
|
|
|
|||
|
|
|**选项**|**A. 单阶段通知 (Fire-and-Forget)**|**B. 两阶段提交 (2PC / Prepare-Commit) (推荐)**|
|
|||
|
|
|---|---|---|
|
|||
|
|
|**机制**|`ConfigManager` 直接发布 `ConfigChangedEvent`。模块收到即生效。|**Phase 1**: `ValidateRequest` -> 收集投票。<br><br> <br><br>**Phase 2**: 全员通过 -> `CommitCommand`;否则 -> `Abort`。|
|
|||
|
|
|**风险**|**高**。如果新配置导致模块 A 崩溃,或者模块 B 拒绝接受(如参数越界),系统将处于部分更新的**不一致状态**,且无法自动回滚。|**低**。所有模块都有机会在生效前“一票否决”。确保了变更的安全性。|
|
|||
|
|
|**延迟**|低。|高(两轮 RTT)。但在控制面(秒级交互)完全可接受。|
|
|||
|
|
|**适用性**|日志级别调整等无关痛痒的配置。|**核心业务参数调整的标准解**。|
|
|||
|
|
|
|||
|
|
### 议题 2:配置数据访问模型 (Access Model)
|
|||
|
|
|
|||
|
|
|**选项**|**A. 互斥锁保护 (Mutex Lock)**|**B. 双缓冲/原子指针 (Double Buffering / RCU) (推荐)**|
|
|||
|
|
|---|---|---|
|
|||
|
|
|**机制**|业务线程每次读配置前加锁:`lock(); val = config.val; unlock();`|业务线程持有 `shared_ptr<Config>`。更新时,`ConfigManager` 原子替换全局指针指向新对象。|
|
|||
|
|
|**性能**|**差**。高频读取(如每秒 10k 数据包)会导致严重的锁竞争,甚至阻塞更新线程。|**极佳**。读取无锁(仅增加引用计数或直接解引用),写入原子替换。|
|
|||
|
|
|**一致性**|强一致。|**最终一致**。旧线程继续用旧配置跑完当前帧,新线程用新配置。天然隔离了事务。|
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、 基线确立与实施规范
|
|||
|
|
|
|||
|
|
为了保障运行时变更的绝对安全,我们确立 **B. 两阶段提交** + **B. 原子指针 (RCU 风格)** 为基线。
|
|||
|
|
|
|||
|
|
### 1. 交互协议时序基线 (Sequence)
|
|||
|
|
|
|||
|
|
定义三个核心事件:
|
|||
|
|
|
|||
|
|
- `ValidateConfigEvent { ConfigID id; ConfigPatch patch; TraceID trace_id; }`
|
|||
|
|
- `ConfigValidationResultEvent { ConfigID id; string module; bool success; string reason; }`
|
|||
|
|
- `CommitConfigEvent { ConfigID id; }`
|
|||
|
|
|
|||
|
|
**时序流程**:
|
|||
|
|
|
|||
|
|
1. **发起 (Prepare)**:`ConfigManager` 收到 API 网关的变更请求,发布 `ValidateConfigEvent`。
|
|||
|
|
2. **投票 (Vote)**:
|
|||
|
|
|
|||
|
|
- 各模块(`SignalProcessor`, `DataReceiver`)在后台校验新参数(例如:检查新 `threshold` 是否 > 0,检查显存是否够用)。
|
|||
|
|
- 模块发布 `ConfigValidationResultEvent` 反馈结果(Yes/No)。
|
|||
|
|
|
|||
|
|
3. **决策 (Decide)**:
|
|||
|
|
|
|||
|
|
- `ConfigManager` 收集回执。设定超时时间(如 1s)。
|
|||
|
|
- **若全票通过**:发布 `CommitConfigEvent`。
|
|||
|
|
- **若有反对或超时**:记录错误日志,向 API 网关返回失败,流程终止(不发布 Commit,系统保持原状)。
|
|||
|
|
|
|||
|
|
4. **提交 (Commit)**:
|
|||
|
|
|
|||
|
|
- 模块收到 `CommitConfigEvent`,执行原子指针替换 (`std::atomic_store`),新配置即刻生效。
|
|||
|
|
|
|||
|
|
### 2. 模块侧实现规范 (C++14 RCU Pattern)
|
|||
|
|
|
|||
|
|
这是实现“无锁热更新”的关键代码范式。
|
|||
|
|
|
|||
|
|
- **配置持有**:
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 模块内部
|
|||
|
|
std::shared_ptr<const ModuleConfig> current_config_;
|
|||
|
|
std::mutex config_mutex_; // 仅用于保护指针的替换操作,不保护读取
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **业务读取 (Hot Path)**:
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
void processData() {
|
|||
|
|
// 1. 获取当前配置的快照 (引用计数+1)
|
|||
|
|
// 在这一帧处理期间,即使外部更新了,config_ptr 指向的内容也不会变,保证了单帧内的逻辑一致性
|
|||
|
|
std::shared_ptr<const ModuleConfig> config_ptr = std::atomic_load(¤t_config_);
|
|||
|
|
|
|||
|
|
// 2. 使用配置
|
|||
|
|
float threshold = config_ptr->cfar_threshold;
|
|||
|
|
// …
|
|||
|
|
} // 3. config_ptr 析构,旧配置引用计数-1。如果归零,自动释放内存。
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **更新提交 (Cold Path)**:
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
void onCommitConfig(const CommitConfigEvent& event) {
|
|||
|
|
// 1. 构建新配置对象
|
|||
|
|
auto new_config = std::make_shared<ModuleConfig>(*current_config_);
|
|||
|
|
new_config->applyPatch(event.patch);
|
|||
|
|
|
|||
|
|
// 2. 原子替换
|
|||
|
|
std::atomic_store(¤t_config_, new_config);
|
|||
|
|
|
|||
|
|
// 3. 完成。旧配置对象会随着所有持有它的业务线程结束而被动释放。
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 异常处理基线
|
|||
|
|
|
|||
|
|
- **超时机制**:`ConfigManager` 在发起 `Validate` 后必须启动定时器。如果超时未收到所有模块的回执,视为**验证失败**。
|
|||
|
|
- **版本对齐**:事件中必须携带 `ConfigID`(版本号或哈希)。模块在 Commit 时必须校验 ID 是否匹配,防止乱序到达的旧指令覆盖新指令。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 总结与下一步行动
|
|||
|
|
|
|||
|
|
我们确立了 **2.3.6 两阶段配置热更新** 的基线:
|
|||
|
|
|
|||
|
|
1. **协议**:**投票 - 提交 (2PC)**,拒绝部分更新。
|
|||
|
|
2. **实现**:**RCU (Read-Copy-Update)** 模式,读侧无锁,写侧原子替换。
|
|||
|
|
3. **安全**:**超时自动回滚**。
|
|||
|
|
|
|||
|
|
下一步建议:
|
|||
|
|
|
|||
|
|
至此,控制指令的通道全部打通。最后,我们需要关注“只读”的数据流 —— 2.3.7 性能指标遥测通道。
|
|||
|
|
|
|||
|
|
业务模块如何高频地(如每秒 100 次)上报性能指标(FPS、吞吐量),而完全不影响主业务线程的性能?
|
|||
|
|
|
|||
|
|
**提问**:您是否确认 **“2PC + RCU 无锁替换”** 的热更新基线?确认后我们将完成 2.3 节的最后一个议题 2.3.7。
|