3. 总体架构设计:分层架构、微服务拆分与服务通信

好,咱们直接进入正题。跨市场多节点交易系统,说白了就是要在不同交易所、不同时区、不同规则下,把交易这件事跑通、跑稳、跑得快。我做了这么多年金融系统,最深的体会就是——架构设计决定了系统的天花板。今天这一章,我就把总体架构设计的三个核心问题掰开揉碎了讲。

3.1 分层架构:接入层、逻辑层、数据层

先说说分层。为什么一定要分层?我在项目中遇到过好几次,有人觉得分层麻烦,把业务逻辑和网络通信写在一起。结果呢?改一个协议就得动整个业务代码,测试一遍要三天。嗯,血的教训。

我习惯把交易系统分成三层:

  • 接入层:负责跟外部打交道。包括交易所的API、客户端的WebSocket、FIX协议网关等。说白了就是“门面”,只做协议转换和流量控制,不碰业务。
  • 逻辑层:核心业务在这里。订单路由、风控检查、撮合引擎、资金计算……所有跟“交易”有关的逻辑都在这层。
  • 数据层:存数据、读数据、同步数据。包括关系型数据库、缓存、消息队列、历史归档等。

你想想看,这三层各司其职,每一层都可以独立扩展、独立部署、独立升级。比如接入层要支持新的交易所协议,你只需要加一个适配器,逻辑层和数据层完全不用动。这就是分层的价值。

核心原则:每一层只依赖下一层,不能跨层调用。接入层不能直接访问数据库,逻辑层不能直接处理网络协议。

下面这张图是我自己画的,把三层架构和关键组件的关系展示清楚:

跨市场交易系统分层架构 接入层(Gateway Layer) FIX协议网关 | WebSocket服务 | REST API | 交易所适配器 职责:协议转换、连接管理、流量整形、身份认证 逻辑层(Service Layer) 订单路由服务 | 风控引擎 | 撮合引擎 | 资金清算 行情聚合 | 策略执行 | 账户管理 职责:业务逻辑处理、状态管理、决策执行 数据层(Data Layer) MySQL集群 | Redis缓存 | Kafka消息队列 | HBase历史库 Elasticsearch日志 | 分布式文件系统 职责:数据持久化、缓存加速、异步通信、数据归档

3.2 微服务拆分原则

分层架构定好了,接下来就是怎么拆微服务。我见过不少团队,一上来就拆成几十个服务,结果运维成本比业务价值还高。拆服务不是目的,目的是让系统更容易维护和扩展

我个人习惯遵循这几个原则:

  1. 按业务边界拆分:一个服务只做一件事。比如订单服务、行情服务、风控服务、账户服务。别把订单和风控写在一起,虽然它们有关系,但变化频率和扩展需求完全不同。
  2. 按数据所有权拆分:每个服务拥有自己的数据库。我曾经接手过一个项目,所有服务共享一个数据库,结果改一个表结构要协调五个团队。那叫一个痛苦。
  3. 按变化频率拆分:稳定的业务和频繁变动的业务分开。比如基础行情服务相对稳定,但策略执行服务可能每周都在改。把它们拆开,互不影响。
  4. 按性能要求拆分:高吞吐的服务独立部署。比如撮合引擎需要低延迟,可以单独部署在物理机上;而账户查询服务可以用容器化部署,弹性伸缩。
避坑指南:我曾经把一个“订单服务”拆得太细,分成了“订单创建服务”、“订单修改服务”、“订单查询服务”。结果一个订单操作要跨三个服务调用,延迟翻了三倍。后来我学乖了——先粗后细,按需拆分

下面这个表格是我常用的微服务拆分对照表,你可以参考:

服务名称 核心职责 数据存储 拆分理由
订单路由服务 接收订单、选择交易所、发送指令 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)

实际项目中,往往是三种模式混用。比如下单用同步,行情推送用异步,资金结算用事件驱动。没有银弹,只有适合。


专注资料整理