二、系统架构总览:整体架构分层设计、核心模块划分、技术选型原则、架构设计目标

好,咱们正式开始聊架构。很多朋友一上来就问我:“做市系统到底长什么样?” 我的回答通常是:别急着看代码,先看分层。分层搞明白了,后面所有细节都能对号入座。

我个人习惯把债券做市系统拆成四层。为什么是四层?因为我在第一版设计时只分了“接入”和“业务”两层,结果上线后耦合得一塌糊涂,改一个报价逻辑差点把网关搞崩。后来痛定思痛,才有了今天这套分层方案。

2.1 整体架构分层设计

先看这张图,这是我手绘的架构分层,你感受一下:

接入层(Access Layer) FIX/WS/HTTP 网关 | 会话管理 | 协议适配 | 流量整形 延迟目标:< 10μs 核心业务层(Core Business Layer) 报价引擎 | 风控引擎 | 订单管理 | 做市策略 簿记管理 | 对冲计算 | 损益核算 延迟目标:< 50μs(含策略计算) 数据服务层(Data Service Layer) 行情缓存 | 历史数据 | 参考数据 | 实时快照 Redis Cluster | In-Memory Grid | 时序数据库 延迟目标:< 100μs(缓存命中) 基础设施层(Infrastructure Layer) 消息队列(Kafka/Pulsar)| 服务发现(Consul) 监控告警(Prometheus + Grafana)| 日志中心 网络 内存 缓存 磁盘

这张图我画了很多遍,每次讲课时都会拿出来。你注意看,每一层我都标注了延迟目标。为什么?因为做市系统对延迟极度敏感,每一层多花10微秒,到客户端可能就是一笔交易都抢不到。

核心原则:层与层之间通过内存队列或共享内存通信,避免跨网络调用。我见过有人把行情数据从接入层先写到数据库,再让业务层去读——结果延迟直接飙到毫秒级,这种设计在债券做市里是行不通的。

2.2 核心模块划分

分层之后,咱们看看每一层里到底放了哪些模块。我按模块的重要程度排个序:

2.2.1 接入层模块

  • FIX 网关:处理 FIX 4.4/5.0 协议,支持会话恢复。我记得有一次交易所升级协议版本,我们的网关因为没做版本协商,直接断连了半小时。后来我加了个协议版本自动探测机制,再也没出过问题。
  • WebSocket 网关:给内部交易员用的,支持订阅式行情推送。
  • HTTP REST 网关:管理类操作,比如查询持仓、修改参数。这个路径不追求极致延迟,但要求高可用。
  • 流量整形器:防止恶意高频请求打垮后端。我曾经遇到一个客户,每秒发 10 万笔报价请求,差点把系统搞挂。后来加了令牌桶限流,才稳住。

2.2.2 核心业务层模块

  • 报价引擎:这是心脏。接收行情,计算双边报价,发送给交易所。内部用事件驱动架构,每个债券的报价计算在一个独立的 Actor 里完成。
  • 风控引擎:检查每笔报价和成交是否越界。包括:最大持仓、最大名义本金、单笔最小/最大量、价格偏离度等。风控必须前置,不能等成交了再检查。
  • 订单管理器:维护所有未成交订单的状态机。支持撤单、改单、部分成交等场景。
  • 做市策略模块:这是各家券商的“黑盒”。我见过用纯规则引擎的,也见过用强化学习的。但不管用什么,策略模块必须支持热加载,不能因为改参数就重启系统。
  • 簿记管理:实时记录每笔成交,计算净头寸。这个模块的数据一致性要求极高,我建议用乐观锁 + 版本号机制。

2.2.3 数据服务层模块

  • 行情缓存:用 Redis Cluster 存最新行情快照。注意,这里不能用 Redis 的持久化功能,会拖慢性能。
  • 历史数据存储:用 ClickHouse 或 TimescaleDB 存 tick 级数据,用于回测和复盘。
  • 参考数据管理:债券基本信息、发行人评级、久期、凸性等。这些数据变化不频繁,但查询量大,建议用本地内存缓存。

2.2.4 基础设施层模块

  • 消息队列:Kafka 用于异步事件,比如成交确认、风控告警。Pulsar 用于跨机房复制。
  • 服务发现:Consul 管理所有微服务的注册与健康检查。
  • 监控告警:Prometheus 采集指标,Grafana 展示。告警规则要细化到每个模块的延迟 P99。

我的经验:模块划分时,一定要遵循“高内聚、低耦合”原则。我见过一个系统把报价计算和订单管理放在同一个进程里,结果报价逻辑出 bug 导致整个订单处理阻塞。后来拆成两个独立进程,用共享内存通信,问题就解决了。

2.3 技术选型原则

技术选型这事儿,我踩过的坑比走过的路还多。总结下来,就三条原则:

原则 说明 我的案例
延迟优先 能用 C++/Rust 就不用 Java,能用 Java 就不用 Python。核心路径上禁止 GC 停顿。 我曾经用 Java 写报价引擎,结果 Full GC 导致报价中断了 2 秒,被交易所罚款。后来全部重写为 C++,延迟稳定在 5μs 以内。
简单至上 能用单机解决就别上分布式。能用内存解决就别用数据库。 有个团队非要用 Cassandra 存行情快照,结果延迟比 Redis 高了一个数量级。我直接让他们换成 Redis Cluster,性能提升 10 倍。
可观测性 每个模块必须暴露延迟、吞吐量、错误率三个指标。没有监控的模块,等于没上线。 我接手过一个系统,没有任何日志和指标。出问题时全靠猜。后来我强制要求每个接口都打日志,并接入 Prometheus,排查效率提升 80%。

避坑指南:我曾经选过一个“看起来很酷”的分布式数据库,结果运维成本极高,而且延迟不稳定。后来我换回了单机 Redis + 本地磁盘持久化,反而更可靠。记住:做市系统不是炫技场,稳定压倒一切。

2.4 架构设计目标

聊完分层和选型,咱们得明确目标。没有目标的架构,就像没有地图的航行。我总结为三个关键词:

2.4.1 低延迟

债券做市对延迟的要求有多苛刻?这么说吧,交易所的撮合周期是 100 微秒。如果你的报价比竞争对手晚到 50 微秒,这笔交易就跟你没关系了。所以,我们的目标是:

  • 接入层到核心业务层:< 20μs
  • 报价计算完成:< 50μs
  • 全链路端到端:< 200μs

怎么实现?说白了就是:减少拷贝、避免锁、用无锁队列、CPU 亲和性绑定。我习惯把核心线程绑定到独立 CPU 核心上,避免上下文切换。

2.4.2 高可用

做市系统不能停。停了就是真金白银的损失。我要求系统达到 99.999% 的可用性,也就是一年宕机时间不超过 5 分钟。怎么做到?

  • 多活部署:至少两个机房同时提供服务,一个挂了另一个秒级接管。
  • 无状态设计:所有业务模块都是无状态的,状态统一放在 Redis 或共享内存里。
  • 优雅降级:行情源断了怎么办?风控模块挂了怎么办?每个模块都要有降级方案。我遇到过行情源中断 10 分钟的情况,系统自动切换到备行情源,交易员完全无感知。

2.4.3 可扩展

债券市场在变,监管在变,策略在变。架构必须能快速适应变化。我的原则是:

  • 插件化策略:每个做市策略是一个独立的插件,可以热加载、热卸载。
  • 配置驱动:所有业务参数(报价价差、最大持仓、风控阈值)都通过配置中心下发,不需要改代码。
  • 水平扩展:当债券数量从 1000 只增长到 10000 只时,系统能通过加机器线性扩展。我建议用一致性哈希做分片,每个分片负责一部分债券。

总结一下:低延迟是生存之本,高可用是底线,可扩展是未来。这三个目标缺一不可。如果你问我哪个最重要?我会说:低延迟。因为做市系统如果慢了,连生存的机会都没有,还谈什么高可用和可扩展?

嗯,架构总览就聊到这儿。下一节咱们深入报价引擎,看看那个“心脏”到底是怎么跳动的。


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