feat(processing): 领域/投影/终态三步提交与 FlightProjectionPort(ACM2-79)

PIPELINE_LOCK 跨 Redis 写;MSG_EVENT HELD→PENDING;Redis 失败不终态不投递;V3 迁移。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
windyboy
2026-09-21 15:24:34 +08:00
co-authored by Cursor
parent 891088235c
commit 2c3d6af309
28 changed files with 928 additions and 167 deletions
+12 -8
View File
@@ -60,13 +60,15 @@ msgexchange-v2 怎么处理报文:记录模型、状态机、事务边界、
→ FAILED(等待退避重试)
→ DEADMALFORMED / PROTOCOL / EXHAUSTED,均需人工处置)
投递:PENDING → SENT
→ PENDING(退避后重试)
→ DEAD(重试耗尽,记录保留作死信)
投递:HELD → PENDING → SENT
→ PENDING(退避后重试)
→ DEAD(重试耗尽,记录保留作死信)
```
`SUCCEEDED`/`SKIPPED`/`DEAD` 是终态,不挡后续;`FAILED` 仍占队头。用尽重试 → `DEAD(EXHAUSTED)`。错误分类与重放白名单见 [reference.md](reference.md)。
`HELD` 是尚未放行的待发事件:领域事务登记,投影写成功后由终态事务转 `PENDING`(「事务边界」)。
## 4. 收报
### 4.1 收报
@@ -116,9 +118,9 @@ processOne(head)
载荷缺失 → DEAD(MALFORMED)
整包协议拒绝 → DEAD(PROTOCOL),不落半包
5. 业务型成功,分三步(INV-3、INV-10):
① 事务提交:航班变更 + 待发事件
① 事务提交:航班变更 + 待发事件(登记为 HELD,投递领不到)
② 写 Redis 航班快照;失败 → 保持未完成,下轮重处理
③ 事务提交:SUCCEEDED + 回填意图
③ 事务提交:SUCCEEDED + 回填意图 + 待发事件放行(HELD → PENDING
6. 结束:主泵不做回填;回填意图已随终态落库,由扫描补写信箱标记
```
@@ -130,15 +132,17 @@ processOne(head)
|---|---|---|---|---|
| 收报入队(`insertIfAbsent`) | 是 | 否 | 否 | 同库事务 |
| 身份首次绑定 | 否 | 否 | 否 | 单语句 + 唯一约束 |
| 业务型领域变更(航班变更 + 待发事件) | 是 | 是 | 是 | 同库事务(`INV-3` |
| 业务型领域变更(航班变更 + 待发事件) | 是 | 是 | 是 | 同库事务(`INV-3`;事件落 `HELD` |
| Redis 航班快照写 | 否 | 是(处理步骤锁跨越本步) | 否 | 外部副作用,不在 PG 事务内;写成功是终态事务的前置(`INV-10` |
| 业务型终态(`SUCCEEDED` + 回填意图) | 是 | 是 | 否 | 同库事务(`INV-3` |
| 业务型终态(`SUCCEEDED` + 回填意图 + 事件放行) | 是 | 是 | 否 | 同库事务(`INV-3` |
| 非业务型终态(`MALFORMED` / `PROTOCOL` / `SKIPPED` / `EXHAUSTED`) | 否 | 否 | 否 | 单语句(终态与回填意图同一条 UPDATE) |
| 航班历史清理的物理删除 | 是 | 是(落实 `US-14` AC4) | 是 | 同库事务:复查判据 + 历史写入成功后删除 |
| 回填(信箱标记 + `BACKFILL_AT`) | 否 | 否 | 否 | 跨库两次单写;幂等可重跑 |
| 人工重放(批量改回 `PENDING`) | 否 | 否 | 否 | 单语句批量;`MessageLifecycleGate` 与回填互斥 |
航班变更与终态不在同一事务,中间夹 Redis 写(`INV-3``INV-10`)。`PIPELINE_LOCK` 跨 Redis 写,与历史清理互斥(`US-14` AC4);无第二写者时不额外串行。
航班变更与终态不在同一事务,中间夹 Redis 写(`INV-3``INV-10`)。三步跑在同一条数据库连接上:进处理步骤先在该连接取会话级咨询锁(`pg_advisory_lock`),两次事务复用这条连接,整步结束才解锁。行锁随提交释放,单靠它盖不住提交之后的 Redis 写,所以第二个写者(历史清理)在自己的事务里也要先取同一把咨询锁,再照旧锁 `PIPELINE_LOCK` 行,两边因此在整段内互斥(`US-14` AC4);无第二写者时不额外串行。
投影失败时领域事务已提交而终态未写,`MSG_EVENT` 行停在 `HELD`:投递只领 `PENDING`Kafka 不会先于处理完成发出(`INV-3`)。下一轮重处理按 `MSG_ID` 先清掉上一轮的 `HELD` 行再重新登记,崩溃重启后既不留悬挂行,也不把同一次变更投两遍。
### 5.4 历史积压
+4 -1
View File
@@ -54,6 +54,8 @@
| `msgx.identity.include-day-boundary` | `false` | 去重时是否把日期算进消息身份;受 `C-3` 约束,不能随意改变 | 默认关闭 |
| `msgx.health.backlog-cache-ttl-ms` | `30000` ms | `/health``/metrics` 共用的未处理消息统计最多缓存多久;`0` 表示每次重算 | 暂定 |
| `msgx.history.history-store-enabled` | `false` | 是否启用历史航班写入;关闭时不删除实时航班 | 默认关闭 |
| `msgx.redis.enabled` | `false` | 是否把航班快照写进 Redis 查询投影;关闭时投影写入是空操作(`G-REDIS-PROJECTION` | 默认关闭 |
| `msgx.redis.flight-key` | `flightInfo` | 航班快照所在的 Redis 哈希键,字段名是 `FLID` | 沿用旧系统 |
| `msgx.history.planned-age-days` | `3` 天 | `US-14` AC2 条件 1:计划时间早于当前超过该天数视为已结束 | `US-14` AC2 |
| `msgx.history.cancelled-hours` | `1` 小时 | `US-14` AC2 条件 2:取消时间早于当前超过该窗口视为已结束 | `US-14` AC2 |
| `msgx.history.diverted-hours` | `1` 小时 | `US-14` AC2 条件 3:备降且计划时间早于当前超过该窗口;备降依据 `FDIV``DDES`/`DDIR`)落地前不参与判定(`Q3`、ACM2-100 | `US-14` AC2 |
@@ -90,7 +92,7 @@
**缺值**:带环境变量占位的必需键(`MSGX_PG_URL``MSGX_MAILBOX_URL``MSGX_KAFKA_SERVERS` 等)缺失时由 DI 解析失败拒绝启动,无需自检代码。
**显式开关(非配置错误)**`msgx.stubs``datasources.default.enabled``mailbox.shared-mysql.enabled``msgx.history.history-store-enabled``msgx.pipeline.autostart`。默认关闭是"未接入"的显式声明,不判违规;只有 `msgx.pipeline.autostart=true` 才要求依赖齐备。Kafka 运行时不可达不是配置错误,归 `/health` 与投递指标告警(`OPS-2`)。
**显式开关(非配置错误)**`msgx.stubs``datasources.default.enabled``mailbox.shared-mysql.enabled``msgx.history.history-store-enabled``msgx.redis.enabled``msgx.pipeline.autostart`。默认关闭是"未接入"的显式声明,不判违规;只有 `msgx.pipeline.autostart=true` 才要求依赖齐备。Kafka 运行时不可达不是配置错误,归 `/health` 与投递指标告警(`OPS-2`)。
## 指标与健康
@@ -121,6 +123,7 @@
| 读取信箱与 HTTP 入口 | `ingress/InboxPoller.kt``ingress/InboxController.kt` |
| XML 解析 | `codec/JacksonXmlCodec.kt``codec/SisWireMapper.kt` |
| 顺序处理与航班变更 | `processing/Pump.kt``processing/DynamicProcessors.kt``processing/ScheduleProcessor.kt` |
| 三步提交与 Redis 投影 | `processing/FlightCommit.kt``processing/FlightProjection.kt``infra/projection/` |
| 写回信箱与发送 Kafka | `processing/BackfillService.kt``delivery/Dispatcher.kt``infra/kafka/KafkaDeliveryPort.kt` |
| 定时任务 | `jobs/JobRunner.kt``jobs/HistorySweepJob.kt``jobs/EventCleanupJob.kt` |
| 数据库读写与手动重试 | `infra/persistence/jdbc/``infra/retry/` |
+1 -1
View File
@@ -153,7 +153,7 @@
| `G-FLOP-UNMAPPED` | [XSD](legacy/unisysaodbsis.xsd) FLOP 字段映射不全 | `US-05` |
| `G-MAFL` | 主航班共享列表未做 | `US-06` AC2 |
| `G-PROC-CLEANUP` | 处理记录到期清理未做 | `US-11` |
| `G-REDIS-PROJECTION` | Redis 快照写入与删除未做 | `US-05``US-06``US-07``US-12` |
| `G-REDIS-PROJECTION` | Redis 快照写入与删除未做:投影端口与三步提交时序已就位,真实 Redis 客户端未接通,日计划覆盖范围的缺席清扫也未做 | `US-05``US-06``US-07``US-12` |
| `G-REF-DATA` | 静态参考数据处理与 admin-api 直读未做 | `US-13` |
| `G-REQ-OPEN-UNIQUE` | 同类型未处理完时不允许再发,未做 | `US-09` |
| `G-REQ-TRACK` | 出站请求跟踪未做 | `US-09` |