这篇讲的是证券交易系统,这类系统包含的内容很多,但是我们还是把目光放在核心的交易部分,比如说股票交易。在某个可交易时间,如果卖家 A 要以至少 y 的价格卖掉股票 x,卖家 B 愿以至多 y 的价格买入股票 x,那么这个交易就可以发生。
虽说是交易系统,但是它和任何一个支付平台的交易系统有着显著的不同,它的核心是一个竞价匹配的机制,而非货币支付的机制,简单地说,这个机制包含了这样四个步骤:
- 挂单(可以是买单,也可以是卖单)
- 匹配(或者叫做撮合)
- 成交
- 清算
从非功能的角度看,有这样几条需求是这样的系统尤其要强调的:
- Consistency,从单个交易的角度来说,主要就是事务性,这是交易系统最最基本的要求。系统不能用了是个灾难,但是如果交易数据错误了这就是个大得多的灾难。
- Durability,交易数据必须要持久化。
- Throughput,很多股票市场都是对全球开放的,吞吐量意味着对于交易高峰的接纳能力。
- Latency,和吞吐量关系密切,可以放在一起讨论。大型交易系统的延迟的最小单位都是 按微秒论的。从架构上看这类系统具备一些异步系统(比如下单支付系统)的特点,但是低延迟的要求决定了它的处理方式明显不同。
- 整个系统来看,包括挂单、匹配、交易和查询这样几个部分。实线部分表示的是写或读写操作,虚线是读操作。
- 假设有卖家和买家两个用户,分别在不同的时间提交了挂单请求,一个是卖单,一个是买单。
- 鉴权分为两个部分来完成,基础的部分由 API Gateway 来完成。
- Exchange Server 收到原始挂单请求以后,首先调用 Sequencer 去获取一个时间戳,也包括一个基于时间戳生成的 ID。这个时间戳非常重要,因为交易的逻辑里面,对于买单卖单的匹配,以及同价单的优先级,都要基于时间戳的规则来进行。时间戳不能仅仅基于 Exchange Server 自己的时钟来进行,因为每台机器的时钟很可能都不一样。
- 对于买单,查询账户系统并扣住保证金。
- 将买单或卖单放入指定的队列,不同的股票有不同的队列来维护。这个队列本身是一个优先级队列,从宏观上看就是 order book。对于买单来说,买价越高越靠前;对于卖单来说,卖价越低越靠前。
- 不同队列的变更会被 router 通知到不同的匹配(撮合)引擎,这里有多个不同的引擎,每个引擎关注不同的队列变更。在每个引擎的内存中维护一个队列中靠近队列出口的买卖单集合,也是以优先级队列的形式维护在内存中(具体实现可以是堆)。
- 这样迅速的匹配就可以在内存中迅速发生并完成,内存的数据结构以 Snapshot + WHL 的方式持久化,以达到效率和状态不丢失的平衡。
- 如果这台机器崩溃,还有集群中的备用机可以顶上,并从上述的 Snapshot + WHL 中恢复之前的状态。这些机器都通过 Node Manager(比如 Zookeeper)来管理。
- 每次匹配完成,都有一个事件加入到 exchange 的队列中,每只股票都有自己的 exchange 队列。
- Router 将队列的事件通知到相应的支付系统和 Tick Calculator。支付系统(或者是清算系统)会完成用户扣款或打款的操作,而 Tick Calculator 会根据交易信息改变当前的股价并持久化到数据库中(这里的数据库需要较大的吞吐量,可以根据股票种类+时间序做 sharding)。
- 用户可以查询自己的账户变更状况,用户也可以通过 Quotation 系统查询股价变更状况。
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》