2. 基础知识

在实盘交易系统中,故障恢复、事件回放与状态重建并不是三个孤立的技术点,而是围绕“系统状态如何被可靠地记录、传播与恢复”形成的一套工程体系。理解这套体系,需要先掌握几个基础概念:实盘系统会遇到哪些故障、事件驱动架构如何组织数据流、状态机如何约束业务流转、幂等性如何防止重复处理、一致性如何保证多副本之间的数据可信。这些术语不是抽象名词,而是后续所有恢复策略的设计依据。

一、实盘系统常见故障类型

实盘系统对可用性和数据正确性的要求远高于普通业务系统。一次行情丢失、一笔订单重复提交,都可能直接造成资金损失。常见故障可以按来源分为以下几类:

  • 进程级故障:交易网关、策略引擎、风控服务等进程崩溃、被 OOM Killer 杀死、线程死锁或 GC 停顿过长。这类故障通常表现为心跳中断、日志戛然而止。
  • 节点级故障:物理机或容器宿主机宕机、网络分区、磁盘写满、时钟漂移。节点级故障往往导致该节点上的所有服务同时不可用。
  • 依赖级故障:行情源断流、交易所接口限流或返回错误、数据库主从切换、消息队列积压。依赖故障的特点是本地进程仍存活,但输入输出链路已不可信。
  • 数据级故障:事件乱序、重复投递、消息丢失、状态快照损坏、回放时发现历史事件被篡改。数据级故障最隐蔽,往往在恢复阶段才暴露。
  • 逻辑级故障:策略参数错误、风控规则写反、状态机迁移条件遗漏。这类故障不会让系统崩溃,但会让系统“正确地执行错误逻辑”。

实盘系统排障时,最危险的不是“系统挂了”,而是“系统看起来正常,但状态已经错了”。因此故障分类的目的不是写文档,而是为每一类故障预先设计检测信号与恢复路径。

二、事件驱动架构:状态变化的唯一事实来源

事件驱动架构(Event-Driven Architecture,EDA)是实盘系统实现可回放、可重建的基础。其核心思想是:不直接修改状态,而是先记录“发生了什么”,再由事件推导状态。订单创建、成交回报、行情快照、风控触发、资金变动,都以事件的形式进入系统。

一个典型的事件流如下:

行情网关 → market.tick 事件 → 策略引擎
策略引擎 → order.create 事件 → 风控服务
风控服务 → order.approved 事件 → 交易网关
交易网关 → order.filled 事件 → 持仓服务 / 账务服务
持仓服务 → position.updated 事件 → 风控服务 / 监控面板

事件驱动架构带来三个关键能力:第一,事件日志天然形成审计轨迹,任何状态变化都能追溯到源头;第二,消费者可以按自己的节奏重放事件,重建本地状态;第三,新增下游服务时,只需订阅事件,不必侵入上游逻辑。但代价也很明显:事件顺序、重复投递、消费者位点管理都会成为新的复杂度来源。

事件驱动架构:事件日志作为状态重建的事实来源 行情网关 market.tick 策略引擎 order.create 风控服务 order.approved 交易网关 order.filled 事件日志(Append-Only) offset=1001 tick offset=1002 order.create offset=1003 order.approved offset=1004 order.filled offset=1005 position.updated 顺序不可变 · 可重放 · 可审计 消费者位点决定回放起点 持仓服务 本地状态 = 事件重放结果 幂等消费 · 位点持久化 账务服务 资金状态 = 事件重放结果 对账 · 快照 · 校验 状态重建 快照 + 增量事件回放 从位点恢复至最新状态

三、状态机、幂等性与一致性

事件驱动架构解决了“记录什么”,状态机解决“允许怎么变”。在实盘系统中,订单、持仓、资金账户都应被建模为有限状态机。以订单为例,其状态迁移通常为:CREATED → RISK_CHECKING → APPROVED → SENT → PARTIALLY_FILLED → FILLED,同时存在 REJECTED、CANCELLED、EXPIRED 等终态。状态机的价值在于:任何事件都必须匹配当前状态才允许迁移,否则应被拒绝或进入异常处理流程。

幂等性解决的是“同一事件被处理多次”的问题。在分布式系统中,网络超时、消费者重平衡、人工重放都会导致重复投递。幂等处理要求:同一业务键(如 order_id + event_seq)的事件,无论处理多少次,对状态的影响都相同。常见实现方式包括唯一约束、去重表、版本号比较和状态机终态保护。

一致性则关注多个服务或副本之间的状态是否可信。实盘系统通常追求最终一致性,但在资金和持仓等关键路径上,需要通过事件顺序、快照校验和对账机制来逼近强一致。三者关系可以概括为:状态机定义合法迁移,幂等性保证重复不破坏状态,一致性保证多副本最终收敛到同一状态。

概念 解决的问题 典型手段 在恢复中的角色
事件驱动架构 状态变化如何记录与传播 事件日志、发布订阅、消费者位点 提供可回放的事实来源
状态机 状态迁移是否合法 状态枚举、迁移表、守卫条件 回放时校验事件是否可应用
幂等性 重复事件如何处理 去重键、版本号、唯一约束 保证重放不产生副作用
一致性 多副本状态是否可信 快照、对账、顺序保证 判断重建结果是否正确

四、案例与常见误区

假设某策略引擎在 10:00:00 发出买入订单,交易网关在 10:00:01 收到成交回报,但持仓服务因网络抖动未收到 order.filled 事件。此时持仓服务本地状态显示“未成交”,而交易所实际已成交。恢复时,如果直接从最新快照继续运行,持仓就会永久错误。正确做法是:从持仓服务最后持久化的消费者位点开始,重放 order.filled 之后的所有事件,并在应用事件时通过状态机校验订单是否处于可成交状态,通过幂等键避免重复累加持仓。

事件回放不是“把日志再跑一遍”这么简单。回放前必须确认三件事:回放起点是否准确、事件是否按原始顺序消费、下游副作用(如发单、发消息)是否被隔离。否则回放本身会制造新的故障。

常见误区包括:

  1. 把数据库当前值当作事实来源。数据库状态是事件处理的结果,不是原因。一旦结果被污染,没有事件日志就无法追溯。
  2. 忽略事件顺序。成交回报先于订单创建到达时,若不做缓冲或排序,状态机会进入非法迁移。
  3. 幂等只做在入口。入口去重后,内部服务调用仍可能重复执行,需要在每个有副作用的环节都设计幂等。
  4. 回放时未隔离外部副作用。重放历史订单事件时再次向交易所发单,会造成真实重复下单。
  5. 快照与事件日志不一致。快照生成时未记录对应位点,导致恢复时无法确定从哪条事件继续。

掌握这些基础概念后,后续的故障恢复流程、事件回放策略和状态重建算法才有共同的语义基础。实盘系统的可靠性,本质上来自对“事件—状态—一致性”这条链路的严格控制,而不是某一个单点组件的强大。