Files
msgexchange-v2/docs/flight-state.md
T

210 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 航班运行数据接入与当前状态管理设计
本文说明航班当前态的设计逻辑:数据从哪里来、快照账本为什么存在、各表如何配合,以及消息如何完成事务提交和对外投递。
未标注内容是规范要求;“开放项”“未决”“已知偏差”尚未定案或尚未完成。
## 1. 目标与边界
系统从共享 MySQL 信箱接收 AODB 报文,在本地数据库维护航班当前态,再通过 Kafka 发布状态变化。
设计目标:
1. **当前态唯一**:AODB 是业务事实来源,本地数据库是本模块唯一查询源。
2. **严格有序**:单活动主泵按信箱 FIFO 处理消息。
3. **原子提交**:航班状态、快照账本、处理结果和 outbox 在同一事务提交。
4. **可恢复**:提交后的信箱回填和 Kafka 投递可独立重试,不重算业务。
5. **完整保存**:变长资源使用明细表,不用固定槽位截断。
本模块只同步报文和维护当前态,不推导取消、延误、备降、返航等业务状态。旅客服务状态不在范围内。
生产环境同一时刻只能有一个活动主泵。`PIPELINE_LOCK` 可以串行化数据库事务,但不能代替消息认领、实例选主和故障切换。
## 2. 数据来源与处理方式
| 来源 | 作用 | 处理方式 |
|---|---|---|
| SCHD DNLD / RESP | 建立某个 Operation Day 的完整基准 | 更新名单、写入航班、差删消失航班、推进快照版本 |
| SCHD ADFT | 新增或更新单个临时航班 | 不换代、不差删、不完成请求 |
| FLOP | 更新单个航班的运行变化 | 在当前态上合并,不修改 Operation Day |
DNLD/RESP 是完整快照:未发送的可选字段或集合表示上游当前没有该数据,应清除本地旧值。FLOP 是增量事件:未发送字段保持不变。
**开放项**:ADFT 的缺失字段表示清除还是保持不变,尚待确认。FLOP 集合表示完整集合还是单次变化,也须逐类确认(ACM2-31#10)。
## 3. 核心数据模型
### 3.1 表及职责
| 表 | 粒度 | 职责 |
|---|---|---|
| `FLIGHT_SCHD` | 每个 `FLID` 一行 | 保存标量当前态、`OPERATION_DAY``STATE_VERSION` 和最近消息 ID |
| 8 张资源明细表 | 每个 `FLID` 多行 | 保存登机门、值机柜台、行李转盘、计划机位、滑槽、延误、靠撤桥、轮挡 |
| `FLIGHT_ROUTE_POINT` | 每个 `FLID` 多行 | 保存 ROUT/ERUT 路线点,以 `ROUTE_KIND` 区分 |
| `SCHD_GEN` | 每个 Operation Day 一行 | 保存当前快照的 `VERSION``LAST_MESSAGE_ID` |
| `SCHD_GEN_FLID` | 每个 Operation Day、每个 `FLID` 一行 | 保存当前快照的完整航班名单 |
| `PIPELINE_LOCK` | 单行 | 串行化状态写事务 |
| `PROC_STATE` | 每条消息一行 | 保存处理状态和重试结果 |
| outbox 表 | 每个待投递事件一行 | 保存状态事件、变更通知和删除通知 |
| `REQ_TRACK` / `COUTMSGS` | 每个请求一行 | 保存请求状态和请求出站消息 |
| `BACKFILL_TODO` | 每个待补偿回填一行 | 保存共享信箱回填任务 |
9 张明细表承载 10 类集合,因为 ROUT 和 ERUT 共用路线表。`ORDINAL` 保留输入顺序;`SOURCE_SEQ` 只保存上游序号,不作为唯一键或去重键。
### 3.2 完整当前态
一个航班的完整当前态为:
```text
FLIGHT_SCHD 主行
+ 该 FLID 在 9 张明细表中的全部记录
= 航班完整当前态
```
写入前先在内存中生成完整新状态,再更新主表和明细表。明细集合按组先删后插,保证数据库与内存状态一致。展示视图只是简化投影,不是权威状态,也不能用于完整数据比对。
### 3.3 Operation Day
`OPERATION_DAY`**Operation Day(运营日)**,表示航班在运行保障业务上的日期,不是报文接收日或数据库落库日。
- DNLD/RESP:取快照指定的 Operation Day。
- FLOP:保留航班当前 Operation Day。
- ADFT 或先于快照到达的 FLOP:无法取得时暂存 `NULL`,后续由快照补齐。
`OPERATION_DAY=NULL` 只表示运营日尚未取得,不表示航班没有运营日。
## 4. 快照账本设计
仅保存 `FLIGHT_SCHD` 无法判断上一份快照包含哪些航班,也无法安全差删、识别最近快照重放或防止并发推进。因此每个 Operation Day 使用两张账本表:
```text
SCHD_GEN 保存当前快照版本和最近消息 ID
SCHD_GEN_FLID 保存当前快照的完整 FLID 名单
```
`SCHD_GEN.VERSION` 是运营日快照版本;`FLIGHT_SCHD.STATE_VERSION` 是单航班状态版本。FLOP 只推进后者,不修改快照账本。
同一运营日可以接收多份快照。每次成功提交都会替换名单并将快照版本加一;失败或最近快照重放不加版本。
## 5. 完整快照处理
设本次快照的 Operation Day 为 D,消息 ID 为 M,新名单为 N,账本旧名单为 O。以下步骤在同一事务完成:
1. 锁定 `PIPELINE_LOCK`,读取 D 的账本和旧名单 O。
2. 若 M 等于 `SCHD_GEN.LAST_MESSAGE_ID`,按最近快照重放处理,不改状态、版本和事件。
3. 校验完整性:`RECS` 必须等于实收航班数,范围为 0–9999。
4. 为 N 中每个航班生成完整新状态,写入主表和明细表,设置 `OPERATION_DAY=D`
5. 计算候选删除集合 `O N`
6. 只删除候选集合中当前仍满足 `FLIGHT_SCHD.OPERATION_DAY=D` 的航班,并登记删除事件。
7. 用 N 替换 `SCHD_GEN_FLID` 的旧名单。
8. 通过 CAS 将 `SCHD_GEN.VERSION` 加一,并记录 M。
9. 登记状态事件和 `PROC_STATE=SUCCEEDED`,提交。
任一步失败,状态、名单、版本、处理结果和事件全部回滚。
### 5.1 差删为什么检查 Operation Day
旧名单只能说明“上一份 D 快照包含该航班”,不能说明航班现在仍属于 D。例如 C 先在 9 月 9 日名单中,随后被 9 月 10 日快照接管。新的 9 月 9 日快照不含 C 时,不能删除已经属于 9 月 10 日的 C。
最终删除集合为:
```text
(旧名单 O 新名单 N)
∩ 当前 OPERATION_DAY 仍等于 D 的航班
```
这就是名单账本和主表 Operation Day 必须同时存在的原因。
### 5.2 Operation Day 回退风险
差删检查只能防止误删,不能防止迟到的旧日快照把 `OPERATION_DAY` 从新日改回旧日,也不能防止旧字段覆盖新状态。
**开放项**:运营日迁移的时序规则尚未定案;当前不承诺 Operation Day 只向前迁移(ACM2-31#9)。
### 5.3 重放边界
- `identity_key` 负责入口消息去重。
- `SCHD_GEN.LAST_MESSAGE_ID` 只识别该运营日最近一份快照的重复执行。
- 事务失败后的同消息重试会重新执行完整事务。
- 更早的历史快照不会被 `LAST_MESSAGE_ID` 拦截。
人工重放历史快照必须带明确的版本和 Operation Day 策略,否则可能覆盖新状态。
## 6. 动态事件处理
FLOP 不建立快照,也不修改快照名单:
1. 根据 `FLID` 读取完整当前态。
2. 合并本次变化;未出现字段保持不变。
3. 保留当前 `OPERATION_DAY`;无法取得时写 `NULL`
4.`STATE_VERSION` 加一,写入主表和明细表。
5. 同事务登记 `KAFKA_MSG``KAFKA_SCHD` 和处理结果。
**已知偏差**:GTDT 当前按登机门集合 Replace 处理,但 FLOP 集合的完整/增量语义尚未确认。动态事件还在锁外生成事件预览、事务内再次计算状态;目标是只在事务内生成一次新状态,供落库和事件共用。
## 7. 请求与提交后处理
### 7.1 请求匹配
请求通过 `REQ_TRACK` 登记,通过 `COUTMSGS` 发送。同类请求只保留一条有效记录,新请求将旧请求置为 `EXPIRED`
RESP 按(Operation Day、发送方、请求类型)完成最新一条 `PENDING` 请求。迟到或重复应答仍可更新快照,但不重复完成请求。DNLD 仅在 Operation Day 匹配时完成请求。协议没有请求关联号,因此该匹配规则仍需验收。
### 7.2 信箱回填
业务事务提交后回填共享 MySQL 信箱。回填失败不得把已提交的 `SUCCEEDED` 改回 `FAILED`
**已知偏差**:当前只在回填失败后写 `BACKFILL_TODO`。若提交后、写待办前崩溃,任务无法恢复。目标是在业务事务中预先登记回填意图。
### 7.3 Kafka 投递
`KAFKA_SCHD` 是航班整态,不是补丁。Dispatcher 合并同一 `FLID` 的未发事件,读取数据库当前态并按最新 `STATE_VERSION` 输出;中间版本不逐条发送。
- 消费端按 `(FLID, STATE_VERSION, UPDATED_AT)` 拒绝旧状态覆盖新状态。
- `KAFKA_MSG` 只通知变化,不承载完整状态。
- 两个主题不保证跨主题顺序。
- 整态中键缺失表示该数据不存在,消费端必须删除旧值。
差删使用自定义 tombstonevalue 非 null,载荷只有 `FLID``DELETED`。它与删除操作同事务登记,投递失败持续重试。
**开放项**:删除事件没有版本号;迟到删除、删除后恢复和 `STATE_VERSION` 连续性尚未解决(ACM2-31#11)。
## 8. 生命周期与历史
本模块只保存当前态:不保存状态历史,不逐条投递中间版本,不提供历史查询,也不承诺保存原始报文。
`OPERATION_DAY=NULL` 的航班不参与快照差删。定期作业可清理超过 N 天且未被请求或快照名单引用的记录;N 默认 7 天,启用前须由消费方确认。
**开放项**:历史运营日账本和航班没有清理规则。清理不能破坏差删依赖的旧名单和 Operation Day。
## 9. 失败与读取
### 9.1 失败处理
| 错误 | 处理 | 队头行为 |
|---|---|---|
| 非法报文、数量不符、容量超限 | `DEAD`,不重试 | 立即释放 |
| 未支持类型 | `FAILED(UNSUPPORTED)` | 达到阈值后转 `DEAD` |
| 数据库或内部故障 | `FAILED(INFRA)`,退避重试 | 成功或超限前阻塞 |
| 快照 CAS 冲突 | 回滚并重读重试 | 成功前阻塞 |
最大阻塞时限、旁路规则和告警阈值仍是开放项(ACM2-31#13)。SEQN 的唯一范围、重置和回绕策略也未定义(ACM2-31#12)。
### 9.2 读取
完整航班状态必须在一致性读事务中读取主表和全部明细表。当前逐航班读取会产生 N+1 查询,且主表和明细表可能不在同一数据库快照。
**待补**:按 FLID 批量加载明细,并为查询和 Dispatcher 建立一致性读边界。`GET /all/flights` 尚未交付。
## 10. 当前范围与关键开放问题
当前只有 PostgreSQL 链路和 DNLD、GTDT 的部分流程接通。Oracle 11g、RESP、ADFT、请求出站、完整解析、回填崩溃恢复和一致性读取仍未完成。
关键开放问题由 ACM2-31 跟踪:FLID 生命周期、快照与动态事件顺序、Operation Day 回退、FLOP 集合语义、删除事件版本、幂等键生命周期、FIFO 参数、一致性读取、快照完整性校验和 Oracle 适配。
### 规则 M:同日快照与动态事件的优先级
当前候选规则是:同一 Operation Day 内,完整快照覆盖此前动态事件写入的重叠字段;快照未出现字段按完整快照语义清除。
该规则要求快照与动态事件的投递顺序等于上游发送顺序。协议尚未证明这一点,因此规则 M 不是已成立的不变量,也不能作为对外承诺(ACM2-31#8)。