3. 故障恢复核心原则
在实盘交易系统中,故障恢复不是“把服务重启起来”这么简单。它本质上是在三个互相拉扯的目标之间做取舍:可用性(系统能否持续对外服务)、持久性(已确认的数据是否绝对不丢)、一致性(多个副本或上下游看到的数据是否一致)。三者无法同时做到极致,这就是分布式系统里常说的“不可能三角”在交易场景下的具体体现。
以订单撮合为例:当主节点宕机,如果立刻切换到备节点继续接单,可用性最高,但备节点可能还没同步到最新成交回报,此时就可能出现“用户以为成交、系统却回滚”的一致性问题;反之,如果坚持等主节点数据完全落盘再恢复,持久性最强,但停机时间被拉长,可用性下降。因此,恢复策略的第一步不是写代码,而是先明确业务对这三个维度的优先级排序。
RTO 与 RPO:把恢复目标量化
要谈恢复,必须先定义两个可度量的指标:
- RTO(Recovery Time Objective):从故障发生到服务恢复可用的最长可接受时间。它衡量“停多久”。
- RPO(Recovery Point Objective):故障发生时,允许丢失的数据时间窗口。它衡量“丢多少”。
例如,某交易网关要求 RTO ≤ 30 秒、RPO = 0,意味着切换必须在半分钟内完成,且不能丢失任何一笔已确认委托。而行情推送服务可能允许 RTO ≤ 5 秒、RPO ≤ 1 秒,因为丢失一秒行情可以靠快照补齐。RTO 和 RPO 直接决定了你选择同步复制还是异步复制、是否引入仲裁节点、是否允许降级。
| 业务模块 | RTO 目标 | RPO 目标 | 恢复策略倾向 |
|---|---|---|---|
| 资金/持仓账本 | ≤ 60 秒 | 0 | 同步复制 + 仲裁,优先持久性 |
| 订单撮合核心 | ≤ 30 秒 | 0 | 主备强同步,切换前校验日志位点 |
| 行情推送 | ≤ 5 秒 | ≤ 1 秒 | 异步复制 + 快照补偿,优先可用性 |
| 风控统计 | ≤ 5 分钟 | ≤ 1 分钟 | 可降级为离线重算,优先可用性 |
故障域隔离与降级策略
故障域是指“一个故障能同时影响的最大范围”。恢复设计的关键动作之一,就是把故障域切小,让一次故障不会拖垮整个系统。常见做法包括:按业务线拆分集群、按机房做单元化部署、把数据库主从分散到不同机架、限制单节点承载的连接数与订单量。
当故障已经发生、无法立即完全恢复时,就需要降级。降级不是“关掉功能”,而是有秩序地牺牲非核心能力,保住核心链路。典型降级顺序如下:
- 关闭行情历史回放、报表导出等非实时功能;
- 将风控从实时拦截降为事后告警;
- 限制新用户接入,只服务存量核心客户;
- 暂停非关键业务写入,把资源让给订单与资金链路;
- 最后才考虑只读模式,禁止新委托。
案例:一次主备切换中的权衡复盘
某交易系统在盘中发生主库宕机,运维按预案切换到备库,RTO 约 25 秒,表面达标。但切换后发现:备库比主库少了最后 3 笔成交回报,导致部分用户持仓与交易所不一致。事后复盘发现,复制模式为异步,且切换脚本没有校验日志位点差。这就是典型的用一致性换可用性,却没有把 RPO 约束写进切换流程。
修正方案是:在切换前增加位点校验,若备库落后超过阈值,则先阻塞新委托、从交易所或上游补拉缺失回报,再开放服务。对应伪代码如下:
def failover(primary, standby):
lag = primary.last_lsn - standby.last_lsn
if lag > RPO_THRESHOLD:
block_new_orders()
replay_missing_events(standby, from_lsn=standby.last_lsn)
verify_consistency(standby)
promote(standby)
unblock_new_orders()
恢复原则的落地检查清单
- 是否为每个核心模块单独定义了 RTO 与 RPO,而不是全系统一个数字?
- 复制模式(同步/异步/半同步)是否与 RPO 目标匹配?
- 故障域是否按机房、机架、业务线做了隔离,避免单点连带失效?
- 降级开关是否有优先级顺序、触发条件和恢复条件?
- 切换脚本是否包含位点校验、数据补偿和一致性验证?
- 是否定期做真实切换演练,而不是只做纸面预案?
回到根本:故障恢复的核心原则不是追求“永不故障”,而是在故障发生时,用事先定义好的 RTO/RPO 约束、故障域边界和降级顺序,做出可解释、可验证、可回滚的决策。可用性、持久性、一致性没有标准答案,只有与业务优先级匹配的答案。