System Architecture Overview
ODTS 25 — 系统架构总览:45+ 个项目组成的生态
本文件需与 ODTS-49(协议注册中心)、ODTS-43(通信架构)、ODTS-48(监控)等结合阅读。 这里是全景图,其他文件是细节。
为什么有 45+ 个项目?
投资银行的场外衍生品系统不是一个单一应用。它是一个由 45 个以上项目组成的生态系统,通过 Protobuf TCP、HTTP REST、文件和数据库进行通信。每个项目有特定的职责。这不是过度设计——每个服务对应一个独立的业务领域,比如结算、确认、保证金、抵押品管理、报告、会计等——它们各自有不同的发布节奏和团队。
万米高空视角 — 修正版
协议层(tradedesign — 37 个子系统的 Proto 契约仓库)
1,709 个 .proto 文件定义全部通信格式
│
┌──────────┼──────────┐
│ │ │
┌─────────▼──┐ ┌───▼────┐ ┌──▼────────────┐
│ 前台/交易台 │ │ 中台 │ │ IMS/GWMS │
│ (5 个前端) │ │(hedging │ │ (内部管理系统) │
│ new-edsweb │ │ -as, │ │ │
│ odts-option │ │ eds-web │ │ accessapp │
│ eds-rm-web │ │ -app, │ │ (网关桥接) │
│ new-quota │ │ eds- │ │ │
│ fuxi-fe │ │ price │ │ gwms/protos │
└──────┬──────┘ │ -server)│ └──────┬────────┘
│ └────┬────┘ │
│ │ │
┌──────▼──────────────▼──────────────▼───────┐
│ 数据层 │
│ Oracle RAC (EDS schema) │
│ + 共享文件服务器 (CSV/XML) │
│ + 消息队列 (ActiveMQ / Kafka) │
└──────┬──────────────┬───────────────────────┘
│ │
┌──────▼──────┐ ┌────▼──────────────────────┐
│ 后台服务群 │ │ 外部集成 │
│ settlement │ │ │
│ confirma- │ │ IPMP (资金划拨平台) │
│ tion │ │ Cert App (数字证书/签章) │
│ margin │ │ Doc Agent (文档生成) │
│ collateral │ │ JUMS (通知服务) │
│ accounting │ │ HCP (对象存储) │
│ regulatory │ │ Dipper (数据同步) │
│ report │ │ SacAS (监管报送) │
│ │ │ │
│ │ │ custodian-bank │
│ │ │ clearing-house │
│ │ │ DTCC/SFC │
│ │ │ CATS (commodity gw) │
│ │ │ oss-image (文件服务) │
└─────────────┘ └────────────────────────────┘
两个世界
世界 1:hedging-as (JFinal + Protobuf + 直接 SQL)。这是 2016-2017 年构建的原始系统。优点:快(无 ORM 开销,Protobuf 序列化高效)、完整(定价、风险、保证金都在一处)、经过实战检验(2017 年起在生产环境运行)。缺点:JFinal 是小众框架(难招人)、到处是原始 SQL 字符串(无编译时检查)、紧耦合(风险计算直接调用 DAO)、无测试基础设施(大多数类没有单元测试)、单体架构(全部在一个 WAR 文件中)。
世界 2:eds-web-app (Spring Boot + MyBatis + REST)。这是正在构建的新系统,旨在取代 hedging-as。优点:Spring Boot(标准框架,容易招人)、REST API(服务可独立部署)、关注点分离更好、测试支持更好、有依赖注入。缺点:功能不完整(某些功能只在 hedging-as 中有)、过渡期需要维护两个系统、定价引擎的数值结果不同、迁移缓慢(5+ 年仍未完成)。
外部系统生态 / External System Landscape
ODTS 对接了 8 个以上的外部系统,按业务目的分为四类:
| 类别 | 系统 | 协议 | 数据方向 | 关键业务 |
|---|---|---|---|---|
| 资金结算 | IPMP 资金划拨平台 | XML over HTTP | → 转账指令, ← 结果轮询 | 银行间资金划拨、CNAPS 转账 |
| 文档与法律 | Cert App 数字证书 | HTTP REST | ↔ 签名请求/响应 | 合同签署、数字证书管理 |
| Doc Agent 文档生成 | HTTP REST | 请求→ 生成← | ISDA/NAFMII/CSA 文档生成 | |
| 通知与存储 | JUMS 通知服务 | HTTP REST | → 模板化通知 | 邮件/SMS 通知(Margin Call 等) |
| HCP 对象存储 | HTTP REST | → 文件上传 | 合同 PDF、报表、附件的持久化 | |
| 监管与数据 | SacAS 监管报送 | Protobuf over HTTP | → 交易报送 | 向 SAC/ISDA/NAFMII 报送数据 |
| Dipper / Pulsar | Protobuf | → 数据同步 | 从生产同步到定稿数据库 | |
| SFTP 文件交换 | SFTP | ↔ CSV/XML 文件 | 与结算行、托管行交换文件 |
这些系统通过不同的集成模式与 ODTS 对接:
- 同步请求-响应:Cert App, Doc Agent, JUMS, HCP — HTTP REST,超时重试
- 异步提交-轮询:IPMP — 提交转账后轮询获取最终状态
- 文件推送:Dipper/Pulsar — Protobuf → CSV 转换后写入目标数据库
- 文件交换:SFTP — 定时上传/下载 CSV 文件
详见 ODTS-52 的可视化流程图。
通信方式
hedging-as 与 eds-web-app 之间: 两者读写同一个 Oracle 数据库(EDS schema)。eds-web-app 簿记交易 → 写入数据库 → hedging-as 从数据库读取来定价。没有实时通知——轮询 (polling) 驱动。
前端与后端之间: new-edsweb → hedging-as 使用 HTTP REST (JSON);new-edsweb → eds-web-app 使用 HTTP REST (JSON);odts-option-web → hedging-as 使用 HTTP REST (JSON);edsWeb (JSP) → hedging-as 使用同 JVM 内的直接 Java 调用。
服务之间: 大多数服务使用基于文件的集成——eds-web-app 将 CSV/XML 文件写入共享目录,settlement-service 轮询读取这些文件。没有 API 调用,没有消息队列。部分服务使用数据库级集成——服务 A 写入表 X,服务 B 从表 X 读取。同样没有实时通知。
这是一个显著的架构负债 (architectural debt)。 基于文件的集成意味着:没有重试逻辑(文件损坏怎么办?)、没有实时处理(文件每 N 分钟轮询一次)、没有监控(文件到了吗?正在处理吗?)、手工恢复(运维必须重新触发失败的处理)。
通信协议层
系统中有一个常常被忽视但至关重要的组件——协议注册中心(tradedesign)。详见 ODTS-49。
这是一个”看不见”的系统:没有进程、没有部署、没有告警。但它定义了 37 个子系统之间的全部通信契约。
tradedesign 包含:
- 37 个
PbMessageHead.*文件(每个子系统一个) - 381 行的全局
MSG枚举(消息类型定义) - 1,709 个
.proto文件分布在 50+ 子目录中 - 15,014 次提交(2015-2022),在 2015 年启动,比主后端 eds-web-app(2016-12)早约 15 个月
所有 Java 项目在编译时依赖 tradedesign 的 JAR 包。当 eds-web-app 需要发送一笔交易给 hedging-as 时,请求和响应的格式由 tradedesign 定义。这个”接口先行”的模式在 2015 年前后是领先的。
2022 年之后,新项目(odyssey report-processing、new-edsweb REST API)不再使用 Protobuf,转而使用 REST + JSON。Tradedesign 的 Git 历史停在了 2022 年初。
45+ 个项目实际分类
| 类别 | 数量 | 项目 |
|---|---|---|
| 协议注册中心 | 1 | tradedesign(15,014 commits, 1,709 proto) |
| 后端应用 | 4 | eds-web-app, hedging-as, eds-price-server, sac-report |
| 前端应用 | 7 | new-edsweb, odts-option-web, edsWeb, eds-rm-web, new-quota, fuxi-fe-demo, odts-linear-web (odyssey) |
| 微服务 (odyssey) | 6 | report-processing-service, internal-gateway, cash-manager, quotation-service, ciccchecker, ofareg |
| 网关/桥接 | 2 | accessapp-new (IMS), gateway (protos) |
| 共享库 | 2 | eds-utility (4,444 commits), eds-utilitysrc |
| 基础设施 | 5 | unionLogin (SSO), eva-health, eva-master, oss-image, ofareg-path |
| 外部系统 | 8+ | IPMP (资金划拨), Cert App (数字证书/签章), Doc Agent (文档生成), JUMS (通知服务), HCP (对象存储), Dipper (数据同步), SacAS (监管报送), SFTP (文件交换) |
| 其他系统 | 2 | CATS (大宗商品), bootstrapvalidator (校验库) |
| 构建/工具 | 3 | protobuf-gradle-plugin, diagrams.net, refactor |
| 外部依赖 | 3 | ice, jzmq, lox (ZeroMQ 相关) |
共计:40+ 个内部项目 + 8+ 个外部集成系统
部署架构
生产环境: 2 个 hedging-as 实例(负载均衡)、2 个 eds-web-app 实例(负载均衡)、1 个 Oracle RAC(2 节点)、1 个文件服务器(共享目录用于基于文件的集成)。
UAT 环境: 1 个 hedging-as 实例、1 个 eds-web-app 实例、1 个 Oracle 实例(独立,每周从生产恢复)。
开发环境: 每个开发者有自己的本地环境(Oracle XE、本地文件系统等)。
共享数据库的惨痛教训
2019 年,eds-web-app 的一个开发者改了一个通用字段类型:EDS_CONTRACT.remark 从 VARCHAR2(500) 改为 VARCHAR2(1000)。这个字段在 eds-web-app 的界面中被用于显示交易备注。改完后正常运行。
但一周后 hedging-as 的 EOD 批处理开始随机失败。根因出人意料:
EDS_CONTRACT 表是 hedging-as EOD 批处理的核心输入表
→ remark 字段长度变更触发了 hedging-as 中一个旧 SQL 的隐式转换
→ 该 SQL 用 CHAR 函数截断 remark 到 500 字节
→ 新插入的 remark 超过 500 字节导致 SQL 异常
→ EOD 批处理中断
→ 当天的保证金催缴延迟了 4 小时
后果: 一个简单的字段长度变更导致整个 EOD 流程中断。修复需要回滚字段长度 + 同步两个应用的 SQL 语句。根本问题不是”改字段的人没通知”,而是”两个应用共享同一个表,但一个不知道另一个的 SQL 逻辑”。 如果有独立的数据库和服务边界,这个风险不存在。
45+ 项目的依赖链如何断裂
另一个常见问题:tradedesign 的 proto 文件变更后,所有依赖项目需要重新编译。但开发者不是每次都能同步更新所有项目。
2021 年某事件:
团队 A 修改了 tradedesign 中某个消息类型的字段定义
→ 重新编译并发布了 tradedesign JAR
→ 团队 B(维护 eds-web-app)没有立即更新依赖
→ eds-web-app 继续使用旧版本 tradedesign JAR
结果:
hedging-as(已更新)发送的新格式消息
→ eds-web-app(未更新)反序列化失败
→ 交易同步中断 2 天(直到被发现和修复)
架构教训: 45+ 项目通过共享 proto 契约耦合在一起。tradedesign 的设计意图是”接口先行”,但在没有版本兼容性检查(proto 的 forward/backward compatibility 没有被强制执行)的情况下,接口变更变成了一把双刃剑——它提高了效率,但也创造了隐式依赖链。
部署流程演变
2022 年之前:开发者提交代码到 SVN → Jenkins 构建 → 手动 SCP 复制 WAR 到生产服务器 → 停 Tomcat、替换 WAR、启动 Tomcat → 手动运行迁移脚本 (SQL*Plus) → 手动冒烟测试。问题:手动步骤导致人为错误、无回滚机制、部署期间有停机、迁移脚本有时顺序错乱。
2022 年之后:开发者提交到 Git → GitHub Actions 构建和测试 → 制品推送到 Nexus → Ansible playbook 部署到生产 → 失败时自动回滚 → 自动冒烟测试。仍在进展中的:金丝雀部署 (canary deployment) 未实现、数据库迁移自动化仍使用旧的自定义脚本、功能开关 (feature flags) 未实现。
架构最大的问题
问题 1:所有服务共享同一个数据库。 每个服务读写同一个 Oracle schema。这意味着服务 A 的 bug 可能损坏服务 B 的数据,数据库 schema 变更需要协调所有服务,没有服务隔离(一个糟糕的查询可以让所有人的数据库宕机),无法独立扩展服务(单一数据库瓶颈)。
问题 2:两个引擎,两个真相。 旧的和新的定价引擎给出不同的结果。前台的 P&L = 1,234,567,风险的 P&L = 1,233,890,差异 677 元(0.05%)。这听起来很小,但每天累积,且每次询价 (RFQ) 时交易员和风控都不知道该信哪个数字。
问题 3:基于文件的集成。 结算、确认和报告仍然使用基于文件的集成。事务系统写 CSV 文件,结算服务每 5 分钟轮询一次。没有错误处理(CSV 格式错误怎么办?)、没有重试机制(结算服务宕机怎么办?)、没有监控(文件按时到达了吗?)。
问题 4:没有服务网格 (service mesh)。 服务通过硬编码的 IP 地址和端口发现彼此。配置在属性文件中,而不是配置服务器。负载均衡在生产中由硬件负载均衡器完成,但在 UAT 中没有。
理想架构应该是什么样子
API Gateway
(认证、限流、路由)
│
┌────────┼────────┐
┌──▼──┐ ┌──▼───┐ ┌──▼───┐
│交易 │ │定价 │ │保证金 │
│服务 │◄┤服务 ►│◄┤服务 │
└──┬──┘ └──┬───┘ └──┬───┘
│ │ │
┌──▼───────▼────────▼────┐
│ 事件总线 (Kafka) │
└──┬───────┬────────┬────┘
│ │ │
┌──▼──┐ ┌──▼───┐ ┌──▼───┐
│结算 │ │市场 │ │报告 │
│服务 │ │数据服务│ │服务 │
└─────┘ └──────┘ └──────┘
│
┌──────▼──────┐
│ PostgreSQL │
│ (服务 A) │
└─────────────┘
┌──────▼──────┐
│ PostgreSQL │
│ (服务 B) │
└─────────────┘
理想架构的收益:每个服务拥有自己的数据库(无共享 schema)、服务通过事件总线(而非文件轮询)通信、单一定价服务(一个真相源)、实时通知(保证金催缴、确认)、独立扩展(只扩展需要的服务)、正确的错误处理(死信队列、重试策略)。
为什么没做? 从当前架构迁移到这个理想状态的成本和风险是巨大的。约 35 个项目需要重构,而业务方不愿意资助一个没有可见功能改进的多年迁移项目。
关键文件
| 组件 | 路径 | 备注 |
|---|---|---|
| 主后端 | ~/odts1/eds-web-app/ | Spring Boot, 36,275 commits |
| 定价/风控引擎 | ~/odts1/hedging-as/ | JFinal + Protobuf |
| 协议注册中心 | ~/odts1/tradedesign/ | 15,014 commits, 1,709 proto 文件 |
| 独立定价服务 | ~/odts1/eds-price-server/ | 9,325 commits |
| 监管报送 | ~/odts1/sac-report/ | 1,308 commits, SAC/ISDA/NAFMII |
| 共享库 | ~/odts1/eds-utility/ | 4,444 commits, 82 位作者 |
| 管理前端 | ~/odts1/new-edsweb/ | Vue 2 + Element UI |
| 期权交易 UI | ~/odts1/odts-option-web/ | Vue 2 + KoiUI |
| 风控前端 | ~/odts1/eds-rm-web/ | 147 commits |
| 额度管理 | ~/odts1/new-quota/ | 459 commits |
| 报表微服务 | ~/odyssey/report-processing-service/ | 1,211 commits |
| 新一代前端 | ~/odyssey/odts-linear-web/ | 2,707 commits |
| 数据库迁移 | ~/odts1/eds-web-app/db/EDS/ | Flyway 脚本 |
| 大宗商品 | ~/odts1/CATS/ | Struts2 + Bloomberg |
| SSO 认证 | ~/odts1/unionLogin/ | 统一登录 |
| IMS 网关 | ~/odts1/accessapp-new/ | Netty + Protobuf 桥接 |
| 业务监控 | ~/odts1/eva-health/ | Python, Prometheus |
| 文件服务 | ~/odts1/oss-image/ | Node.js |
| 架构图 | ~/odts1/diagrams.net/ | draw.io 源文件 |
ODTS 架构是一个围绕共享 Oracle 数据库构建的 45+ 项目生态系统(含 8+ 外部集成系统),由 tradedesign 的 1,709 个 Proto 文件定义全部通信契约,有两个给出略微不同答案的定价引擎。它靠基于文件的集成和自定义 shell 脚本维持在一起,而向现代微服务架构的多年迁移进展缓慢——只有 report-processing-service 真正存活了下来。