docs: 收敛架构设计规范与用户故事实施清单 (ACM2-28)

This commit is contained in:
windyboy
2026-09-07 15:10:57 +08:00
parent bf233f7f69
commit abacd6a3f4
6 changed files with 635 additions and 694 deletions
+121 -221
View File
@@ -1,251 +1,151 @@
# msgexchange-v2 架构文档
> **系统定位**:机场 OMMS **上游报文处理中间件**,消费 CIIMS/AODB 等上游写入共享 MySQL 信箱的 XML 报文,经严格 FIFO 管道解析决策后维护 Redis 航班动态权威,并向下游(Kafka / 出站信箱 / 查询接口)投递;非报文源系统,不替代 CIIMS 或 AODB。
> **架构基准**:总体基线遵循 Plane ACM2-3(综合架构 v4),存储边界与事务模型以 ACM2-12(自有 PostgreSQL + 共享 MySQL 信箱 + Redis 动态/快照 gen;阶段 B 缓做)为准。模块级实现与交互细节参见配套 [design.md](design.md)。
## 1. 系统定位
## 1. 系统定位与范围
**机场 OMMS 上游报文处理中间件**:消费 CIIMS/AODB 等上游写入共享信箱的 XML 报文,
经严格 FIFO 管道解析、决策、维护航班动态权威态,并向下游(Kafka / 出站信箱 / 查询接口)
投递;**非**报文源系统,**不**替代 CIIMS 或 AODB。替换 legacy `msgexchange-api`
Java 8 / Spring Boot 1.5 / Maven)。过渡策略为**双跑三步**:
msgexchange-v2 是机场 OMMS 上游报文处理中间件,用于替换旧版 `msgexchange-api`
它读取 CIIMS、AODB 等系统写入共享 MySQL 信箱的 XML 报文,按顺序更新航班动态,再将结果提供给下游。
```
影子对拍(共享库水位/回放 + 自有 PG 独立 schema 比对)→ 切流(nextgen 权威)→ 旧仓库冻结
本系统负责**收报、解析、状态更新和结果投递**,不生成上游业务报文,不替代 CIIMS/AODB,也不提供 AODB 主数据编辑能力。
- **主要入口**:轮询共享 MySQL 的 `CMINMSGS`
- **兼容入口**`POST /cminmsgs/send`,供现役兼容、手工工具和对拍使用;写入信箱后返回记录 ID,不是生产收报主路径。
- **输出**Kafka 的 `msg` / `schd` 消息、共享 MySQL 的 `COUTMSGS` 出站信箱,以及查询 HTTP 接口;不直接推送前端。
- **当前范围(阶段 A)**:Redis 保存航班动态的权威状态。ES 历史投影及相关清场流程属于暂缓的阶段 B,不在当前交付范围内,也不新增关系型 `FLIGHT_STATE` 表。
本文描述架构约束,不代表所有能力已实现;实现缺口见第 9 节。模块交互、状态机和参数详见 [design.md](design.md),需求见 [user-stories.md](user-stories.md)。历史报文契约仍以 [SIS 接口规范](legacy/SIS_AODB_RMS-V0.1.md) 和 [XSD](legacy/unisysaodbsis.xsd) 为兼容依据,其他 legacy 资料仅作参考。
## 2. 总体架构
```text
CIIMS / AODB 等上游
│ 写入 XML
共享 MySQLCMINMSGS
│ 轮询未处理记录
┌──────────────── msgexchange-v2(单实例)────────────────┐
│ ingress:发现报文 → PostgreSQL 持久化入队 │
│ │ │
│ processing:取 FIFO 队头 → 解析 / 去重 → Handler 决策 │
│ ├─ 更新 Redis 航班动态 / 快照 │
│ └─ PG 事务:处理结果 + 待发事件 │
│ │
│ jobs:在主泵空闲或消息退避窗口内执行维护作业 │
│ delivery:读取 PG 待发事件 → 投递 / 重试 │
└─────────────────────────┬──────────────────────────────┘
├─ Kafkamsg / schd
└─ 共享 MySQLCOUTMSGS
处理结果提交后,再回填 CMINMSGS 的处理标记;失败需补偿。
查询接口读取航班动态,不参与状态写入。
```
- legacy 维护不受本仓库影响;本仓库不声明 legacy 旧表 schema**且不在共享 MySQL 建任何表**
CMINMSGS/COUTMSGS 所在库属他人系统,本系统仅信箱 DML——ACM2-12,见 §6)。
- wire 契约冻结:消息结构唯一事实源为 `docs/legacy/SIS_AODB_RMS-V0.1.md` +
`docs/legacy/unisysaodbsis.xsd`
HTTP 端点路径与响应语义沿用现役(如 compat 写路径 `POST /cminmsgs/send` 返回记录 ID)。
- **影子对拍口径(ACM2-12)**:共享库是单信箱无法"同入口双收",影子输入改以共享库只读
水位/回放 + 自有 PG 独立 schema 比对(详见 §8 与 ACM2-12 Checks ⑤)。
收报、处理和投递各使用一条专用线程,不占用 HTTP 事件循环。**只有主泵可以写航班动态及快照版本**,维护作业也必须遵守这一规则。
### 1.1 上下游边界(中间件职责)
采用 Kotlin + JDK 25、Micronaut 编译期依赖注入和 JDBC 持久化。数据库变更由 Flyway 管理,但只作用于自有 PostgreSQL。具体依赖版本以 `build.gradle.kts` 为准,不在架构文档重复维护。
| 方向 | 角色 | 本系统做什么 | 本系统**不**做什么 |
|---|---|---|---|
| 入站(主路径) | CIIMS / AODB 等上游 | JDBC 轮询共享 MySQL `CMINMSGS` 发现新信 → 自有 PG 入队 → 解析处理 | 不生成原始业务报文;不替代 CIIMS 落信 |
| 入站(compat | 手工工具 / 对拍 | HTTP `POST /cminmsgs/send` 写信箱 + PG 入队 | 非生产主拓扑 |
| 处理 | 本系统 | 维护 Redis 航班动态权威态;Handler 纯函数决策 | 不持有 AODB 主数据编辑权 |
| 出站 | 下游消费者 | Kafkamsg/schd)、共享 MySQL `COUTMSGS`、查询 HTTP | 不直接推送至前端(经 Kafka 等中转) |
## 3. 模块职责
与 SIS / legacy 一致:上游经 CIIMS 等**外部系统**写入 `CMINMSGS` 后,本系统以 **1s JDBC 轮询**
`DATE_PROCESSED IS NULL`)采集并处理——与 legacy `MsgExchangeRunner` 同口径。
| 模块 | 职责与边界 |
|---|---|
| `ingress` | 轮询信箱、持久化入队、补偿重扫及兼容 HTTP 写入;不解析业务报文。 |
| `codec` | XML 解码,区分非法报文与可修复的解码失败。 |
| `processing` | FIFO 调度、业务身份绑定与去重、Handler 决策、快照处理及状态提交。Handler 只返回决策,不直接访问数据库、Redis 或 Kafka。 |
| `delivery` | 消费待发事件,负责按目标保序、`schd` 聚合、投递和失败重试。 |
| `jobs` | 持久化维护作业,由主泵在允许的窗口执行;阶段 B 作业暂不启用。 |
| `reference` | 静态参考数据和上游请求跟踪。 |
| `domain` / `config` | 领域状态、事件和决策模型,以及运行参数。 |
| `infra` | 仓储、Redis 脚本、外部适配器、重试、健康检查与日志;通过接口隔离基础设施。 |
## 2. 技术栈
## 4. 主流程
| 层 | 选型 | 说明 |
### 收报与处理
1. `InboxPoller` 默认每秒扫描 `DATE_PROCESSED IS NULL` 的信箱记录,在自有 PG 中建立 `PROC_STATE(PENDING)`。重复扫描不能重复入队;入队失败留待重扫。
2. 主泵只处理最小未完成 `CMINMSGS_ID`。解析报文、绑定业务身份并去重后,调用对应 Handler 生成决策。
3. 主泵执行 Redis 更新。全量快照通过代际版本 `gen` 管理覆盖和旧数据清理,避免旧快照覆盖新状态。
4. 在 PG 本地事务中同时保存处理结果和 `MSG_EVENT` 待发事件,再回填共享信箱的处理标记。
### 投递
`Dispatcher``MSG_EVENT` 取出待发事件。普通事件按投递目标和 `EVENT_ID` 保序;某个目标失败时,不能跳过其队头投递后续事件。
`schd` 是最新状态通知,不逐条发送中间变化:统一由 `flushSchd``FLID` 聚合,取批次内最新事件后发送。它不提供逐条变更历史,不能与普通事件的 FIFO 语义混为一谈。
## 5. 必须保持的约束
- **消息严格 FIFO**:队头失败并退避时,后续消息仍不能越过它。只有队头完成或按失败策略进入终态后,队列才继续推进。收报重扫和水位设计必须防止较小 ID 漏入队而被后续消息越过。
- **动态状态单写者**:Redis 航班动态和快照 `gen` 只由主泵写入。不能通过增加实例或处理线程来直接扩容。
- **身份去重**:同一业务身份只能绑定一条有效处理记录,重复报文不应再次产生业务副作用。具体身份组成和重放规则见设计文档。
- **快照可恢复**:快照覆盖、旧数据清理和版本推进需要原子性与重放保护;不能在恢复时把旧代数据重新写回。
- **作业不与消息混排**`PUMP_JOB` 是独立队列,只在没有消息队头或队头处于退避窗口时执行。执行窗口与饥饿边界需要明确验证。
这些约束优先于吞吐量优化。单写者降低了并发复杂度,代价是队头阻塞和吞吐上限;如需并行化,必须先重新定义顺序与状态归属,不能只调整线程数。
## 6. 数据归属与一致性
| 存储 | 保存内容 | 边界 |
|---|---|---|
| 语言/运行时 | Kotlin 2.3 + JDK 25 | 目标运行时为 JVM 25Micronaut 5.1 依赖基线要求) |
| 框架 | Micronaut platform BOM **5.1.3**core 系实际解析 **5.1.13**,版本重钉属 U02/U05 | 编译期 DIKSP`kotlin-ksp` + `micronaut-inject-kotlin` **5.1.3**)生成 `*$Definition` |
| 持久化 | **自有 PostgreSQL**(全部内部状态)+ 共享 MySQL 信箱 | 自有库:管道状态 PROC_STATE/MSG_EVENT + 任务调度 PUMP_JOB/REQ_TRACK + 21 类静态 REF_MASTERFlyway 迁移 `db/migration`);共享库严格保持 CMINMSGS/COUTMSGS 最小 DML 契约。JDBC 仓储与 InboxPoller 已有初版,事务/补偿/出站适配属 U05(ACM2-12 |
| 权威存储 | Redis(航班动态 flightInfo + 快照 gen | 仅主泵单线程写入(I5);Lua 脚本执行原子状态覆盖与代际版本推进(gen 协议重设计属 U09) |
| 投递 | Kafkaacks=all + 幂等生产;ACM2-23 | transactional outbox 模式,经自有库 MSG_EVENT 表中转 |
| 投影(阶段 B | Elasticsearch(历史) | **阶段 B 缓做(ACM2-12**FLIGHT_STATE 不落关系表,Redis 保持动态权威;历史投影链路待阶段 B 重启评估 |
| 注册中心 | EurekaMicronaut 原生注册) | 服务名契约 `msgexchangeapi`(影子实例 `msgexchangeapi-shadow`)——当前运行时仍取 `micronaut.application.name`,配置映射待 U17 完善 |
| 自有 PostgreSQL | `PROC_STATE` 处理状态、`MSG_EVENT` 待发事件、`PUMP_JOB` 作业、`REQ_TRACK` 请求跟踪、`REF_MASTER` 静态数据,以及 `PROC_STATE_HST` 归档 | 本系统的内部持久化状态;唯一的本地事务边界。 |
| 共享 MySQL | `CMINMSGS` 入站信箱、`COUTMSGS` 出站信箱 | 外部系统所有。仅执行约定的信箱读写和处理标记回填,不建表、不迁移 schema、不写历史表。兼容 HTTP 入口可按既有契约写入入站信箱。 |
| Redis | 航班动态 `flightInfo` 和快照版本 `gen` | 阶段 A 的动态权威存储,不是可随意清空的缓存。 |
## 3. 总体拓扑
**不使用跨库事务。** PG 事务只能保证“处理结果与待发事件一起提交”,不能覆盖 Redis 更新、MySQL 回填或 Kafka 发送。跨存储依靠幂等、重试和持久化补偿恢复:
```
CIIMS/上游 ──外部写(他人系统)──▶ 共享 MySQL CMINMSGS(信箱)
│ JDBC 轮询/重扫(① 发现 DATE_PROCESSED IS NULL 新信)
┌──────────────────────────────────────────────────┐
│ msgexchange-nextgen │
│ (单实例 · 单写者) │
│ ingressInboxPoller + 补偿重扫,U05
│ └──② 自有 PG PROC_STATE(PENDING) │
│ (跨库非同事务;② 失败→① 重扫补建) │
compatPOST /cminmsgs/send ──▶ 信箱 insert │
│ + PG 入队(现役 HTTP 写路径,U16 对拍) │
│ │
│ processingmsgx-pump 线程,严格 FIFO 队头) │
│ Pump ──tick──▶ MessageProcessor │
│ │ │ decodeXmlCodec
│ │ │ identity 绑定(I3
│ │ │ Handler.decide(纯函数) │
│ │─Schd RESP/DNLD▶ SnapshotFlow(流程4
│ │─PUMP_JOB───▶ JobExecutor(作业窗口,决策1
│ │ │
│ ├────Redis Lua──▶ Redis flightInfoA权威) │
│ ├──自有 PG 事务2──▶ MSG_EVENT + SUCCEEDED │
│ └──共享 MySQL 回填 DATE_PROCESSED(外部副作用)│
│ │
│ deliverymsgx-dispatcher 线程,每 target FIFO
│ Dispatcher ──逐条──▶ Kafka(msg) │
│ └─flushSchd 聚合─▶ Kafka(schd) │
│ (阶段 B 追加:ES flight_hts → Redis 投影删除) │
└──────────────────────────────────────────────────┘
共享 MySQL(信箱)◀──① 轮询发现 / 回填──▶ 自有 PostgreSQL ◀──② 管道状态
Redis(动态+gen)◀── Lua 写 ── processing
│ │
▼ ▼
下游 Kafka topic Eureka / logstash
```
| 中断位置 | 恢复要求 |
|---|---|
| 信箱已有报文,PG 入队失败 | 重扫补建,并按信箱 ID 去重。 |
| Redis 更新成功,PG 提交失败 | 消息重试可能再次更新 Redis;更新与快照协议必须支持幂等重放和版本校验。 |
| PG 已提交,信箱回填失败 | 持久化记录补偿任务并重试回填,不能重新执行已完成的业务处理。 |
| 下游已接收,本地尚未标记发送成功 | 允许重发;下游或出站适配协议必须具备去重能力。 |
要点:
对外投递按**至少一次**设计,不承诺端到端恰好一次。Kafka 生产者幂等不能消除应用重启或 outbox 重发带来的所有重复。Redis 恢复也不能仅依赖 PG 处理状态:备份、报文保留及回放范围需要在上线前验证。
- **收报主路径(与现役/SIS 一致)**:上游经 CIIMS 等**外部系统**写入共享 MySQL `CMINMSGS`
(本系统不建表);`ingress`**JDBC 轮询**`DATE_PROCESSED IS NULL`1s 节律,与
legacy `MsgExchangeRunner` 同口径)发现新信 → 自有 PG 建 `PROC_STATE(PENDING)`
PG 入队失败时以共享库水位**重扫补建**(U05)。`POST /cminmsgs/send` 为现役 HTTP
**写**路径(手工/对拍),非上游报文到达的主拓扑。
## 7. 关键决策索引
- **三条专用 daemon 单线程**`msgx-inbox-poller` / `msgx-pump` / `msgx-dispatcher`)由 `PipelineLifecycle`
`ServerStartupEvent` 后拉起,不占用 Netty event loop;停机 `requestStop` +
interrupt + joinU07)。仅当 `msgx.pipeline.autostart=true` 时装配——生产默认关,
当前属**有意脚手架门禁**(生产可运行需先完成 U05 数据层实装,见 §7)。
- **单写者约束(I5)**:阶段 A 全部 Redis 写集中在主泵线程;实例数必须为 1
(运行期租约/选主保护属 U26,尚未实装,当前靠部署拓扑约束)。
- **消息严格 FIFO,作业采用窗口语义(决策 1 经 ACM2-12 修订)**:消息按最小未完成
`CMINMSGS_ID` 保序;PUMP_JOB 不与消息构成统一全序,仅在无消息队头或队头处于退避窗口时执行。
当前 `jobBefore` 的 head-state 判定已近似该语义,但窗口、饥饿边界和测试仍待 U15 固化。
- **存储边界(ACM2-12)**:本框图内事务库为**自有 PostgreSQL**PROC_STATE/MSG_EVENT/
PUMP_JOB/REQ_TRACK/21 类);CMINMSGS/COUTMSGS 在**共享 MySQL 信箱**(上游外部写、
本系统 JDBC 轮询读 + 处理回填写);Redis 除 flightInfo 还承载快照 gen。图中跨库步骤
(① JDBC 发现 / ② PG 入队 / 外部回填)与 ACM2-3 原「同库事务锚」语义不同,见 §6:
本地事务只在自有 PG,信箱交互为外部读/写 + 最终一致。
保留 D1–D12 编号,便于设计文档和工程历史引用;以下是决策摘要,而非完成清单。
## 4. 模块职责
| 编号 | 决策及理由 |
|---|---|
| D1 | 业务报文严格 FIFO,维护作业窗口执行,优先保护航班状态的时序正确性。 |
| D2 | 阶段 B 暂缓。历史写入成功后才可生成删除事件;顺序调用本身不保证原子性,恢复与去重方案需在启用前补齐。 |
| D3 | `schd` 只从 `flushSchd` 聚合发送,减少已被覆盖的中间状态通知。 |
| D4 | 未实现的报文类型按 `UNSUPPORTED` 可恢复失败处理,不当作非法报文直接丢弃;补齐能力后按重放规则恢复。 |
| D5 | 在持有具体消息或批次上下文的位置记录失败和退避;不吞掉线程中断或 JVM 严重错误。 |
| D6 | 基础设施通过接口注入,时间通过 `Clock` 注入,便于确定性测试顺序、重试与超时。 |
| D7 | 内存 stub 仅显式开启时装配,生产禁止使用,避免把未持久化的数据误当作已落库。 |
| D8 | 使用编译期依赖注入,并以启动冒烟测试验证关键 Bean 装配。 |
| D9 | 自有 PG 内完成本地事务,共享 MySQL 仅作信箱;跨存储采用补偿,不使用 XA。 |
| D10 | 动态状态单写者,生产只允许一个活动实例;多实例必须先具备可靠的排他保护。 |
| D11 | Kafka 生产要求 `acks=all``enable.idempotence=true``max.in.flight=1`,切流前验证 Broker 兼容性;不允许通过关闭幂等来满足生产接入。 |
| D12 | 仅将自有库终态记录归档到 `PROC_STATE_HST`,不侵入共享库的表结构或保留策略。 |
| 包 | 职责 | 对应 ACMA-8 | 主要类 |
|---|---|---|---|
| `ingress/` | 收报:JDBC 轮询共享信箱发现新信 → 自有 PG 建 PENDING+ 补偿重扫;HTTP 写路径 compat);不解析报文 | 流程 1I3 | `InboxPoller`U05`InboxController` `InboxService` |
| `processing/` | 主泵:FIFO 领取、ignoreMsg、identity 绑定、纯函数决策、RESP/DNLD 快照、自有 PG 事务2 | 流程 2/4I1/I2/I5 | `Pump` `MessageProcessor` `SnapshotFlow` `Identity` `Handler(Registry)` |
| `delivery/` | 投递:每 target 严格 FIFO、schd 聚合 | 流程 3 | `Dispatcher` `SchdAggregation` |
| `jobs/` | 泵作业:清场/归档/投影重建(PUMP_JOB 自有 PG,作业窗口执行) | 流程 4/5/7,I4 | `JobExecutor` `HistorySweepJob` `ArchiveJob` `ProjectionRebuildJob` |
| `codec/` | XML 解码 + 失败分类(MALFORMED vs CODEC_ERROR | 决策 4 前置 | `XmlCodec` `DecodeResult` |
| `domain/` | 状态机枚举、事件/决策模型、Phase 开关 | I1I5 | `ProcState` `MsgEvent` `Decision` `MsgKind` |
| `infra/` | 仓储接口、重试策略、Redis Lua、stub、健康、日志 | 数据模型节 | 见 design.md |
| `config/` | `PipelineProps` 参数表(ACMA-8 参数初值) | — | `PipelineProps` |
## 8. 部署、切换与运维
## 5. 关键架构决策
**部署与安全**
> 决策编号保持与 [design.md](design.md) 及工程历史引用一致;按【定案】与【设计依据与权衡】两段式规范表述
- 生产维持单活动实例,停机时停止接收新任务并等待工作线程退出。运行期排他保护尚未完成,当前不能依靠程序自动阻止双实例写入
- 配置、口令和环境端点通过环境变量提供。兼容写接口沿用内网信任模式,缺少鉴权,必须限制网络访问;管理端点不得直接暴露到生产外网。
- Eureka 用于服务发现,Logstash 接收结构化日志;日志出口故障不应阻塞业务处理。
### D1 — 业务报文严格 FIFO 与维护作业窗口化调度
**替换旧系统**
- **定案**:业务报文按最小未完成 `CMINMSGS_ID` 严格保序、逐条执行;清场/归档/投影重建等维护作业(`PUMP_JOB`)单独排入持久化作业队列表,严禁与业务报文混排。作业仅在消息队列为空、或队头报文处于重试退避等待窗口且作业可在窗口期内完成时触发
- **设计依据与权衡**:报文到达与处理时序直接决定航班生命周期状态机的权威正确性(如“计划变更”与“航班取消”时序颠倒将导致严重脏数据),业务 FIFO 为最高优先级不变式(I1)。维护作业属于低频异步运维任务,若与业务报文抢占同一调度队列将引入队头阻塞与事务锁竞争;采用窗口化插针调度,既确保业务报文零干扰,又实现后台任务免停机自适应推进。
- **落地与边界**`Pump.tick``JobExecutor`;作业饥饿防护与精确退避窗口由 U15 固化。
采用“影子对拍 → 切流 → 旧系统冻结”。共享信箱不能让新旧系统同时认领和回填;影子输入使用只读水位或回放。影子环境须隔离 PG schema/实例、Redis key 空间、Kafka topic 和服务注册身份,并禁止误写生产信箱。切流时保证只有一个权威写者
### D2 — 阶段 B 历史投递与投影清理同步编排(缓做)
**可观测性要求**
- **定案**:历史航班写入 ES 成功后,由同一消费线程同步向自有库 `MSG_EVENT` 写入“删除 Redis 投影”事件,不采用跨系统异步轮询或分布式两阶段提交。本项归属阶段 B(ACM2-12 缓做),待后续阶段评估重启
- **设计依据与权衡**:利用同线程同步顺序调用保障“ES 写入成功”与“删除事件就绪”的因果强一致性,消除外部索引已更新但缓存清理事件悬挂丢失的竞态窗口;避免引入额外的分布式协调器与待确认补偿表,控制架构复杂度。
- **落地与边界**`Dispatcher.tick`(定案 2;阶段 B 重启后实装)。
使用消息 ID、事件 ID 关联处理与投递日志;健康检查反映依赖实际可用性,而不只是进程存活。运行中重点关注队列积压、队头滞留时间、投递延迟、重试/DEAD 数量和回填补偿积压。死信和一致性异常需要可执行的告警与重放流程,不能只留一条错误日志
### D3 — 调度快照(schd)批量聚合与最新态压缩投递
## 9. 当前实现与上线门槛
- **定案**:调度快照类报文(`KAFKA_SCHD`)严禁进入逐条投递链路,在逐条轮询中显式排除;出站唯一路径为 `flushSchd` 定时与批阈值触发的批量聚合流程:按航班唯一标识(`FLID`)分组去重,仅提取组内最新一条(`max(EVENT_ID)`)快照聚合为批,一次性投递至 Kafka
- **设计依据与权衡**:调度类报文存在高频状态刷新特征,逐条下发会导致 Kafka 主题与下游消费者遭遇瞬态数据风暴,且会无谓广播已被新快照覆盖的历史过期状态(现役系统缺陷);聚合去重确保下游获取确定性的全量最新切面,同时大幅削减网络 I/O 与 Kafka 吞吐压力。
- **落地与边界**`Dispatcher.tick``SchdAggregation`U06/N03)。
当前已有管道骨架、重试机制、部分 JDBC 适配和开发环境 stub 冒烟能力,**不能据此认定生产链路已闭环**。默认配置关闭管道自动启动及真实数据库/信箱适配
### D4 — 未实装报文可重放机制(FAILED-UNSUPPORTED
上线前至少需要完成并验证:
- **定案**:处理管道遇到尚未实装 Handler 的报文类型或未就绪的快照 Staging 时,统一标记为 `FAILED(UNSUPPORTED)` 并进入指数退避,严禁写入不可逆终态(如 DEAD 或伪成功)
- **设计依据与权衡**:支持业务协议分阶段平滑演进与上线。报文协议 Handler 翻译分批交付,过渡期提前接入的未支持报文必须保持可重放状态,待新版本 Handler 发布后通过 `ReplayService` 批量重放激活,杜绝因协议尚未覆盖而造成数据永久丢弃
- **落地与边界**`MessageProcessor``SnapshotFlow``ReplayService`U10/N21
- 真实 PG 事务、信箱水位与补扫、回填补偿、出站信箱,以及所需业务 Handler
- Redis 更新幂等性、快照版本校验、故障中断恢复与权威数据恢复方案
- FIFO、身份去重、作业窗口和投递故障下的回归测试
- 生产启动校验、单实例排他保护、影子隔离和 Kafka 配置约束;当前配置仍允许 Kafka 参数覆盖,且默认 in-flight 值与 D11 要求不同。
- 死信告警、人工重放、端到端追踪、积压指标及安全边界。
### D5 — 异常分级收敛与 JVM 致命故障快速失败
- **定案**:报文级业务与编解码异常(FAILED、重试计数递增、退避调度)在持有具体消息上下文(head/batch)的执行边界就地捕获与状态机扭转;主泵与分发器外层循环仅兜底捕获并记录通用 `Exception`,严禁捕获或吞掉 `InterruptedException``VirtualMachineError` / `Error`
- **设计依据与权衡**:异常必须精准归因到具体报文以维护审计轨迹与隔离单条故障;主循环必须对线程中断信号做出响应以保障容器平滑优雅停机(Graceful Shutdown),同时允许 OOM 等致命底层错误即时穿透导致进程崩溃退出(Fail-Fast),防范系统在亚健康或内存损坏状态下带病运行。
- **落地与边界**`MessageProcessor``Dispatcher`U08)。
### D6 — 基础设施抽象解耦与确定性虚拟时钟驱动
- **定案**:核心领域逻辑与管道状态机与底层中间件物理实现完全解耦,存储依赖均定义为抽象接口,单测采用内存假仓储;所有时间依赖必须通过可注入的 `Clock` 获取,禁止直接调用 `System.currentTimeMillis()` 或系统默认时钟。
- **设计依据与权衡**:保障单元与架构测试环境无需启动重量级外部容器(PostgreSQL/Redis/Kafka),确保测试毫秒级完成;通过手动操纵虚拟时钟对退避窗口、超时截断等时序临界路径实现确定性断言,彻底消除线程休眠(Thread.sleep)引入的测试脆弱性与执行抖动。
- **落地与边界**`infra/persistence/Repositories.kt``FailureScheduler`
### D7 — 内存 Stub 条件装配门禁与生产硬隔离
- **定案**:所有内存 Stub 实现类统一受 `@Requires(property = "msgx.stubs", value = "true")` 条件装配注解约束,生产配置文件中该项强制缺省或设为 `false`Stub 与 `autostart` 组合仅用于本地开发与快速冒烟。
- **设计依据与权衡**:Stub 仅保留在进程内瞬态集合中,不具备持久化保证;通过框架级配置门禁强制阻断内存实现渗透至生产环境的可能,从根本上杜绝数据静默丢失风险。
- **落地与边界**`infra/stub/StubRepositories.kt`U07/U01)。
### D8 — 依赖注入编译期静态化与上下文冒烟锁定
- **定案**:采用 Micronaut KSP 编译期 AOT 处理生成无反射 `BeanDefinition`;在持续集成流水线中配置轻量级 `ApplicationContext` 启动冒烟测试,刚性校验关键 Bean 的生成与装配链。
- **设计依据与权衡**:消除运行时反射与类扫描开销,降低系统内存占用并加速容器启动;依赖升级或注解变更可能隐蔽破坏 KSP 代码生成,CI 冒烟测试将装配回归缺陷锁死在提交阶段,避免延迟至生产部署暴露。
- **落地与边界**`build.gradle.kts``PipelineSmokeTest.kt`U01)。
### D9 — 跨库存储边界与自有库本地事务闭环
- **定案**:外部共享 MySQL 严格限定为只读轮询(`CMINMSGS`)、状态回填(`DATE_PROCESSED`)及出站写入(`COUTMSGS`)的外部信箱通道,严禁在共享库执行 DDL 或创建中间表;系统全部内部状态(消息处理轨迹、事件 Outbox、任务调度、静态参考数据)统一由自有 PostgreSQL 承载。本地 ACID 事务仅在 PostgreSQL 内部闭环(`MSG_EVENT` 插入与 `PROC_STATE` 终态更新原子提交);信箱回填作为外部副作用在事务提交后异步执行,并通过失败补偿保障最终一致性。
- **设计依据与权衡**:共享 MySQL 为外部多方共用系统,无法支持两阶段提交(XA),且外部长事务会导致严重的信箱锁竞争与级联故障传导;将一致性边界收敛于自有 PostgreSQL,使核心处理管道具备完全可控的事务与审计能力,跨系统交互通过“本地消息表 + 异步补偿”解耦。
- **落地与边界**`MessageProcessor``InboxPoller``CminmsgMailbox``OutboxMailbox`ACM2-12 / ACM2-19)。
### D10 — 动态权威主泵单写者(Single-Writer)无锁模型
- **定案**:Redis 航班动态权威态(`flightInfo`)及快照代际状态(`gen`)的全部写操作,严格收敛至单一主泵线程(`msgx-pump`);系统生产拓扑强制以单实例部署运行,多实例部署需配置运行期分布式租约/排他锁拒启保护。
- **设计依据与权衡**:彻底消除多线程并发写入带来的锁竞争、死锁与 ABA 状态覆盖风险;单写者模型极大简化了复杂航班生命周期状态机的推演与证明,在无需引入重型分布式协调机制的前提下,实现极高吞吐的确定性状态更新。
- **落地与边界**`Pump``PipelineLifecycle``FlightRedisClient`(不变式 I5、U26)。
### D11 — 生产 Kafka 幂等投递与防降级硬契约
- **定案**:消息与调度事件向 Kafka 投递时,生产者客户端强制配置 `acks=all``enable.idempotence=true` 以及 `max.in.flight.requests.per.connection=1`;生产切流前必须确认 Broker 节点支持 `InitProducerId(22)` 协议,严禁在生产环境配置降级为非幂等生产。
- **设计依据与权衡**:由于自有库 Outbox 分发器在网络重试时可能重复投递,必须依赖 Kafka Broker 端的序列号机制实现精确一次写入(EOS);严禁生产降级消除了网络抖动或 Broker Leader 切换时导致下游接收乱序报文或重复业务事件的隐患。
- **落地与边界**`Dispatcher``KafkaEventPublisher`ACM2-23)。
### D12 — 报文归档自有库收敛与外部信箱零侵入
- **定案**:入站历史报文的定期归档与清理,处理对象仅限自有库中的终态记录(SUCCEEDED / SKIPPED / DEAD),归档目标确定为自有 PostgreSQL 的 `PROC_STATE_HST`;严禁向共享 MySQL 写入 `CMINMSGS_HST` 表。
- **设计依据与权衡**:外部共享库的物理表空间与历史保留策略归外部系统管辖,第三方中间件向外部数据库写入历史表不仅违反权限与隔离契约,还会因外部表结构变更引入系统性风险;将归档生命周期完全自包含在自有库中,确保系统自洽与合规。
- **落地与边界**`ArchiveJob``db/migration/V1.0.0__own_pg_pipeline.sql`ACM2-17)。
## 6. 数据边界(ACM2-12 最终口径)
- **自有 PostgreSQL(本系统唯一自有数据库)**:全部内部状态,本地事务只在此库成立。
`db/migration/V1.0.0__own_pg_pipeline.sql`PG 方言)建:`PROC_STATE`(取消息侧:处理
状态/重试/毒丸)+ `MSG_EVENT`(发消息侧:outbox+ `PUMP_JOB`(作业调度)+ `REQ_TRACK`
15 类请求)+ `REF_MASTER`(21 类静态,SOURCE 审计)。
- **共享 MySQLcdairport,他人系统库)——本系统不建任何表/schema,仅信箱 DML**
上游外部写 `CMINMSGS`;本系统 JDBC 轮询读 + 处理完成回填 `DATE_PROCESSED/STATUS`
出站写 `COUTMSGS`(他人系统读取发送)。与信箱的交互是**外部副作用**,非本系统事务的一部分:
收报主路径=JDBC 轮询发现新信 → 自有 PG 建 PENDING 入队;compat HTTP 写=
信箱 insertRaw 成功(返回 CMINMSGS_ID)→ PG 入队;PG 建行失败以共享库
`DATE_PROCESSED IS NULL` 重扫补建;回填 = 处理成功后 PG 本地事务外异步/持久化补偿,最终一致(ACM2-12 / ACM2-19)。
- **Redis**:航班动态 flightInfo(阶段 A 权威,I5+ 快照 **SCHD_GEN**Lua 内原子
「覆盖+按代差删+版本推进」,协议重设计属 U09)。
- **阶段 BFLIGHT_STATE):缓做,不落表**;ES 仍承载历史航班(判史/查询),
相关投影/删除事件链待阶段 B 重评估后启用。
- legacy 旧表 schema 归 legacy 仓库维护;CMINMSGS/COUTMSGS 的结构与保留策略由共享库方管理。
**ARCHIVE 归档定案(ACM2-17**:严禁向共享 MySQL 写入 `CMINMSGS_HST`(共享库严格保持 CMINMSGS 读/回填、
COUTMSGS 写入两表契约);终态入站消息归档目标确定为自有 PG `PROC_STATE_HST`
## 7. 权威(阶段 A Redis;阶段 B 缓做)与当前就绪度
| 阶段 | 权威 | 投递目标 | 状态 |
|---|---|---|---|
| A`msgx.phase=A` | Redis flightInfo+ gen | KAFKA:msg、KAFKA:schd | 管道骨架+重试闭环已实装;PG/JDBC 信箱轮询已有初版,但水位、事务、回填补偿与出站信箱尚未闭环;gen→Redis 协议属 U09 |
| B(缓做,重新评估后命名/启用) | Redis 仍为动态权威 | ES 历史写入与成功集清场 | FLIGHT_STATE 不落表;HISTORY_SWEEP 标为 DEFERRED,不作为阶段 A 切流门禁 |
**当前就绪度(基线状态)**:自动化测试集覆盖 39 项单元与架构测试;开发环境支持 `MICRONAUT_ENVIRONMENTS=dev` 端到端 Stub 冒烟(无外部中间件启动 → compat HTTP 写路径响应 200 → `/health` UP)。生产管道默认保持关闭(`msgx.pipeline.autostart=false`,真实数据源与信箱适配层默认 disabled)。生产就绪前置依赖包括:U05(自有 PG 本地事务、双库补偿与出站信箱闭环)、U07(生产 fail-fast 启动校验)、U09Redis Lua 快照代际 CAS 与恢复协议)、U13(投递超时升级与 DLQ 告警)、U15(统一序号与作业窗口边界固化)。逐项跟踪详见 ACM2-10 实施计划。
## 8. 部署与安全姿态
- **实例数 = 1**(主泵单写者前提);双实例误配当前无运行期防护(U26:租约/DB 锁 + 拒启,未实装)。
- **影子隔离(目标架构规范;当前为基础实现态)**:服务名(`msgexchangeapi-shadow`+ 独立
schema + Redis key 前缀 + 独立 topic 三层隔离;当前代码仅 `msgx.register-eureka=false` 生效——
Kafka topic 写死字面量 `"msg"`/`"schd"`Dispatcher)、`FlightRedisClient.eval` 无 key 前缀参数、
服务名未接 `msgx.service-name`(§2)。影子数据库:自有 PG 开独立 schema/实例;共享信箱无法
双写,影子输入=只读水位/回放口径(ACM2-12 Checks ⑤)。
- **网络信任模型**`/cminmsgs/send` 无鉴权(沿用现役内网信任姿态);eureka default-zone
回退 `127.0.0.1:8761`;口令/端点全部环境变量外置(零入库)。安全节细化属 U28。
- **管理端点**Micronaut 5.1 下 `/env` 默认**禁用**、`/beans` 默认 enabled+sensitivedev/影子经
顶层 `endpoints.*`**非** `micronaut.endpoints.*`——实测前缀错误时不生效)放开 `/env``/beans`
与 health 明细。工程未引入 micronaut-securitysensitive 的实际拦截行为待 U28 定案。
- **同名单风险**:影子与生产同名同路径时,compat HTTP 写可能误写生产信箱——切流前必须核对服务名三隔离。
## 9. 可观测性
- **日志**logstash TCP JSON 通道(Async + neverBlock 降级,logstash 不可达不阻塞业务线程);
结构化生命周期日志(入队/PENDING/SUCCEEDED/SKIPPED/FAILED/DEAD/毒丸/flush 批次);MDC `traceId`
(当前 = cminmsgsId/eventId,处理片段;贯穿入队→投递属 U12 遗留)。
- **健康**`/health` 聚合 `redis-flight-store` / `kafka-delivery` 自定义指示器——真实 ping
判定(false/异常→DOWN,缺 bean→DOWN),非仅 bean 存在。
- **指标缺口**micrometer 队列深度/投递延迟 gauge 未引入(版本对齐待 U05 批次);
DEAD/DLQ 告警出口与一致性哨兵实装(U25)未落地——告警当前以 ERROR 日志为落点。
具体进度由配套设计(design.md §9 已知缺口表)与 Plane ACM2-10 实施计划维护,本文不记录测试数量、临时补丁版本或逐项工单进展。架构基线沿用 ACM2-3,存储与事务边界以 ACM2-12 的修订为准。