第二章 系统架构设计:整体架构图、模块划分与数据流设计

好,咱们直接进入正题。跨交易所套利系统,说白了就是一台精密的「搬砖机器」。你想想看,它要在多个交易所之间来回倒腾,既要快,又要稳,还不能出错。那这机器内部到底长什么样?我今天就带你拆开看看。

我个人习惯,设计任何系统之前,先画一张大图。这张图就是系统的骨架,后面所有代码、配置、运维,都得围着它转。我见过不少团队,上来就写代码,写到一半发现数据流不通,或者模块耦合太紧,最后只能推倒重来。嗯,咱们别犯这个错。

2.1 整体架构图:三层模型

我把整个系统拆成三层:数据层、策略层、执行层。每一层各司其职,层与层之间通过消息队列解耦。这样做的好处是,哪一层出了问题,我可以单独重启、升级,不影响其他层。

下面这张 SVG 图,是我亲手画的。它清晰地展示了数据从交易所原始行情,到策略决策,再到订单下达到交易所的完整路径。你盯着看两分钟,后面讲的所有内容,都能在这张图上找到位置。

跨交易所套利系统 - 三层架构图 数据层 (Data Layer) WebSocket 行情 REST API 补数 深度数据聚合 Kafka 消息总线 策略层 (Strategy Layer) 价差计算引擎 信号生成器 风控过滤器 Redis 执行层 (Execution Layer) 订单管理模块 交易所网关 对冲/锁仓 日志 数据流方向:交易所 → 数据层 → 策略层 → 执行层 → 交易所

这张图里,我特意把 Kafka 和 Redis 画了出来。为什么?因为它们是整个系统的「血管」和「大脑」。Kafka 负责把数据从一层传到另一层,Redis 负责存一些高频访问的状态,比如当前持仓、最新价差。我在项目中遇到过,有人把订单状态也放 MySQL,结果查询太慢,导致重复下单。后来全换 Redis 了,问题解决。

2.2 模块划分:每个模块的职责

架构图看完了,咱们再深入一层,看看每个模块具体干什么活。我按层来讲,这样你心里有数。

2.2.1 数据层模块

  • WebSocket 行情订阅器:负责跟交易所保持长连接,接收实时 tick 数据。我建议每个交易所至少开两条连接,一条主用,一条备用。曾经有一次,币安的一条线路挂了,备用线自动顶上,系统零中断。
  • REST API 补数器:WebSocket 偶尔会丢数据,或者断线重连后有缺口。这个模块每隔 5 秒拉一次最新行情,做数据校验和补全。
  • 深度数据聚合器:把不同交易所的订单簿数据,统一成标准格式。比如币安用 level2,OKX 用 depth,聚合器把它们转成统一的「买一卖一」结构。
  • Kafka 生产者:把清洗后的数据,按 topic 发送到消息队列。我习惯按交易所+交易对来分 topic,比如 topic_binance_btcusdt

2.2.2 策略层模块

  • 价差计算引擎:从 Kafka 消费数据,实时计算两个交易所之间的价差。公式很简单:spread = ask_price_A - bid_price_B。但要注意,得考虑手续费和滑点。
  • 信号生成器:当价差超过预设阈值(比如 0.1%),就生成一个交易信号。信号里包含:买入交易所、卖出交易所、数量、方向。
  • 风控过滤器:这是我最重视的模块。它会检查:当前持仓是否超限?账户余额够不够?最近有没有异常交易?全部通过,信号才往下发。
  • Redis 状态缓存:存一些高频读写的状态,比如「当前价差」、「上次交易时间」、「账户余额快照」。读写速度要求在 1ms 以内。

2.2.3 执行层模块

  • 订单管理模块:接收策略层的信号,生成具体的限价单或市价单。它会维护一个订单状态机:新建→已提交→部分成交→完全成交→已取消。
  • 交易所网关:封装各个交易所的 API 差异。比如币安用 order.new,OKX 用 place_order。网关统一暴露一个 submit_order() 接口。
  • 对冲/锁仓模块:如果套利方向是「买A卖B」,但 B 的订单没成交,这个模块会启动对冲逻辑,防止单边风险。
  • 日志与监控:所有订单、成交、错误,都记录到本地文件和监控系统。我习惯用 ELK 做日志分析,出了问题能快速定位。

核心原则:每一层只做自己的事。数据层不管策略,策略层不管执行。这样你才能独立升级、独立测试。

2.3 数据流设计:从 tick 到订单的完整旅程

光有模块还不够,你得知道数据是怎么在模块之间流动的。我画个时序图,你跟着走一遍。

  1. Step 1:交易所 A 和 B 同时推送 tick 数据。WebSocket 订阅器收到后,立即发给 Kafka。
  2. Step 2:价差计算引擎从 Kafka 消费数据。它拿到 A 的卖一价和 B 的买一价,算出价差。比如 spread = 50000.1 - 49990.0 = 10.1 USDT
  3. Step 3:信号生成器检查阈值。假设阈值是 5 USDT,10.1 > 5,生成信号:{action: "buy_A_sell_B", quantity: 0.1, price_A: 50000.1, price_B: 49990.0}
  4. Step 4:风控过滤器收到信号。它查 Redis,发现账户余额够,持仓没超限,放行。
  5. Step 5:订单管理模块收到放行信号。它调用交易所网关的 submit_order(),在 A 下买单,在 B 下卖单。
  6. Step 6:交易所返回订单 ID。订单管理模块更新状态,记录日志。

你看,整个过程不到 50 毫秒。如果哪个环节慢了,比如 Kafka 消费延迟,或者 Redis 查询超时,套利机会就溜走了。我在项目中遇到过,有一次 Redis 连接池配小了,导致风控查询排队,延迟从 2ms 飙升到 200ms,直接亏了一笔。后来我把连接池从 10 调到 50,再也没出过问题。

避坑指南:我曾经在数据流里加了一个「去重模块」,因为 Kafka 偶尔会重复投递消息。结果去重逻辑写错了,把正常消息也过滤掉了,导致策略一直不触发。后来我改用 Kafka 自带的 enable.idempotence=true,配合 Redis 做幂等性校验,才彻底解决。

2.4 关键数据表设计

最后,我列一下系统里最核心的几个数据表/结构。你设计数据库或 Redis key 时,可以参考。

数据名称 存储位置 关键字段 更新频率
实时行情 Kafka topic exchange, symbol, bid, ask, timestamp 100ms/次
价差快照 Redis Hash pair, spread, threshold, last_update 每次价差变化
订单状态 Redis + MySQL order_id, status, filled_qty, avg_price 每次状态变更
账户余额 Redis exchange, asset, free, locked 每 10 秒刷新
交易日志 Elasticsearch time, action, symbol, profit, error 每次交易

嗯,这张表你保存好。后面写代码、配监控,都得用到它。尤其是订单状态,我建议你用 Redis 做主存储,MySQL 做持久化备份。这样既快又安全。

好了,架构设计这块就讲这么多。你把这套三层模型吃透了,后面写代码就是填空的事。记住,架构是骨架,数据流是血液,两者缺一不可。