Files
Inbox/系统基座文件/2/2.3/2.3.1 事件总线架构与路由机制 (Event Bus Architecture & Routing Mechanism).md
2025-12-11 07:24:36 +08:00

7.0 KiB
Raw Blame History

tags, date created, date modified
tags date created date modified
星期四, 十一月 20日 2025, 11:58:41 晚上 星期四, 十一月 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::functionstd::type_index 订阅者必须实现 IEventHandler<MyEvent> 接口。
耦合度 极低。模块不需要继承特定基类,只要函数签名对就行。 。侵入性强,增加类层级复杂度。
灵活性 。支持 Lambda便于捕获 this 指针或上下文。 低。
性能 极高(现代编译器优化 std::function 很好)。 虚函数调用开销(微小)。

三、 基线确立与实施规范

为了兼顾雷达系统对指令的即时响应(如资源抢占)和状态处理的高吞吐(如海量模块状态变更),我们确立 C. 混合双通道 + 泛型回调 为设计基线。

1. 接口定义基线 (C++14)

我们定义一个强类型的、支持 TraceID 注入的接口。

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。