# msgexchange-v2 架构文档 > 现行架构权威为 Plane `airport_chengdu_msgexchange_api` 工作区的 **ACM2-3(综合架构 v4)**; > 脚手架跟踪 **ACM2-4**,评审与实施计划(U01–U30)**ACM2-10**。本文是仓库内的架构速览, > 与代码同步维护;两者冲突时以 ACM2-3 为准并回改本文。 > 配套设计细节见 [design.md](design.md)。 ## 1. 系统定位 新一代机场消息交换服务(AODB 报文接入 → 处理 → 对外投递),替换 legacy `msgexchange-api`(Java 8 / Spring Boot 1.5 / Maven)。过渡策略为**双跑三步**: ``` 影子对拍(同入口双收,比对输出)→ 切流(nextgen 权威)→ 旧仓库冻结 ``` - legacy 维护不受本仓库影响;本仓库不声明 legacy 旧表 schema(见 §6 数据边界)。 - wire 契约冻结:消息结构唯一事实源为 `SIS_AODB_RMS-V0.1.md` + `doc/unisysaodbsis.xsd`; HTTP 端点路径与响应语义沿用现役(如 `POST /cminmsgs/send` 返回记录 ID)。 ## 2. 技术栈 | 层 | 选型 | 说明 | |---|---|---| | 语言/运行时 | Kotlin 2.3 + JDK 25 | JDK 21 不可行(Micronaut 5.1 系要求 JVM 25+,ACMA-9 实测) | | 框架 | Micronaut 5.1.3 | 编译期 DI:KSP(`kotlin-ksp` + `micronaut-inject-kotlin`)生成 `*$Definition` | | 持久化 | 业务库:MySQL + Flyway;静态参考库:**PostgreSQL**(ACM2-11) | 仓储现为接口(Micronaut Data JDBC 实装属 U05,阶段 1 后续;`datasources.reference` enabled=false 待阶段 6) | | 权威存储 | Redis(阶段 A) | flightInfo hash;仅主泵线程写(I5);Lua 脚本原子覆盖 | | 投递 | Kafka(acks=all + 幂等) | outbox 模式,经 MSG_EVENT 表中转 | | 投影(阶段 B) | Elasticsearch + Redis 投影 + FLIGHT_STATE | 仅阶段 B 启用(`msgx.phase`) | | 注册中心 | Eureka(Micronaut 原生键) | 服务名契约 `msgexchangeapi`(影子 `msgexchangeapi-shadow`)——**U17 未落地**:当前注册名仍取 `micronaut.application.name`(=msgexchange-nextgen),`msgx.service-name` 无运行时消费方(见 §8 与 design.md §9) | | 可观测 | logstash TCP(Async 包装)+ MDC traceId + 自定义健康指示器 | 见 [design.md §8](design.md) | ## 3. 总体拓扑 ``` ┌──────────────────────────────────────────────────┐ │ msgexchange-nextgen │ │ (单实例 · 单写者) │ AODB/上游 ──HTTP──▶│ ingress │ │ InboxService ──事务1──▶ CMINMSGS(原文) │ │ └▶ PROC_STATE(PENDING) │ │ │ │ processing(msgx-pump 线程,严格 FIFO 队头) │ │ Pump ──tick──▶ MessageProcessor │ │ │ │ decode(XmlCodec) │ │ │ │ identity 绑定(I3) │ │ │ │ Handler.decide(纯函数) │ │ │─Schd DNLD──▶ SnapshotFlow(流程4) │ │ │─PUMP_JOB───▶ JobExecutor(同队列,决策1) │ │ │ │ │ ├────Redis Lua──▶ Redis flightInfo(A权威) │ │ └──事务2──▶ MSG_EVENT(outbox)+ 回填+SUCCEEDED│ │ │ │ delivery(msgx-dispatcher 线程,每 target FIFO) │ │ Dispatcher ──逐条──▶ Kafka(msg) │ │ └─flushSchd 聚合─▶ Kafka(schd) │ │ (阶段 B 追加:ES flight_hts → Redis 投影删除) │ └──────────────────────────────────────────────────┘ │ │ ▼ ▼ 下游 Kafka topic Eureka / logstash ``` 要点: - **两条专用 daemon 单线程**(`msgx-pump` / `msgx-dispatcher`)由 `PipelineLifecycle` 在 `ServerStartupEvent` 后拉起,不占用 Netty event loop;停机 `requestStop` + interrupt + join(U07)。仅当 `msgx.pipeline.autostart=true` 时装配——生产默认关, 当前属**有意脚手架门禁**(生产可运行需先完成 U05 数据层实装,见 §7)。 - **单写者约束(I5)**:阶段 A 全部 Redis 写集中在主泵线程;实例数必须为 1 (运行期租约/选主保护属 U26,尚未实装,当前靠部署拓扑约束)。 - **统一 FIFO(决策 1)**:定时作业(cron → PUMP_JOB 入队)与消息同队列, 作业产物不绕过队头顺序;job 与队头的先后目前为入队时间近似, 统一序号列属 U15(未实装,见 design.md §9 缺口清单)。 ## 4. 模块职责 | 包 | 职责 | 对应 ACMA-8 | 主要类 | |---|---|---|---| | `ingress/` | 收报事务1:原文落库 + 伴生 PENDING 行;不解析报文 | 流程 1,I3 | `InboxController` `InboxService` | | `processing/` | 主泵:FIFO 领取、解码、identity 绑定、纯函数决策、事务2 | 流程 2/4,I1/I2/I5 | `Pump` `MessageProcessor` `SnapshotFlow` `Identity` `Handler(Registry)` | | `delivery/` | 投递:每 target 严格 FIFO、schd 聚合 | 流程 3 | `Dispatcher` `SchdAggregation` | | `jobs/` | 泵作业:清场/归档/投影重建(经 PUMP_JOB 同队列) | 流程 4/5/7,I4 | `JobExecutor` `HistorySweepJob` `ArchiveJob` `ProjectionRebuildJob` | | `codec/` | XML 解码 + 失败分类(MALFORMED vs CODEC_ERROR) | 决策 4 前置 | `XmlCodec` `DecodeResult` | | `domain/` | 状态机枚举、事件/决策模型、Phase 开关 | I1–I5 | `ProcState` `MsgEvent` `Decision` `MsgKind` | | `infra/` | 仓储接口、重试策略、Redis Lua、stub、健康、日志 | 数据模型节 | 见 design.md | | `config/` | `PipelineProps` 参数表(ACMA-8 参数初值) | — | `PipelineProps` | ## 5. 关键架构决策 | # | 决策 | 落点 | |---|---|---| | D1 | 作业与消息同队列(cron 只经 PUMP_JOB 入队,产物不绕过队头) | `Pump.tick` / `JobExecutor` | | D2 | 阶段 B:ES 投递成功后同线程**同步** enqueue 删除事件(不轮询 ack) | `Dispatcher.tick`(定案 2) | | D3 | schd 唯一出口是 flushSchd 批量聚合(逐条循环显式排除 KAFKA_SCHD) | `Dispatcher.tick`(U06/N03) | | D4 | 未实装 ≠ 非法:无 handler / staging 未实装 → FAILED(UNSUPPORTED) 可重放,绝不写终态 | `MessageProcessor` `SnapshotFlow`(U10/N21) | | D5 | 失败迁移在持有具体 head/batch 的边界完成;loop 只作最后防线,不吞 InterruptedException/Error | `MessageProcessor` `Dispatcher`(U08) | | D6 | 接口驱动 + 假仓储单测;时间一律经可注入 `Clock` | `infra/persistence` `FailureScheduler` | | D7 | stub 装配门禁:`msgx.stubs=true` 才装配内存实装;与 `autostart` 组合支撑 dev 冒烟 | `infra/stub`(U07/U01) | | D8 | 编译期 DI(KSP)+ 启动期冒烟测试锁定 BeanDefinition 生成 | `build.gradle.kts`(U01) | ## 6. 数据边界 - **本仓库 Flyway 只建六张辅助表**:`PROC_STATE` / `MSG_EVENT` / `REF_DATA` / `REQ_TRACK` / `PUMP_JOB` / `FLIGHT_STATE`(`V2.0.0__aux_tables.sql`)。 - **21 类静态主数据 → 独立 PostgreSQL 参考库**(ACM2-11 定案;`datasources.reference` / `StaticRefRepository`):航空公司/航线/机位/登机桥等静态数据迁出 `REF_DATA`; **`REF_DATA` 仅留守 SCHD_GEN 行**(流程 4 gen CAS 与 SUCCEEDED 同事务,见 design.md §2 注); 航班动态权威 Redis(阶段 A flightInfo)**不变**。参考库表结构/迁移/21 类同步实装属阶段 6。 - `CMINMSGS` / `CMINMSGS_HST` / `COUTMSGS` 等 legacy 旧表归 legacy 仓库维护(冻结期), 本仓库不重复声明;**全新空库需先建 legacy schema**,否则收报首句 SQL 报表不存在 (README「数据库初始化」节)。 - 回滚兼容关键:SUCCEEDED 时回填 `CMINMSGS.SUBSYSTEM_*` + `DATE_PROCESSED/STATUS`, 旧系统可按自身语义无缝接管(Runbook 第 7 步)。 ## 7. 两阶段权威与当前就绪度 | 阶段 | 权威 | 投递目标 | 状态 | |---|---|---|---| | A(`msgx.phase=A`) | Redis flightInfo | KAFKA:msg、KAFKA:schd | 管道骨架+重试闭环已实装;Redis Lua/实仓储属 U05/U09 | | B(`msgx.phase=B`) | FLIGHT_STATE + 投影 | + ES:flight_hts、REDIS:flightInfo | 未实施(阶段 2 后) | **就绪度(2026-09-07 复核口径)**:可编译、37 测试全绿、**dev stub 进程级冒烟实测可端到端** (`./gradlew run` 无外部依赖启动 → 收报 200 → `/health` UP,修复记录见 README「进程级 dev 冒烟」); 生产默认配置**不可对外服务**——`autostart=false` 且生产(stubs=false)下仓储无实装、DI 装配 即失败。生产就绪前置:U05(数据层+事务)、U07 fail-fast 定案、U09(快照恢复协议)、 U13(投递毒丸补全)、U15(统一序号)。逐项状态见 ACM2-10「定稿实施计划」。 ## 8. 部署与安全姿态 - **实例数 = 1**(主泵单写者前提);双实例误配当前无运行期防护(U26:租约/DB 锁 + 拒启,未实装)。 - **影子隔离(目标态;U17/U26 未落地,勿按现状引用)**:服务名(`msgexchangeapi-shadow`)+ 独立 schema + Redis key 前缀 + 独立 topic 三层隔离;当前代码仅 `msgx.register-eureka=false` 生效—— Kafka topic 写死字面量 `"msg"`/`"schd"`(Dispatcher)、`FlightRedisClient.eval` 无 key 前缀参数、 服务名未接 `msgx.service-name`(§2)。 - **网络信任模型**:`/cminmsgs/send` 无鉴权(沿用现役内网信任姿态);eureka default-zone 回退 `127.0.0.1:8761`;口令/端点全部环境变量外置(零入库)。安全节细化属 U28。 - **管理端点**:Micronaut 5.1 下 `/env` 默认**禁用**、`/beans` 默认 enabled+sensitive;dev/影子经 顶层 `endpoints.*`(**非** `micronaut.endpoints.*`——实测前缀错误时不生效)放开 `/env`、`/beans` 与 health 明细。工程未引入 micronaut-security,sensitive 的实际拦截行为待 U28 定案。 - **同名单风险**:影子与生产同名同路径会互相收报——切流前必须核对服务名三隔离。 ## 9. 可观测性 - **日志**:logstash TCP JSON 通道(Async + neverBlock 降级,logstash 不可达不阻塞业务线程); 结构化生命周期日志(收报/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 日志为落点。