2、事件驱动架构:事件驱动编程模型、观察者模式、事件循环与回调、异步处理基础
好,咱们进入第二章。这一章我打算聊聊事件驱动架构。说实话,这是整个订单簿交易框架的灵魂。你想想看,市场数据每秒成千上万条涌进来,如果还用那种「你问我答」的同步方式,系统早就卡死了。
事件驱动,说白了就是「有事您说话,没事别烦我」。系统里各个模块各自待命,一旦有事件发生——比如新订单来了、价格变了——就立刻响应。这种模式特别适合高频交易场景。
2.1 事件驱动编程模型
事件驱动编程,核心就三个角色:事件源、事件通道、事件处理器。
- 事件源:谁产生事件?交易所的行情推送、用户的下单指令、风控模块的告警……这些都是事件源。
- 事件通道:事件怎么传递?通常是一个队列或者消息总线。我习惯用 Python 的
queue.Queue或者 Redis 的 List。 - 事件处理器:谁来处理?各个策略模块、风控模块、日志模块,它们订阅自己感兴趣的事件。
举个例子,一个简单的订单簿更新事件:
class OrderBookEvent:
def __init__(self, event_type, data):
self.event_type = event_type # 'ADD', 'UPDATE', 'DELETE'
self.data = data # 订单详情
self.timestamp = time.time() # 事件发生时间
嗯,这里要注意:事件本身要尽量轻量。我在项目中见过有人把整个订单簿对象塞进事件里,结果内存直接爆了。事件只传递必要信息,比如订单ID、价格、数量,就够了。
2.2 观察者模式
观察者模式,是事件驱动最经典的实现方式。它定义了一种一对多的依赖关系:一个主题对象(Subject)可以通知多个观察者(Observer)。
我刚开始做量化时,把观察者模式理解成了「广播」。其实不对。广播是无差别的,而观察者模式是有选择地通知。每个观察者只关心自己订阅的事件类型。
来看一个精简的实现:
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.event_type, [])
for handler in handlers:
handler(event)
我曾经踩过一个坑:订阅了事件,但忘记取消订阅。结果系统运行几天后,内存里堆积了成千上万个无用的处理器引用。所以,一定要在模块销毁时取消订阅。
2.3 事件循环与回调
事件循环,就是那个「一直在跑、不断检查有没有新事件」的循环。它通常长这样:
while True:
event = event_queue.get() # 阻塞等待事件
handle_event(event) # 处理事件
你可能会问:这不就是个死循环吗?对,但它不是普通的死循环。它是有调度策略的。比如,高优先级的事件先处理,或者某些事件可以合并处理。
回调函数,是事件循环的「搭档」。当事件发生时,事件循环会调用对应的回调函数。回调函数不能阻塞,否则整个事件循环就卡住了。
我记得有一次,一个同事在回调函数里做了数据库查询,结果查询耗时 200 毫秒。在交易系统里,200 毫秒足够错过好几个 tick 了。所以,回调函数里绝对不能做耗时操作。
2.4 异步处理基础
异步处理,是为了解决「一个事件处理太久,影响其他事件」的问题。Python 里常用的异步方式有:
- 多线程:每个事件处理器跑在独立线程里。但要注意 GIL 锁。
- 协程:用
asyncio实现,适合 I/O 密集型任务。 - 进程池:适合 CPU 密集型计算,比如策略信号生成。
我个人推荐在订单簿系统里用协程 + 线程池的组合。协程处理网络 I/O(比如接收行情),线程池处理计算任务(比如更新订单簿)。
来看一个简单的异步事件处理器:
import asyncio
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=4)
async def handle_order_book_update(event):
# 异步处理网络 I/O
data = await fetch_additional_data(event)
# 同步计算任务丢到线程池
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(executor, heavy_computation, data)
# 发布结果事件
event_bus.publish(ResultEvent(result))
这里有个关键点:事件处理不能保证顺序。如果你依赖事件顺序(比如先加订单再删订单),就必须在事件里加上序列号,或者用有界队列保证顺序。
2.5 本章知识体系
下面这张图,是我自己画的事件驱动架构核心逻辑。你看一眼,基本就明白整个体系怎么串起来的。
这张图从左到右、从上到下,完整展示了事件从产生到最终处理的流程。你写代码时,就按这个结构来组织模块,基本不会出错。
公众号:蓝海资料掘金营,微信deep3321