创建仓库
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期四, 十一月 20日 2025, 11:58:41 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 11:59:25 晚上
|
||||
---
|
||||
|
||||
# 2.3.1 事件总线架构与路由机制 (Event Bus Architecture & Routing Mechanism)
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于您提供的设计文档(特别是 `05_任务调度器设计.md`)和系统环境,我们面临以下**硬性约束**:
|
||||
|
||||
1. **进程内通信 (In-Process)**:本节讨论的是同一个进程(`main_app`)内部,不同 C++ 对象(模块)之间的交互。**严禁**引入 Socket、Pipe 或由 OS 调度的 IPC 机制(如 DBus/ZMQ),以避免微秒级的系统调用开销。
|
||||
2. **语言标准**:必须兼容 **C++14** (GCC 7.3)。
|
||||
3. **实时性要求**:控制指令(如 `StopModule`)必须在 **\< 1ms** 内到达目标模块。
|
||||
4. **全链路追踪**:事件总线是 `TraceID` 传递的关键载体,必须支持上下文的自动传播。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:路由分发策略 (Dispatch Strategy)
|
||||
|
||||
| 选项 | A. 纯同步直接调用 (Synchronous Direct) | B. 纯异步队列 (Asynchronous Queued) | C. 混合双通道 (Hybrid Dual-Channel) **(推荐)** |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | `publish()` 时直接在**调用者线程**遍历并执行所有回调函数。 | `publish()` 将事件推入队列。后台线程池异步取出并执行回调。 | 提供 `publishSync`(高优指令)和 `publishAsync`(状态上报)两个接口。 |
|
||||
| **延迟** | **最低 (微秒级)**。无上下文切换,无排队。 | **较高**。受队列深度和调度器负载影响。 | **灵活**。关键指令零延迟,非关键消息不阻塞主业务。 |
|
||||
| **死锁风险** | **高**。如果回调函数里又发了新事件,容易导致递归死锁。 | **低**。解耦了生产者和消费者。 | **中**。需规范同步通道的使用场景。 |
|
||||
| **适用场景** | 紧急停止、资源抢占。 | 日志上报、非关键状态更新。 | **生产环境标准解**。 |
|
||||
|
||||
### 议题 2:订阅者模型 (Subscriber Model)
|
||||
|
||||
| 选项 | A. 泛型/模板回调 (Type-Erasure) **(推荐)** | B. 继承接口 (Inheritance) |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | `bus->subscribe<MyEvent>(lambda)`。利用 `std::function` 和 `std::type_index`。 | 订阅者必须实现 `IEventHandler<MyEvent>` 接口。 |
|
||||
| **耦合度** | **极低**。模块不需要继承特定基类,只要函数签名对就行。 | **高**。侵入性强,增加类层级复杂度。 |
|
||||
| **灵活性** | **高**。支持 Lambda,便于捕获 `this` 指针或上下文。 | 低。 |
|
||||
| **性能** | 极高(现代编译器优化 `std::function` 很好)。 | 虚函数调用开销(微小)。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了兼顾雷达系统对**指令的即时响应**(如资源抢占)和**状态处理的高吞吐**(如海量模块状态变更),我们确立 **C. 混合双通道 + 泛型回调** 为设计基线。
|
||||
|
||||
### 1\. 接口定义基线 (C++14)
|
||||
|
||||
我们定义一个强类型的、支持 `TraceID` 注入的接口。
|
||||
|
||||
```cpp
|
||||
class IEventBus {
|
||||
public:
|
||||
virtual ~IEventBus() = default;
|
||||
|
||||
/**
|
||||
* @brief 订阅特定类型的事件
|
||||
* @tparam EventType 事件结构体类型
|
||||
* @param handler 回调函数,接收 const EventType&
|
||||
*/
|
||||
template <typename EventType>
|
||||
void subscribe(std::function<void(const EventType&)> handler);
|
||||
|
||||
/**
|
||||
* @brief 同步发布 (高优先级指令)
|
||||
* @details 在当前线程立即执行所有订阅者。调用者会被阻塞直到所有处理完成。
|
||||
* @param event 事件对象 (需继承自 BaseEvent 以携带 TraceID)
|
||||
*/
|
||||
template <typename EventType>
|
||||
void publishSync(const EventType& event);
|
||||
|
||||
/**
|
||||
* @brief 异步发布 (状态上报/非关键消息)
|
||||
* @details 将事件放入无锁队列,由 EventBus 内部的 Worker 线程稍后处理。立即返回。
|
||||
* @param event 事件对象
|
||||
*/
|
||||
template <typename EventType>
|
||||
void publishAsync(const EventType& event);
|
||||
};
|
||||
```
|
||||
|
||||
### 2\. 核心实现机制
|
||||
|
||||
- **同步通道 (`publishSync`)**:
|
||||
- **实现**:直接查找 `std::unordered_map<std::type_index, std::vector<Handler>>`。
|
||||
- **锁策略**:使用 `std::shared_timed_mutex` (读写锁)。发布时加**读锁**(允许多个事件同时发布,只要不修改订阅关系),订阅时加**写锁**。
|
||||
- **死锁规避**:**严禁**在 `publishSync` 的回调中再次调用 `subscribe`(修改订阅表)。允许递归调用 `publish`,但需注意栈溢出风险。
|
||||
- **异步通道 (`publishAsync`)**:
|
||||
- **实现**:维护一个 `WorkQueue`。由于事件类型各异,队列元素需使用 `std::function<void()>` 包装器(Type Erasure)来存储“执行动作”,而不是存储原始事件数据。
|
||||
- **并发模型**:
|
||||
- **单分发线程 (默认)**:一个后台线程专门负责从队列取任务并执行。保证了同一事件的消费顺序。
|
||||
- **队列选型**:**MoodyCamel `ConcurrentQueue`** (MPMC 无锁队列) 或 `boost::lockfree::queue`。鉴于麒麟系统库现状,若无第三方库,使用 `std::deque` + `std::mutex` + `std::condition_variable` 也是可接受的(吞吐量在控制面不是瓶颈)。
|
||||
|
||||
### 3\. TraceID 的隐式传递 (2.3.2 预埋)
|
||||
|
||||
- **基线要求**:`IEventBus` 不仅仅是搬运工,它还是**上下文的管理者**。
|
||||
- **同步发布时**:`TraceID` 自然随着线程栈传递。
|
||||
- **异步发布时**:`publishAsync` 必须在**入队时**捕获当前线程的 `TraceID`,并将其打包到 Lambda 中。在**出队执行时**,先恢复该 `TraceID` 到线程本地存储 (TLS),再执行回调。
|
||||
- *这解决了异步调用导致追踪链断裂的经典难题。*
|
||||
|
||||
### 4\. 异常安全边界
|
||||
|
||||
- **基线决策**:`EventBus` **必须捕获**订阅者抛出的所有异常。
|
||||
- **论证**:一个模块的回调函数崩溃(throw exception)绝不能导致发消息的模块(如调度器)崩溃,也不能中断后续其他模块接收该事件。
|
||||
- **行为**:`try-catch` 包裹每个 handler 的调用。捕获异常后,记录 `ERROR` 日志(包含 TraceID),然后继续执行下一个 handler。
|
||||
|
||||
-----
|
||||
|
||||
## 总结与下一步行动
|
||||
|
||||
我们确立了 **2.3.1 事件总线** 的基线:
|
||||
|
||||
1. **架构**:**混合双通道 (Sync/Async)**。
|
||||
2. **实现**:**泛型 Pub/Sub**,基于 C++14。
|
||||
3. **安全**:**异常隔离** + **读写锁保护**。
|
||||
|
||||
有了这个“神经系统”,我们可以开始讨论在这上面跑什么“数据包”了。为了让全链路追踪真正生效,我们需要定义事件的“信封”格式。
|
||||
|
||||
**下一步建议**:进入 **2.3.2 全链路追踪上下文传递 (Trace Context Propagation)**,详细定义 `BaseEvent` 结构和 `TraceContextGuard` 的实现机制。这是之前多次提到的 `TraceID` 落地的具体技术点。
|
||||
|
||||
**提问**:您是否确认 **“混合双通道 + 泛型回调”** 的事件总线基线?确认后我们将深入 2.3.2。
|
||||
Reference in New Issue
Block a user