Files
msgexchange-v2/docs/architecture.md
T
windyboy ca7c36ef38 docs(acm2-75): 精简重构架构文档并对齐两段事务口径
- architecture.md:§1–§7 全文精简重构——图收敛为拓扑、主流程改七步时序+类别差异表、
  约束红线去行话、数据归属去黑话、关键决策按章程瘦身为 D1/D2
- specification.md:INV-17b 补静态参考数据单事务口径
- implementation/reference/README/requirements:对齐扫描谓词与两段事务表述
- AGENTS.md:新增文字纪律与用词归属规则
2026-09-15 13:23:40 +08:00

127 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构
本文写架构约束与归属,不代表能力已实现(可声明性见 [specification.md](specification.md));上线与切流的操作步骤另立规程。历史报文契约以 [SIS 接口规范](legacy/SIS_AODB_RMS-V0.1.md) 和 [XSD](legacy/unisysaodbsis.xsd) 为兼容依据。
## 1. 系统定位与范围
msgexchange-v2 是 OMMS H5 查询系统的消息网关,替换旧版 `msgexchange-api`,收取 CIIMS adapter 信箱中 AODB 下发的 XML 报文:航班动态写入业务数据库、写 Redis 投影供页面查询,并经 Kafka 通知运营航班显示界面;静态参考数据(机场、航空公司、机型、机位等 13 类基础数据与资源状态)写入独立数据表。
功能需求是 [requirements.md](requirements.md) 的十四条用户故事(采集、处理、投递、查询、维护,`US-01``US-14`),运行需求是四条验收(单实例、可观测、测试隔离、切换回退,`OPS-1``OPS-4`)。不生成航班/业务数据类报文,不替代 CIIMS/AODB,不调用 admin-api;完整非目标见同文件「范围与非目标」。
- **主要入口**:轮询共享 MySQL 入站表 `CMINMSGS`,只取处理标记为空的行,按编号升序、每批有上限(`US-01``C-30`)。
- **兼容入口**`POST /cminmsgs/send` 供联调工具把报文写进信箱,与上游投递走同一条处理路径;返回的编号只表示已进信箱,不代表已处理或下游已收到(`US-02`)。
- **查询入口**`GET /all/flights` 返回当前全部动态航班(不含共享航班),读 Redis,与网页客户端同源(`US-12``INV-24`)。
- **出站**:只向 AODB 发参考数据和日计划两类请求,经共享 MySQL 出站表 `COUTMSGS`,由 CIIMS adapter 消费;只保证请求写入信箱,不保证 AODB 收到(`US-09``C-24`)。
- **输出**Kafka 主题 `msg` 发单条航班变更、`schd` 定时发批量最新状态;Redis 存航班投影;静态参考数据表供 admin-api 只读(`US-08``US-13`)。
- **权威**:航班当前态的权威是自有 PostgreSQL(`FLIGHT_SCHD`、资源明细表、`FLIGHT_ROUTE_POINT`;字段含义见 [implementation.md](implementation.md)「航班域」);信箱、Redis、Kafka、展示视图都不是(`INV-11b`)。
- **航班历史**:已结束航班先写入 Elasticsearch 历史库,成功后才从实时数据删除(`US-14``D1`)。
测试环境用 PostgreSQL;生产环境用 PostgreSQL 还是 Oracle 11g 尚未确定,Oracle 适配验证通过前不作支持承诺(生产库选型见 `Q1`)。
监控指标与告警口径见 [reference.md](reference.md)「指标与健康」。
## 2. 总体架构
```text
CIIMS adapter(把 AODB 下发的 XML 写入信箱)
│ 落信
共享 MySQLCMINMSGS(入站信箱)
──────── msgexchange-v2(单活动实例,同进程四组线程) ────────
ingress 收报 processing 处理(流程见第 4 节)
delivery 投递 jobs 作业:回填 / 历史清理 / 记录清理
──────────────────────────────────────────────
├─▶ Kafkamsg / schd ── 运营航班显示界面
├─▶ Redis:航班查询投影
├─▶ 共享 MySQL:COUTMSGS ── 参考数据与日计划请求发 AODB
├─▶ Elasticsearch:航班历史
└─▶ 自有 PostgreSQL:静态参考数据表 ── admin-api 只读
```
四组线程在同一进程、互不调用,协作只经自有 PG 的持久记录交接;HTTP 接口走事件循环,不占这四组线程。重启后各段从记录接着做,不依赖内存进度(`US-01` AC4、`US-03` AC3、`US-10` AC2)。
线程之间不加锁,靠幂等写入:收报按信箱编号只登记一次(`US-01` AC2)、回填只写空标记(`C-5`)、记录清理只删已回填且超过保留期的行(`INV-25`);撞上时后到的一次重试即可。唯一的例外是处理消息的主循环(主泵)与航班历史清理之间要加锁(`INV-18`)。
运行边界:同一时刻只允许一个实例处理消息(`OPS-1`);处理进度只有信箱处理标记一处,停旧启新时未处理的消息由旧系统继续(`OPS-4`);积压、处理失败、投递失败、回填失败各有指标与告警(`OPS-2`)。
技术栈:Kotlin + JDK 25、Micronaut 编译期依赖注入、JDBC 持久化;数据库变更由 Flyway 管理,只作用于自有 PostgreSQL。依赖版本见 `build.gradle.kts`
## 3. 模块职责
| 模块 | 职责与边界 |
|---|---|
| `ingress` | 轮询信箱、登记入队、兼容入口落信(`US-01``US-02`);不解析业务报文。 |
| `codec` | XML 解码,区分非法报文与可修复的解码失败(`US-03`)。 |
| `processing` | FIFO 调度、业务身份绑定与去重、领域决策与落库(`SCHD`/`FLOP`/`FDEL`/`ADFT`/静态参考数据),航班类写 Redis 投影;决策与回填机制见 [implementation.md](implementation.md)「消息、身份与决策」。 |
| `delivery` | 读待发事件投 Kafka:按 `FLID` 保序、`schd` 聚合、失败重试(`US-08``C-29`)。 |
| `jobs` | 回填扫描、航班历史清理,以及 `PROC_STATE``MSG_EVENT``SCHD_SNAP_LOG``REQ_TRACK` 的到期清理;单独线程、不进 FIFO(与主泵的互斥见 `INV-18`),保留期一律见 [reference.md](reference.md)。 |
| `domain` / `config` | 领域状态、事件和决策模型,以及运行参数。 |
| `infra` | 仓储(JDBC/stub)、外部适配器(共享信箱、Kafka、Redis、航班历史存储、AODB 出站)、重试、健康检查与日志;对其他模块只暴露接口。 |
代码入口清单见 [reference.md](reference.md)「模块与代码入口」。
## 4. 主流程
航班动态消息走满全链,其余类别只换其中几步:
1. **收报**:按「处理标记为空」发现信箱行,登记入队(`INV-2b`)。
2. **主泵**:按最小未完成 `MSG_ID` 取队头,解码,按业务身份去重(`INV-3``INV-9`);非法或不支持的报文无副作用,终态留档(`US-03`)。
3. **事务一**:持 `PIPELINE_LOCK`,领域变更与待发事件一起提交(`INV-17b`)。
4. **投影**:写 Redis,写成功才算处理完成(`INV-23`);失败保持未完成、下轮重写投影,业务效果幂等(`US-03` AC3)。
5. **事务二**:处理终态与回填意图一起提交。
6. **回填**:作业把处理标记写回共享信箱(`US-10`);写不上的记录在案并告警。
7. **投递**:读待发事件发 `KAFKA:msg` / `KAFKA:schd`,至少一次、`FLID` 内保序(`INV-10``C-29`);一直失败的记录保留可查并告警(`US-08`)。
其余类别与这条主干的差异:
| 类别 | 与主干的差异 | 完成判据 | 失败时 |
|---|---|---|---|
| 日计划(`DNLD`/`RESP`) | 第 3 步改为每批一个事务,第 5 步在整包完成后 | 整包成功,含 Redis 刷新(`US-07` AC4/AC5) | 不标记已处理,下轮整包重处理(`US-07` AC4 |
| 静态参考数据 | 无第 4 步;一个事务完成落库、终态与回填意图 | 该类落库成功(`US-13` AC2) | 校验不过整类不动,其他类照常(`US-13` AC2 |
| 出站请求 | 不走收报队列:登记新请求并作废同类旧请求,写入 `COUTMSGS` | 请求已写入信箱(`US-09` AC1) | 写入失败下轮重试;交付承诺只到落信(`C-24` |
| 航班历史清理(作业) | 不走消息队列:历史写入成功后物理删除 | 实时数据已删(`US-14` AC3) | 历史写不成功不删,下轮重来(`US-14` AC3 |
出站的后半程:应答按报文类型匹配等待中的请求,发错或迟到的不更新数据、记录后跳过;超过时限未等到应答,标记超时;`EROR` 定位到本系统发出的请求,标记失败并告警(`US-09`)。
机制细节各有归属:扫描谓词、分派与事务边界、回填四结果、保序与 `schd` 聚合见 [implementation.md](implementation.md)。
## 5. 必须保持的约束
以下约束不得违反;完整定义与验证方式见 [specification.md](specification.md)
- 只跑一个实例,航班当前态只有一个写入方:信箱读取不加锁,主泵与历史清理互斥(`OPS-1``PRE-5``INV-18`)。
- 按顺序处理、只处理一次:每次只取编号最小的未完成消息,重复扫描、失败重处理与兼容入口并发都只登记一次、生效一次(`INV-2b``INV-3``INV-9`);不丢消息依赖「编号即到达顺序」且编号不复用、不回退(`PRE-2``PRE-3`)。
- Redis 写成功才算处理完成;查询接口与网页客户端读同一份 Redis,出问题时报错,不返回空列表假装正常(`INV-23``INV-24``US-05``US-06``US-12`)。
- 日计划快照以 AODB 下发为准:快照里没有的航班删除,未携带的字段清除;增量报文不适用这条(`INV-14b``INV-15b``US-07`)。
- 对外投递至少一次,同一航班(`FLID`)内保序,跨航班不承诺顺序(`INV-10``C-29``US-08`)。
- 航班只在历史写入成功后删除,历史库还没接通时一条也不删(`D1``INV-28``US-14`)。
- 静态参考数据一类校验失败只停这一类,其他类照常;空值是「当前没有值」,不是删除(`INV-26``INV-27``US-13`)。
- 航班唯一,版本只进不退:`FLID` 唯一,已写入非空的运营日不可改;每次成功写入版本号加一,重复消息不重复加(`INV-12``INV-13`)。
这些约束是拿速度换来的:一个写入方、一次只推进一条,前一条没处理完,后面都得等。要提速、要多实例,光加线程没有用——得先重新设计消息顺序和数据由谁写,多实例还得补上可靠的互斥保护。
## 6. 数据归属与一致性
| 存储 | 承载内容 | 职责说明 |
|---|---|---|
| 自有 PostgreSQL | 处理锁 `PIPELINE_LOCK`、消息处理状态与回填意图 `PROC_STATE`、待发事件 `MSG_EVENT`、出站请求跟踪 `REQ_TRACK`、航班当前态 `FLIGHT_SCHD`、资源明细表、`FLIGHT_ROUTE_POINT`、静态参考数据 `REF_MASTER`、日计划快照留痕 `SCHD_SNAP_LOG` | 本系统唯一的业务数据库,也是航班当前态的唯一权威(`INV-11b`);本地事务只发生在这里,事务怎么分段见第 4 节。记录级定义见 [implementation.md](implementation.md)「持久化记录」。 |
| Redis | 航班查询投影 | 只作查询,不是权威,也不存处理状态(`INV-11b`);只由本系统写入和移除(`INV-24`),内容来自 PG 当前态;`GET /all/flights` 与网页客户端读的就是它。 |
| 共享 MySQL | `CMINMSGS` 入站信箱、`COUTMSGS` 出站信箱 | 信箱归外部系统所有。本系统只读写消息、回写处理标记,不建表、不改表结构、不清数据、不写历史表(`C-14`);原文保留多久、何时清除由库方定(`C-5``C-12``Q7``Q9`)。出站请求写进去就算交付(`C-24`)。 |
| 航班历史存储(Elasticsearch) | 已结束航班的历史副本 | 已结束航班写入这里作历史副本;写入确认成功后才删实时数据,写不成一条也不删(`D1``INV-28`)。保留期与容量上限未定(`G-FLIGHT-HIST-RETENTION`)。 |
**PG 的事务只管自己库。** Redis 写没写成、信箱标记写没写上、Kafka 发没发出,PG 事务都管不着;这些步骤各自可重试,重做多少遍结果都一样,重启后从 PG 记录接着走。哪种中断该怎么接续,见 [implementation.md](implementation.md)「中断恢复」。
对外投递只承诺至少一次(`C-29`):应用重启、待发事件重发都可能让同一条消息多发一次,Kafka 的生产端幂等挡不住这种重复。
## 7. 关键决策
只列正文推不出来、仍有约束价值的决策,按 `D1``D2` 编号。第三列只给证据与偏差指针;可声明性见 [specification.md](specification.md)。决策不随实现状态增删。
| 编号 | 决策及理由 | 证据 / 偏差 |
|---|---|---|
| D1 | 删除实时数据前,下游必须已收到删除事件:FDEL 自带;历史清理先补发再删。历史写入是需求内交付(`US-14`)。 | 红线见 [implementation.md](implementation.md)「生命周期」与 `INV-28` |
| D2 | Kafka 生产端同时满足三项:确认级别、幂等、单连接在途条数上限;不许关幂等绕开这条限制。 | 取值见 [reference.md](reference.md) 参数表;投递口径见 `C-29``INV-10``US-08` |