2. 核心概念解析:事件、事件源、事件处理器、事件循环

好,咱们正式开始啃核心概念。

这四个词,说白了就是事件驱动引擎的「四根柱子」。你搞懂了它们,整个系统的骨架就清晰了。我当年刚接触这个架构时,也花了不少时间才理清它们之间的关系。今天咱们一个一个拆开讲。

2.1 事件(Event)—— 系统的「神经信号」

事件是什么?简单说,就是「发生了某件事」这个信息本身。

在交易系统里,事件可以是一个订单被成交了,可以是一笔行情数据到了,也可以是风控规则被触发了。它不包含业务逻辑,只包含「发生了什么」以及「相关的数据」。

我个人习惯把事件设计成不可变对象。一旦创建,就不能修改。为什么?因为事件可能在多个处理器之间传递,如果谁都能改,你根本追查不到问题出在哪一步。我在一个早期项目里吃过这个亏,后来所有事件都强制用只读结构。

一个典型的事件结构长这样:

@dataclass(frozen=True)
class TradeEvent:
    event_id: str
    event_type: str          # 比如 "ORDER_FILLED"
    symbol: str
    price: float
    quantity: int
    timestamp: float
    extra: dict = field(default_factory=dict)

注意几个关键点:

  • event_type:用字符串常量,别用数字。可读性第一。
  • timestamp:统一用浮点数或纳秒级整数。我建议用纳秒,高频场景下差几微秒都可能出问题。
  • extra:兜底字段。你永远不知道未来会加什么信息,留个字典最灵活。

小技巧:事件类型建议用枚举或常量类管理。我曾经见过有人直接写字符串 "fill"、"FILL"、"filled" 混用,排查了一天。统一管理,省心。

2.2 事件源(Event Source)—— 谁在「生产」事件?

事件源就是事件的「生产者」。它负责监听外部或内部的变化,然后包装成事件丢进系统。

常见的几类事件源:

事件源类型 举例 说明
外部数据源 行情网关、交易所API 接收网络数据,解析成事件
内部定时器 心跳事件、定时任务 按时间周期触发
用户操作 手动下单、参数调整 GUI或CLI触发
系统内部 风控模块、订单管理 模块间通信产生的事件

事件源的核心职责只有一个:把原始信号转成标准事件。它不需要关心事件被谁处理、怎么处理。

class MarketDataSource:
    def __init__(self, websocket_url: str):
        self.ws_url = websocket_url

    def start(self, event_bus):
        # 伪代码:连接行情,收到数据就发布事件
        while True:
            raw_data = self.ws.receive()
            event = self._parse(raw_data)
            event_bus.publish(event)  # 只负责发布,不负责处理

注意:事件源不要做耗时操作。我曾经见过有人在行情解析里做复杂计算,结果阻塞了数据接收,导致行情延迟。事件源只做「接收→包装→发布」这三步,其他事情交给处理器。

2.3 事件处理器(Event Handler)—— 真正干活的「工人」

事件处理器就是处理事件的逻辑单元。它订阅特定类型的事件,然后执行对应的业务逻辑。

一个处理器只做一件事。这是原则。

举个例子:

class OrderFillHandler:
    def __init__(self, portfolio_manager):
        self.pm = portfolio_manager

    def handle(self, event: TradeEvent):
        if event.event_type != "ORDER_FILLED":
            return  # 不关心的直接跳过

        # 更新持仓
        self.pm.update_position(event.symbol, event.quantity, event.price)

        # 记录日志
        logger.info(f"成交: {event.symbol} {event.quantity}@{event.price}")

        # 触发后续策略逻辑
        self.pm.check_strategy_triggers(event.symbol)

你想想看,如果我把成交处理、风控检查、日志记录、策略触发全塞到一个处理器里,会怎样?

  • 代码耦合,改一处可能影响所有逻辑
  • 难以测试,你得模拟各种场景
  • 扩展困难,加一个新功能就得改老代码

我的经验:每个处理器只订阅它需要的事件类型。用装饰器或注册表来声明订阅关系,比硬编码 if-else 优雅得多。

2.4 事件循环(Event Loop)—— 系统的「心脏」

事件循环就是那个永不停歇的「调度器」。它从事件队列里取出事件,然后分发给对应的处理器。

说白了,就是一个 while True 循环,但里面的门道不少。

class EventLoop:
    def __init__(self):
        self.queue = asyncio.Queue()
        self.handlers = {}  # event_type -> [handler1, handler2, ...]

    def register(self, event_type: str, handler):
        self.handlers.setdefault(event_type, []).append(handler)

    async def run(self):
        while True:
            event = await self.queue.get()
            handlers = self.handlers.get(event.event_type, [])
            for handler in handlers:
                try:
                    await handler.handle(event)
                except Exception as e:
                    logger.error(f"处理器异常: {e}", exc_info=True)
                    # 这里要决定:继续还是终止?

事件循环要处理几个关键问题:

问题 解决方案 我的建议
处理器异常 捕获异常,记录日志,继续运行 别让一个处理器拖垮整个系统
事件积压 监控队列长度,触发背压机制 设置最大队列长度,超限就丢弃或降级
处理器耗时 异步处理,或使用线程池 高频场景下,处理器必须毫秒级完成
事件顺序 同一标的的事件保持FIFO 用分区队列,按symbol分桶

避坑指南:我曾经在一个项目里没做异常隔离,结果一个处理器抛异常后,整个事件循环挂了。所有订单都卡住了,那叫一个惨。记住:事件循环必须健壮到能处理任何异常

2.5 四者关系:一张图说清楚

这四个概念的关系,我用一张 SVG 图来展示:

事件源 行情/订单/定时器 产生 事件队列 FIFO 缓冲区 取出 事件循环 调度分发 分发 事件处理器 成交处理器 风控处理器 日志处理器 策略处理器 事件源 事件队列 事件循环 事件处理器

这张图你看懂了吗?数据流是单向的:事件源产生事件 → 放入队列 → 事件循环取出 → 分发给处理器。没有回头路,没有交叉依赖。

核心原则:事件源不知道处理器存在,处理器不知道事件源是谁,事件循环只负责调度不负责业务。各司其职,这就是事件驱动架构的精髓。

嗯,这四个概念就讲到这里。你可能会觉得「就这?」,但相信我,真正落地时,每个环节都有坑等着你。后面几章咱们会深入每个模块的实战细节。


公众号:蓝海资料掘金营,微信deep3321