3. 总体架构设计:分层架构、微服务拆分与服务通信
好,咱们直接进入正题。跨市场多节点交易系统,说白了就是要在不同交易所、不同时区、不同规则下,把交易这件事跑通、跑稳、跑得快。我做了这么多年金融系统,最深的体会就是——架构设计决定了系统的天花板。今天这一章,我就把总体架构设计的三个核心问题掰开揉碎了讲。
3.1 分层架构:接入层、逻辑层、数据层
先说说分层。为什么一定要分层?我在项目中遇到过好几次,有人觉得分层麻烦,把业务逻辑和网络通信写在一起。结果呢?改一个协议就得动整个业务代码,测试一遍要三天。嗯,血的教训。
我习惯把交易系统分成三层:
- 接入层:负责跟外部打交道。包括交易所的API、客户端的WebSocket、FIX协议网关等。说白了就是“门面”,只做协议转换和流量控制,不碰业务。
- 逻辑层:核心业务在这里。订单路由、风控检查、撮合引擎、资金计算……所有跟“交易”有关的逻辑都在这层。
- 数据层:存数据、读数据、同步数据。包括关系型数据库、缓存、消息队列、历史归档等。
你想想看,这三层各司其职,每一层都可以独立扩展、独立部署、独立升级。比如接入层要支持新的交易所协议,你只需要加一个适配器,逻辑层和数据层完全不用动。这就是分层的价值。
下面这张图是我自己画的,把三层架构和关键组件的关系展示清楚:
3.2 微服务拆分原则
分层架构定好了,接下来就是怎么拆微服务。我见过不少团队,一上来就拆成几十个服务,结果运维成本比业务价值还高。拆服务不是目的,目的是让系统更容易维护和扩展。
我个人习惯遵循这几个原则:
- 按业务边界拆分:一个服务只做一件事。比如订单服务、行情服务、风控服务、账户服务。别把订单和风控写在一起,虽然它们有关系,但变化频率和扩展需求完全不同。
- 按数据所有权拆分:每个服务拥有自己的数据库。我曾经接手过一个项目,所有服务共享一个数据库,结果改一个表结构要协调五个团队。那叫一个痛苦。
- 按变化频率拆分:稳定的业务和频繁变动的业务分开。比如基础行情服务相对稳定,但策略执行服务可能每周都在改。把它们拆开,互不影响。
- 按性能要求拆分:高吞吐的服务独立部署。比如撮合引擎需要低延迟,可以单独部署在物理机上;而账户查询服务可以用容器化部署,弹性伸缩。
下面这个表格是我常用的微服务拆分对照表,你可以参考:
| 服务名称 | 核心职责 | 数据存储 | 拆分理由 |
|---|---|---|---|
| 订单路由服务 | 接收订单、选择交易所、发送指令 | MySQL(订单表) | 核心链路,需高可用 |
| 风控引擎 | 检查交易限额、黑白名单、异常检测 | Redis(实时规则)+ MySQL(历史) | 变化频繁,需独立升级 |
| 行情聚合服务 | 收集多交易所行情、去重、推送 | Kafka(实时流)+ Redis(快照) | 高吞吐,需独立扩展 |
| 资金清算服务 | 计算盈亏、手续费、保证金 | MySQL(事务)+ HBase(归档) | 数据一致性要求高 |
| 账户管理服务 | 用户信息、API密钥、权限 | MySQL | 低频访问,可容器化 |
3.3 服务间通信模式
服务拆开了,怎么让它们说话?这是个大问题。我总结下来,跨市场交易系统里常用的通信模式就三种:
3.3.1 同步调用(REST/gRPC)
适合实时性要求高的场景。比如用户下单,需要同步检查风控、查询账户余额。这时候用gRPC比较合适,性能好、有强类型约束。REST也可以,但序列化开销大一些。
但要注意——同步调用会形成调用链依赖。如果风控服务挂了,订单服务也跟着挂。所以同步调用一定要配合熔断、降级、超时控制。
// 伪代码示例:订单服务同步调用风控服务
OrderResponse placeOrder(OrderRequest req) {
// 1. 调用风控服务(同步)
RiskCheckResult risk = riskService.check(req);
if (!risk.isPassed()) {
return OrderResponse.rejected(risk.getReason());
}
// 2. 调用账户服务(同步)
Balance balance = accountService.getBalance(req.getUserId());
if (balance.getAvailable() < req.getAmount()) {
return OrderResponse.rejected("余额不足");
}
// 3. 发送订单到交易所
return exchangeService.sendOrder(req);
}
3.3.2 异步消息(Kafka/RabbitMQ)
适合非实时、可延迟的场景。比如行情推送、订单状态变更通知、日志收集。用消息队列的好处是解耦——生产者只管发,消费者只管收,互不依赖。
我记得有一次,行情服务因为网络抖动延迟了2秒,但因为是异步消息,订单服务完全不受影响。如果当时用的是同步调用,整个系统都得卡住。
3.3.3 事件驱动(Event Sourcing + CQRS)
这个模式在交易系统里特别有用。每个状态变更都记录为事件,查询和写入分离。比如订单状态变化:创建→已提交→部分成交→全部成交→已结算。每个事件都不可变,可以追溯。
我为什么推荐这个模式?因为交易系统最怕数据不一致。事件驱动可以保证所有状态变更都有据可查。出了问题,回放事件就能定位。
// 事件示例:订单状态变更事件
{
"eventId": "evt_20240301_001",
"eventType": "OrderFilled",
"timestamp": 1709251200000,
"data": {
"orderId": "ord_12345",
"filledQty": 100,
"filledPrice": 45000.50,
"remainingQty": 0
}
}
三种模式怎么选?我一般这样判断:
- 需要实时响应、强一致性 → 同步调用(gRPC)
- 可以接受延迟、需要解耦 → 异步消息(Kafka)
- 需要完整审计、可追溯 → 事件驱动(Event Sourcing)
实际项目中,往往是三种模式混用。比如下单用同步,行情推送用异步,资金结算用事件驱动。没有银弹,只有适合。