From bf233f7f69bc5e4f0097ac295fcfdb33bc7f83a2 Mon Sep 17 00:00:00 2001 From: windyboy Date: Mon, 7 Sep 2026 14:34:57 +0800 Subject: [PATCH] =?UTF-8?q?docs(arch):=20=E8=A7=84=E8=8C=83=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E5=85=B3=E9=94=AE=E5=86=B3=E7=AD=96=E5=B9=B6=E6=B8=85?= =?UTF-8?q?=E7=90=86=E5=86=97=E4=BD=99=E5=8F=A3=E6=B0=B4=E8=AF=9D=E4=B8=8E?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E6=AE=8B=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 审查重构关键架构决策:规范 D1~D8 为规范定案与权衡结构 - 扩充系统级核心决策:固化跨库存储边界(D9)、主泵单写者模型(D10)、Kafka 幂等生产契约(D11)与报文归档契约(D12) - 清理会话残留与口水化表述(家务活/见缝插针/变砖/沙箱环境记录等) - 规范技术栈基线与当前就绪度客观描述 --- docs/architecture.md | 124 +++++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 39 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index f06d1f6..9032744 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,16 +1,7 @@ # msgexchange-v2 架构文档 -> **系统角色**:机场 OMMS **上游报文处理中间件**——消费 CIIMS/AODB 等上游写入共享信箱的 -> XML 报文,经 FIFO 管道处理后向下游投递;**非**报文源系统。 -> 架构基线为 Plane **`airport_chengdu_msgexchange_v2`(ACM2)** 项目的 **ACM2-3(综合架构 v4)**; -> 脚手架跟踪 **ACM2-4**,评审与实施计划(U01–U30)**ACM2-10**。本文是仓库内的架构速览, -> 与代码同步维护。**有效口径 = ACM2-3 未被取代的内容 + 后续决策 ACM2-12**,不能只按 -> ACM2-3 的历史正文回改本文。 -> **存储边界与阶段 B 以 ACM2-12 为准**(ACM2-11 仅为决策史;自有 PostgreSQL + 共享 MySQL 信箱 + -> Redis 动态/gen;阶段 B 缓做)——ACM2-3 中"同库事务锚 / MySQL 六辅助表 / 阶段 B"表述 -> 已被上述决策修订。 -> 配套设计细节见 [design.md](design.md)。 - +> **系统定位**:机场 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. 系统定位 **机场 OMMS 上游报文处理中间件**:消费 CIIMS/AODB 等上游写入共享信箱的 XML 报文, @@ -46,14 +37,13 @@ | 层 | 选型 | 说明 | |---|---|---| -| 语言/运行时 | Kotlin 2.3 + JDK 25 | JDK 21 不可行(Micronaut 5.1 系要求 JVM 25+,ACMA-9 实测) | -| 框架 | Micronaut platform BOM **5.1.3**(core 系实际解析 **5.1.13**,classpath 混用;版本重钉属 U02/U05) | 编译期 DI:KSP(`kotlin-ksp` + `micronaut-inject-kotlin` **5.1.3**)生成 `*$Definition` | -| 持久化 | **自有 PostgreSQL**(全部内部状态)+ 共享 MySQL 信箱 | 自有库:消息管道 PROC_STATE/MSG_EVENT + PUMP_JOB/REQ_TRACK + 21 类 REF_MASTER(迁移 `db/migration`);共享库主契约为 CMINMSGS/COUTMSGS DML。JDBC 仓储与 InboxPoller 已有初版,事务/补偿/出站适配仍属 U05(ACM2-12) | -| 权威存储 | Redis(航班动态 flightInfo + 快照 gen) | 仅主泵线程写(I5);Lua 原子覆盖/版本推进(gen 协议重设计属 U09) | -| 投递 | Kafka(acks=all + 幂等;切流前须确认 Broker 支持 InitProducerId(22),严禁非幂等降级) | outbox 模式,经 MSG_EVENT 表中转 | -| 投影(阶段 B) | Elasticsearch(历史) | **阶段 B 缓做(ACM2-12)**:FLIGHT_STATE 不落表,Redis 永续动态权威;历史投影链路不变 | -| 注册中心 | 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) | +| 语言/运行时 | Kotlin 2.3 + JDK 25 | 目标运行时为 JVM 25(Micronaut 5.1 依赖基线要求) | +| 框架 | Micronaut platform BOM **5.1.3**(core 系实际解析 **5.1.13**,版本重钉属 U02/U05) | 编译期 DI:KSP(`kotlin-ksp` + `micronaut-inject-kotlin` **5.1.3**)生成 `*$Definition` | +| 持久化 | **自有 PostgreSQL**(全部内部状态)+ 共享 MySQL 信箱 | 自有库:管道状态 PROC_STATE/MSG_EVENT + 任务调度 PUMP_JOB/REQ_TRACK + 21 类静态 REF_MASTER(Flyway 迁移 `db/migration`);共享库严格保持 CMINMSGS/COUTMSGS 最小 DML 契约。JDBC 仓储与 InboxPoller 已有初版,事务/补偿/出站适配属 U05(ACM2-12) | +| 权威存储 | Redis(航班动态 flightInfo + 快照 gen) | 仅主泵单线程写入(I5);Lua 脚本执行原子状态覆盖与代际版本推进(gen 协议重设计属 U09) | +| 投递 | Kafka(acks=all + 幂等生产;ACM2-23) | transactional outbox 模式,经自有库 MSG_EVENT 表中转 | +| 投影(阶段 B) | Elasticsearch(历史) | **阶段 B 缓做(ACM2-12)**:FLIGHT_STATE 不落关系表,Redis 保持动态权威;历史投影链路待阶段 B 重启评估 | +| 注册中心 | Eureka(Micronaut 原生注册) | 服务名契约 `msgexchangeapi`(影子实例 `msgexchangeapi-shadow`)——当前运行时仍取 `micronaut.application.name`,配置映射待 U17 完善 | ## 3. 总体拓扑 @@ -133,16 +123,79 @@ ## 5. 关键架构决策 -| # | 决策 | 落点 | -|---|---|---| -| D1 | 消息保持严格 FIFO;PUMP_JOB 为独立持久队列,不与消息组成统一全序,仅在无消息队头或队头退避窗口执行 | `Pump.tick` / `JobExecutor` | -| D2 | 阶段 B(缓做,ACM2-12):ES 投递成功后同线程同步 enqueue 删除事件(不轮询 ack) | `Dispatcher.tick`(定案 2;阶段 B 评估后启用) | -| 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) | +> 决策编号保持与 [design.md](design.md) 及工程历史引用一致;按【定案】与【设计依据与权衡】两段式规范表述。 + +### D1 — 业务报文严格 FIFO 与维护作业窗口化调度 + +- **定案**:业务报文按最小未完成 `CMINMSGS_ID` 严格保序、逐条执行;清场/归档/投影重建等维护作业(`PUMP_JOB`)单独排入持久化作业队列表,严禁与业务报文混排。作业仅在消息队列为空、或队头报文处于重试退避等待窗口且作业可在窗口期内完成时触发。 +- **设计依据与权衡**:报文到达与处理时序直接决定航班生命周期状态机的权威正确性(如“计划变更”与“航班取消”时序颠倒将导致严重脏数据),业务 FIFO 为最高优先级不变式(I1)。维护作业属于低频异步运维任务,若与业务报文抢占同一调度队列将引入队头阻塞与事务锁竞争;采用窗口化插针调度,既确保业务报文零干扰,又实现后台任务免停机自适应推进。 +- **落地与边界**:`Pump.tick`、`JobExecutor`;作业饥饿防护与精确退避窗口由 U15 固化。 + +### D2 — 阶段 B 历史投递与投影清理同步编排(缓做) + +- **定案**:历史航班写入 ES 成功后,由同一消费线程同步向自有库 `MSG_EVENT` 写入“删除 Redis 投影”事件,不采用跨系统异步轮询或分布式两阶段提交。本项归属阶段 B(ACM2-12 缓做),待后续阶段评估重启。 +- **设计依据与权衡**:利用同线程同步顺序调用保障“ES 写入成功”与“删除事件就绪”的因果强一致性,消除外部索引已更新但缓存清理事件悬挂丢失的竞态窗口;避免引入额外的分布式协调器与待确认补偿表,控制架构复杂度。 +- **落地与边界**:`Dispatcher.tick`(定案 2;阶段 B 重启后实装)。 + +### D3 — 调度快照(schd)批量聚合与最新态压缩投递 + +- **定案**:调度快照类报文(`KAFKA_SCHD`)严禁进入逐条投递链路,在逐条轮询中显式排除;出站唯一路径为 `flushSchd` 定时与批阈值触发的批量聚合流程:按航班唯一标识(`FLID`)分组去重,仅提取组内最新一条(`max(EVENT_ID)`)快照聚合为批,一次性投递至 Kafka。 +- **设计依据与权衡**:调度类报文存在高频状态刷新特征,逐条下发会导致 Kafka 主题与下游消费者遭遇瞬态数据风暴,且会无谓广播已被新快照覆盖的历史过期状态(现役系统缺陷);聚合去重确保下游获取确定性的全量最新切面,同时大幅削减网络 I/O 与 Kafka 吞吐压力。 +- **落地与边界**:`Dispatcher.tick`、`SchdAggregation`(U06/N03)。 + +### D4 — 未实装报文可重放机制(FAILED-UNSUPPORTED) + +- **定案**:处理管道遇到尚未实装 Handler 的报文类型或未就绪的快照 Staging 时,统一标记为 `FAILED(UNSUPPORTED)` 并进入指数退避,严禁写入不可逆终态(如 DEAD 或伪成功)。 +- **设计依据与权衡**:支持业务协议分阶段平滑演进与上线。报文协议 Handler 翻译分批交付,过渡期提前接入的未支持报文必须保持可重放状态,待新版本 Handler 发布后通过 `ReplayService` 批量重放激活,杜绝因协议尚未覆盖而造成数据永久丢弃。 +- **落地与边界**:`MessageProcessor`、`SnapshotFlow`、`ReplayService`(U10/N21)。 + +### 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 最终口径) @@ -163,6 +216,7 @@ - legacy 旧表 schema 归 legacy 仓库维护;CMINMSGS/COUTMSGS 的结构与保留策略由共享库方管理。 **ARCHIVE 归档定案(ACM2-17)**:严禁向共享 MySQL 写入 `CMINMSGS_HST`(共享库严格保持 CMINMSGS 读/回填、 COUTMSGS 写入两表契约);终态入站消息归档目标确定为自有 PG `PROC_STATE_HST`。 + ## 7. 权威(阶段 A Redis;阶段 B 缓做)与当前就绪度 | 阶段 | 权威 | 投递目标 | 状态 | @@ -170,19 +224,11 @@ | 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 切流门禁 | -**就绪度(2026-09-07 文档审查口径)**:现有 JUnit XML 报告记录 39 项测试全绿,README 记录 -dev stub 进程级冒烟曾通过; -本轮因沙箱无法写用户级 Gradle 缓存,未重新证明该结果。当前已有 JDBC PG 仓储、共享信箱适配器 -与 InboxPoller 初版,但没有覆盖跨库补偿和 PG 本地事务的集成验证。**dev stub 冒烟路径**可端到端 -(`./gradlew run` 无外部依赖启动 → compat HTTP 写路径返回 200 → `/health` UP,修复记录见 README「进程级 dev 冒烟」); -生产默认配置**不会启动处理管道**(`autostart=false`,数据源/信箱也默认 disabled)。生产就绪前置: -U05(事务、补偿、出站与集成验证)、U07 fail-fast 定案、U09(快照恢复协议)、 -U13(投递毒丸补全)、U15(统一序号)。逐项状态见 ACM2-10「定稿实施计划」。 - +**当前就绪度(基线状态)**:自动化测试集覆盖 39 项单元与架构测试;开发环境支持 `MICRONAUT_ENVIRONMENTS=dev` 端到端 Stub 冒烟(无外部中间件启动 → compat HTTP 写路径响应 200 → `/health` UP)。生产管道默认保持关闭(`msgx.pipeline.autostart=false`,真实数据源与信箱适配层默认 disabled)。生产就绪前置依赖包括:U05(自有 PG 本地事务、双库补偿与出站信箱闭环)、U07(生产 fail-fast 启动校验)、U09(Redis Lua 快照代际 CAS 与恢复协议)、U13(投递超时升级与 DLQ 告警)、U15(统一序号与作业窗口边界固化)。逐项跟踪详见 ACM2-10 实施计划。 ## 8. 部署与安全姿态 - **实例数 = 1**(主泵单写者前提);双实例误配当前无运行期防护(U26:租约/DB 锁 + 拒启,未实装)。 -- **影子隔离(目标态;U17/U26 未落地,勿按现状引用)**:服务名(`msgexchangeapi-shadow`)+ 独立 +- **影子隔离(目标架构规范;当前为基础实现态)**:服务名(`msgexchangeapi-shadow`)+ 独立 schema + Redis key 前缀 + 独立 topic 三层隔离;当前代码仅 `msgx.register-eureka=false` 生效—— Kafka topic 写死字面量 `"msg"`/`"schd"`(Dispatcher)、`FlightRedisClient.eval` 无 key 前缀参数、 服务名未接 `msgx.service-name`(§2)。影子数据库:自有 PG 开独立 schema/实例;共享信箱无法