一、事件驱动架构概述
做量化交易这些年,我越来越觉得,交易系统本质上就是个事件处理系统。你想想看,行情来了是事件,订单成交了是事件,风控触发了也是事件。说白了,整个交易流程就是事件在不断地产生、流转、被消费。
我个人习惯把事件驱动架构比作一个城市的交通系统。事件就像路上的车,事件总线就是主干道,各个处理模块就是不同的目的地。车要开到哪、走哪条路、什么时候该停,都得有个清晰的规则。
交易系统中的事件驱动模型
传统的轮询模式,说白了就是不停地问「有消息吗?有消息吗?」。这在低频交易时代还行,但到了高频场景,CPU全浪费在空转上了。
事件驱动模型就不一样了。它是被动触发的——有事件来了,系统才干活。没事件?那就歇着。这种模式在交易系统里特别合适,因为市场行情本身就是个典型的异步事件流。
我见过一个团队,早期用轮询方式处理行情,延迟动不动就几十毫秒。后来改成事件驱动,延迟直接降到微秒级。嗯,差距就是这么大。
事件驱动模型的核心组件其实就三个:
- 事件源:谁产生事件?行情网关、订单系统、风控模块、用户操作……
- 事件总线:事件怎么传?内存队列、消息中间件、共享内存……
- 事件处理器:谁来消费?策略引擎、风控模块、日志系统、监控面板……
核心要点:事件驱动模型的关键在于「解耦」。事件源不需要知道谁在处理它,处理器也不需要关心事件从哪来。各模块只跟事件总线打交道,互不依赖。
事件的生命周期
一个事件从生到死,大致经历三个阶段。我在项目中遇到过不少因为生命周期管理不当导致的线上事故,所以这块我特别想讲细一点。
1. 产生(Event Generation)
事件产生的那一刻,其实就已经决定了它的「基因」。比如一个行情Tick事件,它天生就带着时间戳、品种代码、最新价这些属性。
这里有个坑——事件产生时一定要打上精确的时间戳。我曾经排查过一个诡异的问题:两个事件明明有先后顺序,但处理结果却是反的。最后发现,是事件产生时没记录时间,导致后续排序全乱了。
避坑指南:事件产生时,务必记录系统时间(最好是纳秒级)。别等到处理阶段再补时间戳,那时候已经晚了。
2. 路由(Event Routing)
事件产生后,得知道往哪送。这就是路由要做的事。
路由策略通常有几种:
- 广播:所有处理器都能收到。适合行情数据这种「大家都要」的场景。
- 点对点:只发给指定的处理器。适合订单这种「只有特定模块能处理」的场景。
- 主题订阅:处理器按主题订阅,只收自己关心的。这是最灵活的方式。
我个人比较推荐主题订阅模式。你想想看,一个风控模块只关心风控事件,你非要把所有行情事件也塞给它,那不是浪费CPU吗?
3. 消费(Event Consumption)
事件到了处理器手里,怎么消费?同步还是异步?阻塞还是非阻塞?
这里有个原则:消费逻辑要尽量轻量。如果处理一个事件要花几毫秒,那整个系统的吞吐量就上不去。
我记得有一次,一个同事把数据库写入操作直接放在事件处理器里。结果行情一快,事件队列直接爆了。后来改成异步写入,问题就解决了。
小技巧:事件消费时,如果要做耗时操作(比如写数据库、发网络请求),一定要异步化。可以用线程池或者消息队列来解耦。
事件分类的核心维度
事件多了,就得分类。不然系统会乱成一锅粥。我一般从三个维度来给事件分类:
1. 业务类型
这是最直观的分类方式。交易系统里常见的事件类型有:
| 事件类型 | 举例 | 典型处理逻辑 |
|---|---|---|
| 行情事件 | Tick、K线、深度 | 更新策略状态、触发信号 |
| 订单事件 | 下单、撤单、成交 | 更新订单簿、计算持仓 |
| 风控事件 | 超限、异常、熔断 | 暂停交易、报警通知 |
| 系统事件 | 启动、关闭、心跳 | 状态切换、健康检查 |
2. 紧急程度
不是所有事件都值得立刻处理。我习惯把紧急程度分成三级:
- 高优先级:风控事件、资金变动。必须立即处理,延迟不能超过微秒级。
- 中优先级:订单状态变更。可以容忍毫秒级延迟。
- 低优先级:日志、统计信息。可以排队慢慢处理。
为什么要分优先级?说白了,就是保证关键事件不被淹没。你想想看,如果风控事件和行情事件排在一个队列里,行情一多,风控事件就被堵住了。那万一市场剧烈波动,系统可能来不及反应。
实践建议:用多个优先级队列来隔离不同紧急程度的事件。高优先级队列用独立的线程池,保证它永远有资源可用。
3. 影响范围
这个维度很多人会忽略,但其实特别重要。影响范围决定了事件处理失败后的后果:
- 局部事件:只影响单个策略或单个品种。比如某个策略的止损触发,其他策略不受影响。
- 全局事件:影响整个系统。比如账户资金不足、交易所断连。
- 级联事件:一个事件会引发一连串后续事件。比如风控熔断后,所有策略都要停止交易。
我处理过一个级联事件的案例:某个策略的止损触发后,因为没处理好级联逻辑,导致其他策略也跟着误操作。后来我们给每个事件都加了个「影响范围」字段,处理前先判断要不要通知其他模块。
知识体系总览
说了这么多,我画了张图帮你梳理一下整体结构。这张图把事件驱动架构的核心要素串起来了:
这张图把事件驱动架构的核心要素都串起来了。从左到右是事件流的方向,从上到下是分类维度。你设计系统时,可以拿这张图当个检查清单,看看每个环节是不是都考虑到了。
我的经验:刚开始做事件驱动架构时,别想着一步到位。先搭个简单的内存队列版本,跑通了再考虑引入消息中间件。我见过太多团队一上来就上Kafka,结果连基本的事件格式都没定好,最后改得痛不欲生。
好了,事件驱动架构的概述就聊到这。下一节我们会深入事件总线的具体实现,包括怎么设计优先级队列、怎么处理事件积压、怎么保证事件不丢失。这些都是在实战中踩过坑才总结出来的经验,到时候再细聊。
公众号:蓝海资料掘金营,微信deep3321