Learning
VOL. VII · NO. 92 · OTC Derivatives · 19 JUL 2026

Communication Architecture

OTC 衍生品 · 19 JUL 2026 · 19 min read · 3,056 words
· · ·

ODTS-43: 系统通信架构——七种协议 + 一个元协议

目标读者:想要理解 CICC OTC 衍生品系统中 15+ 个服务如何”对话”的 BA/PM

数据来源:eds-web-app、hedging-as、eds-utility、tradedesign、odyssey 各项目的代码扫描 + 配置文件

一句话结论:这不是一个微服务架构,这是一个协议拼盘(protocol polyglot)——7 种通信协议 + 1 个元协议(tradedesign)共存的架构反映了 10 年演进过程中”每一代人用当时最好的方案”的历史事实。

相关文档:ODTS-49 详细解释了 tradedesign 协议注册中心。


概述

CICC 的 OTC 衍生品系统有 15+ 个独立服务进程。它们之间的通信不是通过统一的 API 网关或 Service Mesh,而是通过 7 种不同的协议,每种协议服务于特定的通信场景。

但在所有 7 种协议之下,存在一个第零协议——tradedesign:一个 1,709 个 Proto 文件组成的消息契约仓库,定义了所有服务之间交换的每一条消息的精确格式。详见 ODTS-49

┌─────────────────────────────────────────────────────────────┐
│  第零层:tradedesign 协议注册中心(15,014 commits)        │
│  定义 37 个子系统的全部消息格式(1,709 个 .proto 文件)    │
├─────────────────────────────────────────────────────────────┤
│                   7 种通信协议                              │
│                                                             │
│  ActiveMQ  → 服务间事件总线(Protobuf 编码)               │
│  MINA+PB   → 客户端-服务端实时双向通信(核心业务操作)    │
│  Redis RPC → 后端服务间的同步调用                          │
│  ZeroMQ    → 行情数据实时推送                              │
│  Kafka     → 异步数据流(对外系统接口 + CDC)             │
│  REST      → Web API + 文件操作 + 遗留系统桥接            │
│  Feign     → Odyssey 微服务间调用                         │
└─────────────────────────────────────────────────────────────┘

关键事实:这不是过度工程。每种协议解决的是不同的通信需求——从毫秒级的行情推送到小时级的批处理数据提取,不存在”一个协议通吃所有场景”的方案。而 tradedesign 作为元协议,确保了无论底层是 ActiveMQ、MINA 还是 REST,消息的格式定义始终来自同一个权威来源。


协议一:ActiveMQ(JMS)——服务间事件总线

作用

ActiveMQ 是系统的中枢神经。当 eds-web-app 创建一个合约、更新一个参数、完成一笔交易时,它不直接调用其他服务,而是向 ActiveMQ Topic 发布一条消息,让感兴趣的订阅者自己拉取。

技术配置

broker: failover:(tcp://192.168.148.121:61616)
stomp:  192.168.148.94:61613
环境隔离: 所有 Topic 以数据库用户名作为前缀(如 EDS_DEV-)

关键消息流

Topic生产者消费者传递内容业务含义
{DB}-eds-model-parametereds-web-app (MqSender)hedging-as模型参数 ProtoBuf通知定价引擎:合约/参数有变更,请重新计算
{DB}-eds-hedging-datahedging-as (HedgingEntry)eds-web-app → UI对冲结果 ProtoBuf定价引擎把计算完的对冲结果发回给 UI
{DB}-eds-refresheds-web-appSTOMP → Web UIEdsRefreshNotify告诉浏览器:数据变了,刷新视图
{DB}-risk_ruleeds-web-apphedging-asTextMessage (ID)风控规则变更,请刷新缓存
{DB}_AppRefreshTopiceds-web-apphedging-as (ClosePriceSubscriber)刷新类型收盘价更新,通知定价引擎
{DB}-CurrencyRateRefreshTopiceds-web-apphedging-asRefreshType汇率更新通知

核心代码

eds-utility/src/com/cicc/activeMQ/MqSender.java 封装了发送逻辑:

// 参数通知(核心消息)
sendNotifyMessage(type, deskId, uuid)
   → BytesMessage with PbMessageHead.Message (protobuf编码)
   → 属性: deskId, uuid, type=BOOKING_AS

// 刷新通知
sendRefreshByDeskEntity(deskEntityId)
   → EdsRefreshNotify protobuf

// CGM通知
sendCGMNotifyMessage(...)
   → 同上格式,用于CGM模块

消费者端由 HedgingAS.java 启动时初始化:

HedgingEntry.getInstance()
    .setSender(new Sender(ActiveMQ))
    .setSubscriber(new RiskRuleSubscriber())
    .setSubscriber(new ClosePriceSubscriber())
    .setSubscriber(new CurrencyRateSubscriber())

为什么用 Topic 而不是 Queue?

观察发现所有 ActiveMQ 通道都使用 Topic(发布订阅模式),而非 Queue(点对点模式)。这意味着:

  1. 一个消息可以被多个消费者同时消费
  2. 如果消费者不在线,消息会丢失(非持久订阅)
  3. 没有消息堆积风险——代价是消费者重启后可能错过中间的消息

这适合”状态同步”场景——系统不需要保证每一条消息都被处理,但需要保证”当前状态”是正确的。


协议二:MINA + Protobuf(TCP)——实时双向通信

作用

这是 CICC OTC 系统最核心的通信模式——所有的业务操作(开仓、平仓、询价、风控计算)都是通过这个通道完成的。它不是 HTTP 请求-响应模式,而是客户端和服务端之间建立的持久 TCP 连接,双向发送 Protobuf 编码的消息。

为什么需要专用 TCP 协议?

在 2015-2018 年系统设计时,REST/HTTP 的吞吐量和延迟不足以支持交易系统的实时交互需求。MINA(Java 高性能 NIO 框架)+ Protobuf(紧凑二进制序列化)的组合在当时是金融系统的标准选择——类似国内其他券商的定制的 TCP 协议。

架构

Web UI (odyssey前端 / new-edsweb)

    │  MINA TCP (Protobuf编码)

AccessApp (网关层, 127.0.0.1:52277)

    │  MINA TCP (Protobuf编码, 服务端网关)

hedging-as (主要服务端, 192.168.166.111:54000)

    │  Redis RPC (见协议三)

eds-web-app (BookingApp, 辅助后端)

服务端配置

comm.properties 中定义了三种协议编码器:

clienttype1.codec=ProtoBufCodecFactory          (UI客户端)
clienttype2.codec=MessageDecorationProtobufServerFactory  (后端服务网关)
clienttype3.codec=ProtoBufCodecFactory          (GUI服务)

每个客户端类型使用不同的编码器,因为通信的消息格式不同(UI 的消息需要额外的路由头部字段)。

消息路由

每个 MINA 消息都通过 GatewaySessionManager 路由。这是一个ConcurrentHashMap<String, GatewaySession>,key 是 session ID,value 包含了 session 的元数据和输出通道:

GatewaySessionManager.sendMD()            // 发送市场数据
GatewaySessionManager.sendDynamicToBookingAs()  // 发送动态请求到定价引擎
GatewaySessionManager.putMessage()        // 存入消息队列

这个架构的代价

这个定制的 TCP 协议虽然性能好,但代价很高:

  1. 调试困难:不能像 REST 那样用 curl 或浏览器测试,必须有专用的客户端库
  2. 连接管理:长连接需要心跳、重连、会话恢复逻辑
  3. 版本兼容:Protobuf schema 的升级需要所有客户端同步更新
  4. 新手学习曲线陡峭:新开发者需要理解 MINA 的 IoSession、IoHandler、ProtocolCodecFactory 等概念

这也是后来 odyssey 微服务转向 REST/Feign 的原因之一。


协议三:Redis RPC(Linda 框架)——后端服务同步调用

作用

当 eds-web-app 需要对冲引擎计算某个合约的风险指标,或者 hedging-as 需要把日终风控结果写回数据库时,它们通过 Redis RPC 通信。这是一个轻量级的类 RMI 框架,使用 Redis 作为注册中心和传输层。

配置

rpc.properties:
  namespace=dev1
  redisHost=192.168.148.177
  redisPort=6379
  password=ImsGwOms
  database=1

服务接口

接口提供方调用方典型方法
HedgingRpcServicehedging-aseds-web-appcalcVolatility(), runEodRiskCheckByDeskEntityId(), refreshContract(), refreshParameter()
BookingServiceeds-web-apphedging-assetEodRiskCheckResult(), updateContract()

有趣的回调模式

eds-web-app 调用 hedging-as.calcVolatility()

              hedging-as 计算结果

              hedging-as 回调 BookingService.setEodRiskCheckResult()

              eds-web-app 收到结果

这是一个双向 RPC——调用链上的服务可以互相调用,而不是传统 Client → Server 的单向模式。因为 hedging-as 计算完后需要把结果持久化,而持久化的入口在 eds-web-app 的数据层。

初始化

// hedging-as 启动时
RpcEntry.initServer(SenderType.HEDGING_AS)
    .register(HedgingRpcService.class, new HedgingRpcServiceImpl())
    .initClient(SenderType.HEDGING_AS)
    .register(BookingService.class)
    .startUp();

// eds-web-app 启动时
// 类似的初始化,方向相反

协议四:ZeroMQ(PUB/SUB)——行情数据推送

作用

ZeroMQ 专门用于市场数据(行情)的实时推送——这对 OTC 衍生品来说不是实时交易所行情,而是计算后的中间价、估值、对冲持仓等需要推送到 UI 的数据。

架构

hedging-as (行情计算服务)

    │ ZMQ PUB (topic + protobuf bytes)

┌─────────────────────────────────────┐
│  ZmqServer (PUB socket, singleton)  │
│  zmqaddress: tcp://192.168.162.197:5510  │
│  zmqSnapaddress: tcp://192.168.162.197:5501  │
└─────────────────────────────────────┘

    ├──→ Web UI (订阅者1)
    ├──→ Web UI (订阅者2)
    └──→ ...

主题格式

C^ODTS^MNT^{exchId}^{contractId}^
│  │    │       │         │
│  │    │       │         合约ID
│  │    │       交易所ID
│  │    监控类型 (Monitor)
│  OTC 衍生品
类别前缀

数据内容

MQPushService.pushContractMonitor() 构建一个 CICCDataFeed protobuf 消息,包含合约持仓、盯市盈亏、保证金等实时计算数据。

ZMQ 的使用表明系统对行情推送有低延迟要求——ActiveMQ 的 JMS 协议在消息吞吐量和延迟上不如 ZMQ 的原生 PUB/SUB 模型。


协议五:Kafka——异步数据流

作用

Kafka 是系统中最晚引入的通信协议(约 2023 年后),用于与外部系统的数据交换和数据库变更捕获(CDC)。

配置

kafka.host=10.21.237.13:9364,10.21.237.13:9365,10.21.237.13:9366
kafka.security_protocol=SASL_PLAINTEXT
kafka.sasl_mechanism=SCRAM-SHA-256

数据流

Kafka Topic方向数据内容
CICC_ODTS_CONTRACT_POSITION_TOPICeds-web-app → 外部系统合约持仓快照 (Protobuf)
odts.eod.deveds-web-app → 外部系统OTC 持仓调整指令
margin_report_doneeds-web-app → 外部系统保证金报告完成通知
ofa-linear-order-topic-deveds-web-app → PB 系统PB 订单数据 (JSON)
ofa-linear-contract-generateeds-web-app → PB 系统合约生成事件
ofa-linear-dma-account-import-deveds-web-app → PB 系统DMA 账户导入
riskdata-restricted-list外部系统 → eds-web-app受限黑名单数据
CICC_FE_PB_NOTES_POSITION_TOPIC外部系统 → eds-web-appPKS 票据持仓同步
CICC_PB_HEDAGE_POSITION外部系统 → eds-web-appPKS 对冲持仓同步
CICC_PB_HEDAGE_POSITION外部系统 → eds-web-appPKS 对冲持仓同步
odts-cdc-*外部系统 → eds-web-app数据库 CDC 事件(TA资质等)

为什么引入 Kafka?

Kafka 解决了 ActiveMQ 的两个问题:

  1. 消息持久化:Kafka 的日志结构保证了消息不丢失,消费者可以回溯消费
  2. 外部系统集成:ActiveMQ 是内部防火墙后的,Kafka 可以和外部系统(如 PB 系统、PKS)共享

协议六:REST(HTTP)——Web API 层

作用

REST 是系统的用户可见层——前端(new-edsweb、odts-linear-web)通过 REST API 与后端交互。

eds-web-app REST 端点

eds-web-app 在 localhost:8087 暴露约 20+ 个 @RestController

Controller路径前缀功能
ClosePriceController/closePrice收盘价管理
RiskController/risk风险分析
ReportController/report报表(中证、做市、估值)
SacReportController/sacReportSAC 报送审核
ValProcessController/valProcess估值处理
PBRateConfigManagerController/pbRateConfigManagerPB 利率配置
GeminiMailController/geminimail邮件发送

edsWeb(旧)REST 端点

旧系统 edsWeb 暴露的 REST 接口被 Odyssey 微服务通过 Feign Client 调用:

端点调用方用途
POST /sacReport/submitSacReportodyssey-report-processing-serviceSAC 报告提交
POST /sacReport/precheckSacReportDataodyssey-report-processing-serviceSAC 预检查
/closePrice/*HttpClientHelper收盘价查询

协议七:Feign(Spring Cloud)——Odyssey 微服务间调用

作用

在 Odyssey 微服务体系内,服务间使用 Spring Cloud OpenFeign 进行声明式 REST 调用,通过 Nacos 做服务发现和配置管理。

客户端定义

Feign Client目标服务所属微服务
FuxiAuthLoginFeignClientodyssey-fuxi-auth-logininternal-gateway
FuxiAuthVerifyFeignClientodyssey-fuxi-auth-verifyinternal-gateway
OdtsWebClientedsWeb(遗留系统)report-processing-service
DemoFeignfuxi-job-demo-servicecash-manager, quotation

新旧桥接模式

Odyssey 到遗留系统(edsWeb)的调用是整个架构中最复杂的集成点。以 SAC 报告提交流程为例:

用户操作(Odyssey UI)

    │ HTTP

odyssey-report-processing-service

    │ Feign (OdtsWebClient)

edsWeb (旧系统 / 遗留 REST API)

    │ MINA/Protobuf (DynamicProtoBufSacClientService)

后端 (hedging-as 或 sac-as)

这意味着 Odyssey 的前端每次 SAC 操作都经过了三层协议转换:

  1. HTTP → 2. REST (Feign) → 3. MINA/Protobuf

每一层转换都增加了延迟、错误风险和调试复杂度。


通信架构演进时间线

2015-2016 (系统奠基)
│  ActiveMQ + MINA/Protobuf + Redis RPC
│  "一切用定制的 Java 协议,性能优先"

2017-2019 (功能扩张)
│  加入 ZeroMQ 处理行情推送
│  REST 出现,但主要用于文件上传和报表查询

2020-2022 (前端独立化)
│  REST 成为前端 (Vue) 的主要接口
│  但后端之间的"硬核操作"仍然走 MINA/Protobuf

2023-2025 (Kafka + Odyssey)
│  引入 Kafka 处理外部系统数据交换
│  Odyssey 使用 Feign + Nacos
│  新旧系统通过 edsWeb REST API 桥接

2026 (现状)
│  七种协议并存
│  没有"替换旧协议"的计划——每种协议仍有其不可替代的场景

外部系统通信映射 / External System Protocol Map

上述七种协议描述了 ODTS 内部服务之间的通信。在与外部系统交互时,使用的协议和模式有所不同:

外部系统协议模式方向数据格式关键服务
IPMP 资金划拨HTTP异步+轮询双向XMLOdyssey cash-manager-service
Cert App 数字证书HTTP REST同步请求-响应双向JSONeds-web-app (DocRest接口)
Doc Agent 文档生成HTTP REST同步请求-响应请求→生成←JSONeds-web-app / odyssey
JUMS 通知服务HTTP REST同步请求单向→JSONOdyssey cash-manager-service
HCP 对象存储HTTP (PUT/GET)同步双向二进制eds-web-app (HcpHelper)
SacAS 监管报送HTTP同步批量正向→Protobufeds-web-app (SacReportAction)
Dipper / PulsarProtobuf over TCP异步数据同步单向→Protobuf → CSV数据平台
SFTP 文件交换SSH/SFTP文件推送+拉取双向CSV / XML批处理脚本

协议叠加 — 以结算流程为例

一笔结算操作跨越了多种协议:

运营点击"批准转账" (HTTP REST)
  → eds-web-app / odyssey (Feign)
  → cash-manager-service 组装 IPMP XML
  → HTTP POST 到 IPMP (XML)
  → 轮询 IPMP 状态 (HTTP GET, XML)
  → 转账完成
  → cash-manager-service 调用 JUMS (HTTP REST, JSON)
  → JUMS 发送邮件+SMS通知客户和运营
  → 更新 CashTransfer 状态 (JDBC/数据库)
  → 通过 ActiveMQ 通知其他服务状态变更

一笔操作涉及 5 种不同的协议,这也是为什么”全链路追踪”在 ODTS 中几乎不存在——没有一种工具可以横跨 HTTP、XML-over-HTTP、JSON-over-HTTP、JDBC 和 ActiveMQ 做端到端追踪。

外部系统的错误处理模式

外部系统超时配置重试策略幂等性
IPMPconnectTimeout=30000, readTimeout=60000状态轮询自动重试通过 ftOrderNo 保证幂等
Cert App默认 HTTP 超时5 次重试 + 退避通过 signatureId 保证
Doc Agent默认 HTTP 超时3 次重试无(需要运营确认生成结果)
JUMS默认 HTTP 超时1 次重试+回退可能存在重复通知
SacAS批量提交无超时人工重试通过 bussiDataSeq 保证

运维启示

故障排查的复杂度

当系统出现问题时,问题可能发生在七种协议的任意一层:

症状可能的原因
UI 数据显示”等待中”ActiveMQ 消息丢失(非持久 Topic,消费者不在线)
某个合约的定价不更新Redis RPC 调用超时或服务端线程池满
行情不刷新ZMQ PUB 连接断开或 socket 缓冲区满
结算数据不一致Kafka 消费偏移提交失败导致重复/丢失消费
页面加载慢REST API 的数据库查询慢(与协议无关)

为什么没有统一?

对于外部观察者,最自然的问题是:为什么不用 gRPC 统一所有通信?

答案在与这些协议的引入时间:

协议引入时间当年的”最佳选择”
MINA + Protobuf~2015gRPC 还没发布(2016 年才出 1.0)
ActiveMQ~2015当时 Java 生态最成熟的消息中间件
Redis RPC~2016简单的服务间调用,无需注册中心
ZeroMQ~2017当时最快的内存级消息队列
REST~2018前端独立化的必然选择
Kafka~2023外部系统集成需求驱动
Feign~2024Odyssey 带来 Spring Cloud 生态

这是一个10 年演进的时间胶囊。每一层都是当时最合理的选择,没有任何一代人做出了”错误”的决定。


十、这些协议出问题时的业务代价

ActiveMQ Topic 重启丢消息——EOD 风控空跑

某次 hedging-as 在 EOD 开始时重启(运维升级),重启期间 ActiveMQ 发了 3 条模型参数变更通知。因为 Topic 是非持久订阅,重启后的 hedging-as 没有收到这 3 条通知——它不知道某个雪球产品的波动率参数在下午被更新了。

EOD 批处理:
  hedging-as 用旧的波动率参数跑完了全部定价
  → EOD 风控报告显示所有风险指标"正常"
  → 实际参数已经变了 3 小时,风险值应该不同

业务影响:
  → 第二天交易员用 EOD 报告做开盘决策——基于错误的数据
  → 直到下午才发现"昨天的 EOD 报告参数对不上"
  → 重新跑 EOD 花了 3 小时(资源受限,白天不能跑全量)
  → 交易台在错误的风险视图下做了半天交易

技术反思:
  Topic 的非持久订阅是"设计时就知道的取舍"。
  但业务方不知道——他们以为系统重启后一切正常。
  PM/BA 在评审这种架构选择时应该问:
  "Topic 丢消息的概率多大?业务上能接受吗?"

MINA 连接耗尽——交易员无法下单

MINA 的长连接模式要求客户端连接数在服务端设置的线程池范围内。某次新前端上线时,连接数从 50 翻到了 300。hedging-as 的 MINA IoHandler 线程池(默认 128)被打满。

现象:
  → 部分交易员的客户端连不上
  → "卡在登录界面"或者"点击交易按钮没反应"
  → 但不是所有交易员都受影响——看谁的 session 先被挤掉

业务影响:
  → 15 分钟内有 3-5 个交易员无法操作
  → 此时市场有快速变动,无法及时减仓
  → 后续排查发现是连接数超限,紧急重启 + 扩容线程池
  → 15 分钟的"部分不可用"对交易台来说非常漫长

技术原因:
  这个线程池参数(128)是 2017 年设定的。
  没有监控来告警连接数的增长趋势。
  新前端上线没有做连接数压测。

Redis RPC 超时——跨服务调用雪崩

Redis RPC 在后端服务之间做了大量同步调用。某次 Redis 实例高负载(因为有另一个团队在同一个 Redis 实例上跑了一个大查询),RPC 调用的响应时间从 1-2ms 涨到了 200-300ms。

雪崩链条:
  eds-web-app 调用 hedging-as.calcVolatility() → Redis RPC 超时(300ms)

  eds-web-app 的请求线程被阻塞等待

  阻塞线程数超过 tomcat 连接池

  新的交易簿记请求被拒绝(503)

  连锁效应:所有依赖 eds-web-app 的前端也无法操作

业务影响:
  → 交易簿记暂停约 10 分钟
  → 排查发现 Redis RPC 的超时设置是 5 秒——太长了
  → 一个 RPC 慢导致了整个系统的级联故障

教训:
  同步 RPC 调用是架构中最危险的耦合。
  一个服务慢 → 调用方也慢 → 调用方的调用方也慢。
  这是"通信协议选择"后面隐藏的架构风险。

数据目录

通信配置:
  odts1/eds-web-app/config/comm.properties         → ActiveMQ, Kafka, ZMQ, MINA 配置
  odts1/eds-web-app/config/rpc.properties           → Redis RPC 配置
  odts1/hedging-as/config/comm.properties           → ActiveMQ, ZMQ, MINA 配置
  odts1/hedging-as/config/rpc.properties            → Redis RPC 配置
  odyssey/odyssey-report-processing-service/.../application.yml → Nacos + Feign

核心代码:
  odts1/eds-utility/src/com/cicc/activeMQ/          → ActiveMQ 封装
  odts1/hedging-as/src/com/cicc/communication/      → MINA/Protobuf + Gateway
  odts1/hedging-as/src/com/cicc/service/infoQutation/zmq/push/ → ZMQ 行情推送
  odts1/hedging-as/src/com/framework/rpc/           → Redis RPC 接口定义
  odts1/eds-web-app/src/com/cicc/wsmq/              → STOMP WebSocket 推送

Kafka 代码:
  odts1/eds-web-app/src/com/cicc/kafka/             → 生产者/消费者实现
  odyssey/odyssey-report-processing-service/.../kafka/ → Odyssey Kafka 监听器