2. 事件驱动架构:事件驱动编程范式,观察者模式,事件循环与回调函数

好,咱们进入第二章。这一章聊的是事件驱动架构,说白了就是整个订单簿策略的「心脏」和「血管」。你想想看,行情数据一来,你得立刻响应,不能等。这就是事件驱动的核心思想——来了事,就办事

我个人习惯把事件驱动比作「急诊室」。病人(事件)来了,分诊台(事件循环)立刻判断该找哪个科室(回调函数)。如果让病人排队等叫号,那黄花菜都凉了。量化交易里,毫秒级的延迟就是真金白银的损失。

2.1 事件驱动编程范式

传统的编程是「拉」模式——你主动去问数据到了没。事件驱动是「推」模式——数据到了,它主动通知你。这两种思维完全不同。

举个例子:

# 传统轮询方式(别这么干)
while True:
    orderbook = get_orderbook()  # 你一直在问
    if orderbook.has_update():
        process(orderbook)
    time.sleep(0.001)

# 事件驱动方式(推荐)
def on_orderbook_update(orderbook):
    process(orderbook)  # 数据来了,它叫你

event_engine.register('orderbook.update', on_orderbook_update)

我在项目中遇到过不少新手,上来就写死循环轮询。结果呢?CPU 跑满,延迟还高。后来改成事件驱动,CPU 占用率直接降到 5% 以下。说白了,别主动去问,等着被通知就行

核心要点:事件驱动编程 = 事件源 + 事件循环 + 事件处理器。三者缺一不可。

2.2 观察者模式

观察者模式,是事件驱动架构的「骨架」。它解决了一个核心问题:一个对象状态变化时,如何通知多个依赖它的对象?

你想想看,订单簿更新了,可能有十几个策略模块都需要知道。总不能每个模块都去轮询吧?观察者模式就是干这个的。

我画了一张图,帮你理解这个模式:

订单簿(Subject) 观察者列表 [obs1, obs2, ...] 策略A(Observer) on_update() 策略B(Observer) on_update() 风控模块(Observer) on_update() 事件触发:notify() 图:观察者模式在订单簿中的应用

这张图你看懂了吗?订单簿是「主题」,它维护了一个观察者列表。一旦订单簿有更新,就调用 notify() 方法,遍历列表里的每个观察者,调用它们的 on_update()。这就是观察者模式的精髓。

代码实现其实很简单:

class OrderBookSubject:
    def __init__(self):
        self._observers = []  # 观察者列表
    
    def attach(self, observer):
        self._observers.append(observer)
    
    def detach(self, observer):
        self._observers.remove(observer)
    
    def notify(self, data):
        for obs in self._observers:
            obs.on_update(data)  # 逐个通知

class StrategyObserver:
    def on_update(self, orderbook_data):
        # 处理订单簿数据
        print(f"策略收到更新:{orderbook_data}")

避坑指南:我曾经在观察者列表里直接修改数据,导致其他观察者拿到的是脏数据。记住:通知时尽量传深拷贝,或者只传只读视图。不然一个观察者改了数据,其他人都跟着遭殃。

2.3 事件循环与回调函数

事件循环,就是那个「一直在转的轮子」。它负责从事件队列里取事件,然后分发给对应的回调函数。回调函数,就是「你告诉我怎么做,我照做」。

我习惯把事件循环比作「邮递员」。他不停地从邮局(事件队列)拿信(事件),然后按地址(事件类型)送到各家各户(回调函数)。

一个典型的事件循环长这样:

import queue
import time

class EventEngine:
    def __init__(self):
        self._queue = queue.Queue()  # 事件队列
        self._handlers = {}          # 事件类型 -> 回调函数列表
        self._running = False
    
    def register(self, event_type, handler):
        """注册事件处理器"""
        if event_type not in self._handlers:
            self._handlers[event_type] = []
        self._handlers[event_type].append(handler)
    
    def put(self, event):
        """往队列里放事件"""
        self._queue.put(event)
    
    def _process(self, event):
        """处理单个事件"""
        handlers = self._handlers.get(event.type, [])
        for handler in handlers:
            try:
                handler(event.data)  # 调用回调函数
            except Exception as e:
                print(f"回调执行出错:{e}")
    
    def start(self):
        """启动事件循环"""
        self._running = True
        while self._running:
            try:
                # 阻塞等待,直到有事件到来
                event = self._queue.get(timeout=1)
                self._process(event)
            except queue.Empty:
                continue  # 超时继续循环
    
    def stop(self):
        self._running = False

嗯,这里要注意一个关键点:回调函数里不能做耗时操作。为什么?因为事件循环是单线程的,你一个回调卡住了,后面所有事件都得排队等着。

我在项目中遇到过这种情况:有个回调函数里做了网络请求,结果行情来了,它还在等网络响应。整个策略直接「冻住」了。后来怎么解决的?把耗时操作扔到线程池里:

from concurrent.futures import ThreadPoolExecutor

class SafeEventEngine(EventEngine):
    def __init__(self):
        super().__init__()
        self._executor = ThreadPoolExecutor(max_workers=4)
    
    def _process(self, event):
        handlers = self._handlers.get(event.type, [])
        for handler in handlers:
            # 耗时操作提交到线程池
            if self._is_heavy(handler):
                self._executor.submit(handler, event.data)
            else:
                handler(event.data)  # 轻量操作直接执行

警告:回调函数里不要修改共享状态,除非你加了锁。我曾经见过两个回调同时修改订单簿的快照,结果数据全乱了。记住:回调函数应该是「纯函数」——只读不写,或者写操作加锁

2.4 实战:订单簿事件驱动框架

把上面这些串起来,就是一个完整的订单簿事件驱动框架。我直接给你看核心代码:

# 定义事件类型
class EventType:
    ORDERBOOK_SNAPSHOT = 'orderbook.snapshot'
    ORDERBOOK_UPDATE = 'orderbook.update'
    TRADE = 'trade'
    ORDER_STATUS = 'order.status'

# 定义事件对象
class Event:
    def __init__(self, type, data):
        self.type = type
        self.data = data
        self.timestamp = time.time()

# 订单簿策略基类
class OrderBookStrategy:
    def __init__(self, engine):
        self.engine = engine
        # 注册感兴趣的事件
        engine.register(EventType.ORDERBOOK_UPDATE, self.on_orderbook_update)
        engine.register(EventType.TRADE, self.on_trade)
    
    def on_orderbook_update(self, data):
        """子类实现具体的策略逻辑"""
        raise NotImplementedError
    
    def on_trade(self, data):
        """子类实现成交处理"""
        raise NotImplementedError

# 具体策略示例
class MyStrategy(OrderBookStrategy):
    def on_orderbook_update(self, data):
        # 检查价差,决定是否下单
        spread = data['ask_price'] - data['bid_price']
        if spread > 0.5:
            print(f"价差过大,准备套利:{spread}")
            # 这里可以调用下单接口
    
    def on_trade(self, data):
        print(f"成交了:{data}")

这个框架的好处是:策略逻辑和事件分发完全解耦。你想加一个新策略?继承 OrderBookStrategy,实现两个回调方法就行。其他代码一行都不用改。

组件 职责 我踩过的坑
事件引擎 管理事件队列和分发 队列用无界队列,内存爆过
事件对象 封装事件类型和数据 忘了加时间戳,回放时没法对齐
回调函数 处理具体业务逻辑 回调里抛异常,整个循环挂了
观察者列表 维护订阅关系 动态增删观察者导致迭代异常

总结一下:事件驱动架构的核心就三件事——事件来了,找到对应的回调,执行它。观察者模式帮你管理「谁关心什么事件」,事件循环帮你「不停地取事件、分事件」。这两者配合好了,你的订单簿策略就能做到「行情一到,立刻响应」。

我个人习惯在项目里用 asyncio 实现事件循环,性能更好。但今天讲的这个基础版本,足够你理解事件驱动的本质了。你想想看,是不是比轮询优雅多了?


公众号:蓝海数据掘金营,微信deep3321