核心组件设计:事件总线、事件处理器、事件队列、事件路由

好,咱们接着聊。上一章我们把事件驱动架构的骨架搭起来了,现在该往里面填「器官」了。

核心组件就四个:事件总线事件处理器事件队列事件路由。说白了,它们就是整个系统的「血管」、「心脏」、「血库」和「调度员」。我当年第一次设计这套东西时,把事件总线和路由混在一起写,结果调试了三天三夜……嗯,从那以后我再也不敢偷懒了。

1. 事件总线(Event Bus)

事件总线是什么?它就是所有事件的「高速公路」。任何组件想发事件,都走这条路;任何组件想收事件,也在这条路上等着。

我个人习惯把事件总线设计成单例模式。为什么?因为全局只需要一条总线,多了反而乱。你想想看,如果每个模块都有自己的总线,那事件怎么跨模块传递?

核心职责:

  • 提供统一的发布接口(publish / emit)
  • 提供统一的订阅接口(subscribe / on)
  • 管理订阅者列表
  • 事件过滤与分发

代码示例(Python 伪代码):

class EventBus:
    def __init__(self):
        self._subscribers = {}  # 事件类型 -> [处理器列表]

    def subscribe(self, event_type, handler):
        if event_type not in self._subscribers:
            self._subscribers[event_type] = []
        self._subscribers[event_type].append(handler)

    def publish(self, event):
        handlers = self._subscribers.get(event.type, [])
        for handler in handlers:
            handler(event)

这里有个坑:订阅者泄漏。我曾经在一个高频交易系统里,忘了在模块销毁时取消订阅,结果事件越积越多,内存直接爆了。所以,记得加一个 unsubscribe 方法。

注意:事件总线不要做太多事情。它只负责「传递」,不负责「处理」。处理逻辑交给事件处理器。

2. 事件处理器(Event Handler)

事件处理器就是干活的。它收到事件后,执行具体的业务逻辑——比如更新订单状态、计算风险指标、发送报警通知。

我建议把处理器设计成接口/抽象类,这样每个处理器只关注自己那一亩三分地。举个例子:

class EventHandler(ABC):
    @abstractmethod
    def handle(self, event):
        pass

class OrderFilledHandler(EventHandler):
    def handle(self, event):
        # 更新订单状态
        # 计算成交均价
        # 触发风控检查
        pass

为什么要这样?因为量化交易系统里,事件类型太多了。行情事件、订单事件、风控事件、账户事件……如果全写在一个大函数里,那代码就是一团乱麻。我见过一个项目,一个 handler 函数写了 2000 行,后来没人敢改。

小技巧:给每个处理器加一个 can_handle 方法,用来判断当前事件是否该由它处理。这样路由逻辑就清晰了。

3. 事件队列(Event Queue)

事件队列是系统的「缓冲池」。为什么需要它?因为事件的生产速度和消费速度往往不一致。

比如行情来了,一秒 1000 笔 tick,但你的策略计算可能一秒只能处理 200 笔。如果没有队列,事件直接丢给处理器,系统就崩了。

我个人习惯用优先级队列。为什么?因为不是所有事件都同等重要。举个例子:

  • 高优先级:风控事件、撤单事件(必须立即处理)
  • 中优先级:订单成交事件
  • 低优先级:行情 tick、日志事件

代码示例:

import heapq

class PriorityEventQueue:
    def __init__(self):
        self._queue = []
        self._index = 0

    def put(self, event, priority=0):
        heapq.heappush(self._queue, (priority, self._index, event))
        self._index += 1

    def get(self):
        return heapq.heappop(self._queue)[-1]

注意:队列不能无限增长。一定要设置最大长度,或者用「丢弃策略」。我见过一个系统,队列满了之后直接 OOM,整个交易终端挂了 10 分钟……那损失,你懂的。

4. 事件路由(Event Router)

事件路由是系统的「交通指挥员」。它决定:这个事件该发给谁?

最简单的路由就是「根据事件类型分发」。但实际项目中,往往需要更复杂的规则:

  • 基于内容的路由:比如「价格超过 100 的 tick 事件发给策略 A,低于 100 的发给策略 B」
  • 基于来源的路由:比如「来自交易所 A 的事件走通道 1,来自交易所 B 的走通道 2」
  • 基于负载的路由:比如「轮询发给多个处理器,均衡负载」

我建议把路由逻辑单独抽出来,不要和总线混在一起。这样后期改路由规则时,不用动总线代码。

class EventRouter:
    def __init__(self):
        self._routes = []  # [(条件, 处理器)]

    def add_route(self, condition, handler):
        self._routes.append((condition, handler))

    def route(self, event):
        for condition, handler in self._routes:
            if condition(event):
                handler.handle(event)

核心原则:路由只做「判断和转发」,不做「处理」。处理是处理器的事。

5. 整体架构图

下面我用一张 SVG 图把这四个组件的关系画出来。你一看就明白了。

事件源 行情/订单/风控 发布 事件总线 订阅管理 事件分发 路由 事件路由 条件匹配 负载均衡 转发 事件队列 缓冲 优先级 出队 事件处理器 订单处理器 风控处理器 日志处理器 订阅 订阅关系 图例说明 事件源:产生事件的外部或内部模块 事件总线:管理订阅关系,分发事件 事件路由:根据规则匹配处理器 事件队列:缓冲和优先级管理 事件处理器:执行具体业务逻辑

6. 组件协作流程

这四个组件是怎么配合的?我拿一个「订单成交事件」举个例子:

  1. 事件源(交易所接口)收到成交回报,生成 OrderFilledEvent
  2. 调用 事件总线publish 方法,把事件丢进去
  3. 总线把事件交给 事件路由,路由根据事件类型匹配到 OrderFilledHandler
  4. 路由把事件放入 事件队列,等待处理
  5. 事件处理器 从队列中取出事件,执行更新订单状态、计算盈亏、触发风控等逻辑

整个过程是异步的。事件源不用等处理器执行完,直接返回。这样系统就不会被慢操作拖死。

我的经验:刚开始做的时候,别搞太复杂。先实现一个「单队列 + 单线程」的版本,跑通了再上多线程、多队列。一步到位往往死得很难看。

7. 避坑指南

最后,分享几个我踩过的坑:

  • 事件丢失:队列满了直接丢弃事件,但没有任何告警。后来我加了一个「丢弃计数器」,超过阈值就发报警。
  • 死锁:处理器在处理事件时,又往总线发了一个事件,结果自己等自己……嗯,用异步队列可以解决。
  • 性能瓶颈:所有事件走同一个队列,高优先级事件被低优先级事件堵住。后来改成多队列 + 优先级调度,问题解决。

好了,核心组件就这些。下一章我们聊聊「事件定义与序列化」——说白了就是事件长什么样,怎么在网络里传。到时候见。


专注资料整理