1.撮合引擎怎么设计的?
基于内存计算的撮合引擎,要满足以下的几点:
低延迟、撮合的确定性、可恢复、要与风控平台解耦
整体链路是:API-风控-撮合引擎(内存)-成交落库-资产变更
撮合只做一件事:确定成交结果,不直接操作资产(在 cex中撮合引擎只计算保存撮合的结果,不直接操作资产,它只会产出成交事件,推送给资产系统,行情系统,推送系统)
(1.1)那么问题来了,资产什么时候被操作
用户下单
↓
风控系统冻结资产(这一步已经发生)
↓
撮合引擎计算成交结果(不碰钱)
↓
成交事件 Trade
↓
资产系统消费 Trade
↓
真正修改账户余额
(1.2)资产变化要做到可回放,可审计
可回放指:不依赖账户当前状态,只靠交易历史账单,即可把账户状态从 0 算出来,并且和现在结果一致
(1.3)流水表越来越大,重放一次要几个小时,怎么办?
做增量回放,而不是全量回放;定期回放给出一个确切标点,定期把这个回放结果冻结成一个可信状态
一般高频账户,每1000 条交易记录做一次或者每 5 分钟回放一次;低频用户一般为每日做一次
(1.4)成交事件发给资产系统失败了,怎么办? 撮合已经成功了,钱还没变,系统怎么保证最终一致?
只要资产没入账,交易在系统层面就是「未完成态」,必须通过可靠事件 + 重试 + 幂等 + 对账补偿 保证最终一致
撮合成功时,做的不是“转钱”,而是生成一笔不可变的成交事件
事件怎么保证不丢?
- 1、写成交表 trade
- 2、写 event_outbox
异步投递消息:失败重试,MQ挂了等恢复,不影响前面撮合总流水
兜底: 定时扫描交易表,查看已经撮合成功的单和资产流水变里,有没有对应的资产变化流水;有对应的流水,修改对应的资产变化状态,没有则重发;(重复时需要避免重复消费)
2.撮合引擎涉及到哪些表或数据结构
1 | class OrderBook { |
TreeMap:保证价格有序
OrderQueue:同价位 FIFO(时间优先)
1 | -- 订单表 |
–快照表,挂单表,资产变更流水表,资产冻结表
3.撮合引擎的执行流程是怎样的?
1 | 新订单进来 |
4.撮合引擎怎么确保是单线程的?以及为啥不设计成多线程?如果设计成多线程会引发哪些问题?
撮合引擎怎么确保单线程?
撮合系统采用的是 Single Writer 架构。
网关层按对应币对儿的symbol做确定性路由,同一交易对在任一时刻只会被一个撮合执行流处理。
其他节点只做热备和状态订阅,不参与写入。
主备切换是通过事件日志replay完成的,不存在双写窗口,因此可以保证顺序一致性和可回放性
为何不设计成单线程?
1.单核单线程CPU 跑满的情况下,基于内存的撮合,10 万级别TPS可以做到毫秒级延迟
2.单线程没有锁,内存连续 cache命中率高;指令路径短
3.金融系统的设计是基于确定性和可回放性,对于撮合来说最终的是顺序一致结果唯一,通过“一个交易对一个线程”的模型,交易所把并发问题前移到路由层,用内存换无锁,用确定性换稳定性。
设计成多线程会面临的问题?
多线程撮合最大的问题不是性能,而是破坏了撮合结果的确定性。
在多线程环境下,很难严格保证价格优先、时间优先,成交顺序依赖线程调度,导致结果不可重放。
即使通过锁保证互斥,也无法保证顺序公平性,反而引入延迟抖动、死锁和一致性风险。
因此主流交易所采用single-writer架构,用单线程换确定性,用内存换稳定
5.交易所有哪些模块?
交易所 = 钱包 + 账户 + 交易 + 撮合 + 清算 + 行情 + 风控