一、架构选择:从单体到微服务的演进之路

做交易系统这么多年,我见过太多团队一上来就搞微服务,结果把自己搞崩了。说实话,暗池交易引擎的架构选择,没那么玄乎。

1.1 单体架构:简单粗暴,但够用

先说说单体架构。说白了就是把所有功能都塞进一个进程里。订单管理、撮合逻辑、风控检查、行情推送……全在一个大锅里煮。

单体架构的优点:

  • 开发简单,调试方便,一个进程搞定所有
  • 延迟极低,没有网络开销,函数调用而已
  • 数据一致性天然保证,不用考虑分布式事务

我在2018年做过一个项目,刚开始就是单体架构。撮合引擎跑在单机上,延迟能做到5微秒以内。嗯,这个数字在当时已经相当不错了。

但是,单体架构的坑也很明显:

  • 扩容困难,只能垂直扩展(换更好的机器)
  • 一个模块挂了,整个系统都崩
  • 技术栈绑定,想换语言?重写吧

1.2 微服务架构:拆开来看,各司其职

微服务架构,就是把大系统拆成多个小服务。每个服务独立部署、独立扩展、独立维护。

拿暗池交易引擎来说,我一般会拆成这样:

┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│  订单网关    │  │  撮合引擎    │  │  风控服务    │
│  Order Gateway│  │  Matching    │  │  Risk Control│
└──────┬──────┘  └──────┬──────┘  └──────┬──────┘
       │                │                │
       ▼                ▼                ▼
┌─────────────────────────────────────────────┐
│              消息总线 (Kafka/Pulsar)          │
└─────────────────────────────────────────────┘
       │                │                │
       ▼                ▼                ▼
┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│  行情服务    │  │  清算服务    │  │  监控告警    │
│  Market Data │  │  Settlement │  │  Monitoring │
└─────────────┘  └─────────────┘  └─────────────┘

你想想看,每个服务各管一摊,出了问题只影响局部。我曾经遇到过一次风控服务挂了,但撮合引擎还在跑,订单照常处理——只是少了风控检查而已。

我的建议:刚开始做暗池引擎,别一上来就搞微服务。先拿单体架构跑通核心逻辑,等业务量上来了,再逐步拆分。我见过太多团队,微服务还没搞明白,先把系统搞死了。

二、事件驱动架构(EDA):让系统活起来

事件驱动架构,说白了就是「有事说事,没事别吵」。每个组件只关心自己感兴趣的事件,收到事件就处理,处理完再发新事件。

2.1 为什么需要EDA?

传统的请求-响应模式,在交易系统里有个致命问题:耦合太紧。订单服务调用撮合服务,撮合服务调用清算服务……一个环节卡住,整条链都堵死。

EDA的解决思路很简单:

  • 订单来了,发一个「新订单事件」
  • 撮合引擎订阅这个事件,处理完发「成交事件」
  • 清算服务订阅「成交事件」,开始清算
  • 行情服务也订阅「成交事件」,更新行情

每个服务都是独立的,互不依赖。这就是解耦。

2.2 事件总线的选择

我这些年用过不少消息中间件,说说我的感受:

中间件 吞吐量 延迟 持久化 适用场景
Kafka 极高 毫秒级 磁盘持久化 日志、审计、大数据
Redis Streams 微秒级 内存+磁盘 实时撮合、行情推送
ZeroMQ 微秒级 无持久化 内部进程通信
RabbitMQ 毫秒级 磁盘持久化 任务调度、异步处理

我的经验:在暗池撮合引擎里,核心撮合路径我用的是ZeroMQ,因为它延迟最低。但审计日志和风控事件,我会走Kafka,因为它能保证数据不丢。

三、内存数据库 vs 持久化存储

这个选择其实不复杂。一句话:热数据放内存,冷数据放磁盘

3.1 内存数据库:快,但贵

暗池撮合引擎的核心,就是订单簿。订单簿必须放在内存里,因为撮合需要在纳秒级别完成。

我常用的内存方案:

  • Redis: 适合存订单状态、用户会话、临时数据
  • Aerospike: 适合存账户余额、持仓信息
  • 自研内存结构: 订单簿、价格队列,这些必须自己写
// 一个简单的内存订单簿结构
struct OrderBook {
    // 买单价格队列,按价格降序
    std::map<double, OrderList, std::greater<double>> bids;
    // 卖单价格队列,按价格升序
    std::map<double, OrderList> asks;
    // 订单ID到订单的映射
    std::unordered_map<uint64_t, Order> orders;
};

注意:内存数据库不是万能的。我曾经遇到过Redis宕机,丢了3000多笔订单状态。从那以后,我强制要求所有内存数据必须做持久化备份。

3.2 持久化存储:稳,但慢

持久化存储用来存什么?

  • 成交记录(必须永久保存)
  • 审计日志(监管要求)
  • 用户账户信息
  • 系统配置

我推荐用PostgreSQL或者ClickHouse。PostgreSQL适合OLTP,ClickHouse适合OLAP查询。

3.3 混合架构:两全其美

实际项目中,我用的都是混合架构:

┌─────────────────────────────────────────────┐
│              应用层 (撮合引擎)                │
│         ┌─────────────────────┐              │
│         │   内存数据库 (Redis) │              │
│         │   + 自研内存结构    │              │
│         └─────────┬───────────┘              │
│                   │                          │
│                   ▼                          │
│         ┌─────────────────────┐              │
│         │   持久化存储 (PG)    │              │
│         │   + 归档 (ClickHouse)│              │
│         └─────────────────────┘              │
└─────────────────────────────────────────────┘

说白了就是:撮合的时候用内存,成交之后写磁盘。这样既保证了速度,又保证了数据安全。

四、低延迟网络设计要点

做交易系统,网络延迟是最大的敌人。我见过太多团队,代码写得飞快,结果网络拖了后腿。

4.1 物理层:能近就近

这个其实没啥技术含量,就是物理距离。交易所的服务器和撮合引擎的服务器,最好放在同一个机柜里。我见过最极端的案例,两家公司把服务器放在同一个数据中心,中间用光纤直连,延迟只有0.3微秒。

4.2 协议层:能省则省

TCP?太慢了。三次握手、拥塞控制、重传机制……这些都是延迟杀手。

我推荐用:

  • UDP + 应用层可靠传输: 自己实现ACK和重传,比TCP灵活得多
  • RDMA: 绕过内核,直接内存访问,延迟能降到1微秒以下
  • 共享内存: 同一台机器上的进程通信,用共享内存最快
// 共享内存示例(C++)
// 创建共享内存区域
int shm_id = shmget(IPC_PRIVATE, 1024 * 1024, IPC_CREAT | 0666);
// 映射到进程地址空间
void* shm_addr = shmat(shm_id, NULL, 0);
// 直接用指针读写
Order* order = (Order*)shm_addr;
order->price = 100.5;
order->quantity = 1000;

4.3 软件层:能快则快

网络栈的优化,我总结了几点:

  • 零拷贝: 数据从网卡到应用,不要经过内核缓冲区
  • CPU亲和性: 把网络处理线程绑定到特定CPU核心
  • 中断合并: 减少中断次数,批量处理网络包
  • DPDK: 用户态网卡驱动,彻底绕过内核网络栈

避坑指南:我曾经在一个项目里,用了DPDK之后延迟降到了2微秒,但CPU占用率飙升到90%。后来发现是中断合并参数没调好。嗯,调优是个细活。

五、架构全景图

说了这么多,咱们用一张图把整个架构串起来:

暗池交易撮合引擎系统架构图 接入层 订单网关 | WebSocket服务 | REST API | FIX协议适配 核心撮合层(事件驱动) 订单簿管理 | 撮合引擎 | 风控检查 | 价格发现 内存数据库(Redis + 自研结构) 事件总线(ZeroMQ / Kafka) 数据层 PostgreSQL(交易数据) | ClickHouse(分析数据) | 归档存储 监控与运维 Prometheus + Grafana | 日志收集 | 告警系统 | 链路追踪 用户请求 事件流 数据流 监控流

这张图基本概括了暗池交易引擎的核心架构。从上到下,依次是接入层、核心撮合层、数据层、监控运维层。每一层各司其职,通过事件驱动的方式串联起来。

最后说一句:架构设计没有银弹。单体架构有单体架构的好,微服务有微服务的妙。关键看你的业务场景和团队能力。我个人的习惯是:先跑通,再优化,最后才考虑拆分。别为了微服务而微服务,那是最蠢的做法。


公众号:蓝海资料掘金营,微信deep3321