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 历史积压