11 KiB
航班运行数据接入与当前状态管理设计
本文说明航班当前态的设计逻辑:数据从哪里来、快照账本为什么存在、各表如何配合,以及消息如何完成事务提交和对外投递。
未标注内容是规范要求;“开放项”“未决”“已知偏差”尚未定案或尚未完成。
1. 目标与边界
系统从共享 MySQL 信箱接收 AODB 报文,在本地数据库维护航班当前态,再通过 Kafka 发布状态变化。
设计目标:
- 当前态唯一:AODB 是业务事实来源,本地数据库是本模块唯一查询源。
- 严格有序:单活动主泵按信箱 FIFO 处理消息。
- 原子提交:航班状态、快照账本、处理结果和 outbox 在同一事务提交。
- 可恢复:提交后的信箱回填和 Kafka 投递可独立重试,不重算业务。
- 完整保存:变长资源使用明细表,不用固定槽位截断。
本模块只同步报文和维护当前态,不推导取消、延误、备降、返航等业务状态。旅客服务状态不在范围内。
生产环境同一时刻只能有一个活动主泵。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 完整当前态
一个航班的完整当前态为:
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 使用两张账本表:
SCHD_GEN 保存当前快照版本和最近消息 ID
SCHD_GEN_FLID 保存当前快照的完整 FLID 名单
SCHD_GEN.VERSION 是运营日快照版本;FLIGHT_SCHD.STATE_VERSION 是单航班状态版本。FLOP 只推进后者,不修改快照账本。
同一运营日可以接收多份快照。每次成功提交都会替换名单并将快照版本加一;失败或最近快照重放不加版本。
5. 完整快照处理
设本次快照的 Operation Day 为 D,消息 ID 为 M,新名单为 N,账本旧名单为 O。以下步骤在同一事务完成:
- 锁定
PIPELINE_LOCK,读取 D 的账本和旧名单 O。 - 若 M 等于
SCHD_GEN.LAST_MESSAGE_ID,按最近快照重放处理,不改状态、版本和事件。 - 校验完整性:
RECS必须等于实收航班数,范围为 0–9999。 - 为 N 中每个航班生成完整新状态,写入主表和明细表,设置
OPERATION_DAY=D。 - 计算候选删除集合
O − N。 - 只删除候选集合中当前仍满足
FLIGHT_SCHD.OPERATION_DAY=D的航班,并登记删除事件。 - 用 N 替换
SCHD_GEN_FLID的旧名单。 - 通过 CAS 将
SCHD_GEN.VERSION加一,并记录 M。 - 登记状态事件和
PROC_STATE=SUCCEEDED,提交。
任一步失败,状态、名单、版本、处理结果和事件全部回滚。
5.1 差删为什么检查 Operation Day
旧名单只能说明“上一份 D 快照包含该航班”,不能说明航班现在仍属于 D。例如 C 先在 9 月 9 日名单中,随后被 9 月 10 日快照接管。新的 9 月 9 日快照不含 C 时,不能删除已经属于 9 月 10 日的 C。
最终删除集合为:
(旧名单 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 不建立快照,也不修改快照名单:
- 根据
FLID读取完整当前态。 - 合并本次变化;未出现字段保持不变。
- 保留当前
OPERATION_DAY;无法取得时写NULL。 - 将
STATE_VERSION加一,写入主表和明细表。 - 同事务登记
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只通知变化,不承载完整状态。- 两个主题不保证跨主题顺序。
- 整态中键缺失表示该数据不存在,消费端必须删除旧值。
差删使用自定义 tombstone:value 非 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)。