依据 ACM2-31 二轮外部评审逐条修正,待协议确认项已对照 SIS_AODB_RMS V0.1 佐证: - 标注约定:区分规范要求与现状/未决披露,杜绝混作依据 - 航班身份:FLID≠航班号,FDEL+ADFT 换名时 FLID 可能不同(规范 §3.18),代码共享独立事件经 MAID/TAID 关联 - FDAY 三日期概念区分(业务日期/快照覆盖日期/名单归属日期);跨日归属回迁列为未决(§3.3 约束 4) - 规则 M 补成立条件标注(快照生成时点 + 投递顺序前提未逐条核实) - 差删完整性前提引协议口径:单包不分段、RECS 记录数、完整快照注释(§3.16.1) - 动态线集合全量/增量语义待逐类确认(CHOT CSNO 实为机位序号 §3.22.2),Replace 全集仅对快照线定案 - tombstone 澄清为自定义删除事件(非 Kafka 原生墓碑),删除版本/世代缺口开放 - 行锁表述纠正(跨实例同样生效,但不实现认领/选主/切换/FIFO);Oracle 空串=NULL 为既定行为,验证目标改为字段语义保真 - ACTT 实际时间口径(进港 ATA/出港 ATD §3.20),修正示例字段名;FLOP 29 类改为规范 §3.19–3.43 实数;99 上限标注协议出处 - 语言修订:主泵/数据一致性比对/JSON 数组/整态替换措辞等;错误矩阵每类错误单一默认路径 - 开放项(回迁防护/动态集合语义/删除世代/幂等键/FIFO 参数/一致性读/TAOP 语义核对等)归集 ACM2-31
48 KiB
航班运行数据接入与当前状态管理设计
本文是航班状态接入与当前态存储的唯一设计与架构规范文档:统一界定系统职责、存储模型、字段语义、状态流转与跨模块不变量。已定决策与演进红线以正文为准;未完成项与开放风险在各节显式标注。
标注约定:正文未加标注的段落是规范要求;凡标注「开放项 / 未决 / 已知偏差 / 待补 / 验收目标 / 现状」的段落,是对当前实现或未定设计的如实披露,不是已交付事实,两类内容不得混作依据。
阅读速览:专用数据与设计逻辑
专用数据与术语
| 数据 / 术语 | 含义与用途 |
|---|---|
| SIS / AODB | SIS 是 AODB(机场运行数据库,上游业务权威)向各子系统分发数据的接口规范(SIS_AODB_RMS V0.1);本系统从共享 MySQL 信箱接收 AODB 报文。共享信箱是接入边界,本地数据库是本模块对外提供当前状态的唯一读取源,不是高于 AODB 的业务权威。 |
FLID |
AODB 分配的航班唯一 ID(Number(1-12),规范 §3.16.2);主表一行对应一个 FLID,明细、状态事件及删除通知均按它归属航班。FLID ≠ 航班号(FLNO 是标量列):关键键字段(航司代码、航班号、航班日期、航班时间、进离港标识)变更时,AODB 以「删除事件 FDEL + 新航班事件 ADFT」重新指名,两个事件的 FLID 可能不同(规范 §3.18);主航班与代码共享航班各有 FLID、事件分别发送,经 MAID/TAID 关联。 |
FLIGHT_SCHD + 9 张明细表 |
一个航班的完整权威态:主表保存标量、异常对象前缀列及文本字段;明细表保存 10 类变长集合,其中 ROUT/ERUT 共用路线表。展示视图只是只读投影。 |
FDAY(名单归属运营日) |
当前负责该航班成员管理与差删的日计划快照所覆盖的运营日(三个日期概念中的「名单归属日期」,区分见 §3.2),不是报文接收日或落库日,也不承诺等同于业务运营日。NULL 表示尚未被任何日计划代收录,不表示航班没有运行日;跨日归属由整包快照更新,动态事件不修改。 |
SCHD_GEN / SCHD_GEN_FLID |
每个运营日的快照账本与当前成员名单。成员资格以名单为准;差删先比较新旧名单,再用当前 FDAY 防止误删已迁往其他运营日的航班。 |
VERSION / STATE_VERSION |
前者是同一运营日的快照版本,每次新快照推进;后者是单个航班的状态版本。运营日、快照版本、航班状态版本是不同维度。 |
identity_key / LAST_MESSAGE_ID |
前者按“发送方|类型|子类型|序号”识别重复业务消息;后者追踪最近一次写入消息,其中快照账本的该字段用于最近一条快照的重放短路,不能拦住任意历史快照。 |
ORDINAL / SOURCE_SEQ |
前者是明细输入顺序(从 1 起),后者保存报文中的源序号。源序号可空、不唯一,资源号也不是去重依据,不能据此把多条分配记录合并为一条。 |
| SCHD:DNLD / RESP / ADFT | 日计划相关报文。DNLD、RESP 按整包快照换代并差删;ADFT 是单航班临时增量,不换代、不差删、不完成请求。当前仅 DNLD 接入,RESP/ADFT 及请求闭环仍有未决项。 |
| FLOP / GTDT | FLOP 是字段级航班运行事件;规范 §3.19–3.43 定义了 25 类航班级事件(AODB→RMS 事件总计 44 类,§3.1–3.44),GTDT(§3.34)是其中的登机门更新。当前仅接入 GTDT;动态事件只表达所携带字段的变化。 |
REQ_TRACK / COUTMSGS |
前者登记请求及其应答、超时状态,后者承担请求出站 outbox;出站发送与超时重发尚未实现。 |
PROC_STATE / BACKFILL_TODO |
前者记录消息处理状态,后者记录共享信箱回填补偿。回填意图与业务终态同事务持久化仍是待补齐目标。 |
KAFKA_SCHD / KAFKA_MSG / tombstone |
分别表示航班整态投影、变更通知、删除通知。整态投递可合并同航班的中间版本,消费端按整态替换处理、不得按补丁合并。本文 tombstone 指自定义删除事件:载荷仅含 FLID 与 DELETED 标记、value 非 null,不是 Kafka 日志压缩语义的原生墓碑消息,Kafka 不会对其执行压缩删除。 |
集合与异常、文本字段的逐项映射见第 6 节;表结构职责与代际记账见第 3 节;一个运营日的数据全景、历史版本不留存决策与跨运营日清理(开放项)见 §3.5/§3.6。
设计逻辑
- 提交:单活动主泵(即消息处理主循环,下同)按 FIFO 推进;航班状态、快照账本、outbox 与处理终态同事务提交,回填和投递在提交后独立重试。
- 合并:快照与动态事件均保留未出现的字段,显式清除才清空;同日快照覆盖其携带字段的已有值。集合按整组替换,保留全部条目及顺序。
- 记账:
SCHD_GEN每运营日一行,记录最近成功提交的快照版本和消息 ID;SCHD_GEN_FLID每运营日保存该版快照的完整航班名单,新版提交时替换旧名单。 - 差删:从旧名单中找出新快照不再包含的航班,仅删除当前
FLIGHT_SCHD.FDAY仍等于该运营日的记录。未入代航班和已迁往其他日的航班保留,见 §3.3 示例。 - 投递:
KAFKA_SCHD按航班聚合为最新整态,删除发 tombstone;KAFKA_MSG仅通知变化,两个主题不保证先后顺序。
1. 系统定位与核心不变量
本系统作为消息交换与航班运行状态汇聚核心:从共享 MySQL 信箱接入 SIS(AODB)报文,按报文语义把报文合并进本地权威的"航班当前状态",再把状态变化以标准化领域事件发往 Kafka。
系统坚持三条架构底线:
- 单一权威状态(Single Source of Truth)。 本地数据库是本模块对外提供当前状态的唯一读取源;共享 MySQL 信箱与 Kafka 均为本地提交之后的外围边界,各自独立重试,任何外围故障都不会让已提交的状态回滚或重复计算。它不是高于 AODB 的业务权威——上游业务事实以 AODB 为源,本模块是其在本系统范围内的当前态投影。
- 单活动主泵(Single Active Pump)。 生产部署约束为同一时刻仅一个实例执行信箱消费与状态推进。事务行锁可以串行化所有遵守该锁协议的数据库事务(跨进程、跨实例同样生效),但行锁本身不能实现消息认领、主实例选举、故障切换或全链路 FIFO——跨实例安全由「单活动实例」部署约束保证,而非行锁。
- 单提交事务(Single Commit Transaction)。 状态、outbox 事件、处理终态(
PROC_STATE)在同一个本地事务内落库,全部成功或整体回滚。
职责边界。 本模块只同步上游报文并维护当前态,不推导业务状态:取消、延误、备降、返航等运行状态由上游报文给定(对应 FDEL/CNCL/FDIV/FRET 等事件,规范 §3.27–3.33),本模块不做状态机推导;登机、登机结束等旅客服务状态不在接入范围。实际时间取 SIS §3.20 口径——进港为 ATA、出港为 ATD 的单值时间(本地列 ACTT),不区分 A-CDM 建模的落地/入位/离位/起飞等多节点;进出港过站关联按到港记录携带的 TAOP/TAFL/TAID 原样保存(见 §3.2)。
处理按信箱 FIFO 严格有序推进,按业务身份幂等去重,失败按指数退避重试。
数据库口径:现场目标库为 Oracle 11g;当前实际接通并作为验证基准的只有 PostgreSQL,本文描述 PostgreSQL 实现细节。
2. 关键架构决策
| 架构决策 | 理由与边界 |
|---|---|
| 状态、快照代、处理终态与 outbox 同库提交 | 消除 Redis 状态与数据库终态跨库双写窗口;Redis 等缓存不参与动态状态权威。 |
| 当前验证基准为 PostgreSQL,现场目标库为 Oracle 11g | 单次部署只绑定一个内部权威库;Oracle 尚未完成全链路适配,不可作为运行时可用配置开关。 |
| 航班标量主表 + 9 张关系明细表 | 完整保留 10 类变长集合的输入顺序、源序号及多条资源/操作记录,不按展示槽位截断。 |
| 异常对象保留主表前缀列;SRVT/VIPF/MAFL 保留文本列 | 既有载体可承载时不新增抽象或关系表;其读写语义需以回归证明。 |
单活动主泵,事务内 PIPELINE_LOCK 行锁 |
行锁串行化遵守锁协议的数据库事务(跨实例同样生效),但不实现消息认领、FIFO 或跨实例协调;跨实例安全依赖单活动实例部署约束。 |
| 外部回填与事件投递按幂等重试处理 | 不使用跨库分布式事务;回填意图与业务终态同事务持久化是待补齐目标(见第 8 节)。 |
| 展示视图仅为只读投影 | 仅展示前序资源(前两个登机门/柜台等),不作为全量权威状态或数据一致性比对输入。 |
废弃方案与架构约束。 系统明确放弃:固定 GATE1/GATE2 式槽位作为权威、"超界数据静默丢弃"、"零子表为架构约束"、"存储模型决定零方言迁移成本"、以及脱离全量备份前提的 RPO=0 承诺。历史决策论证与撤销记录见 决策历史。
3. 存储:标量主表 + 9 张明细表
3.1 为何不采用固定槽位
航班资源本质是变长集合,数量因航班、机型与保障安排而异。SIS 规范 §2 资源限制表给出协议上限:登机门、值机柜台、行李转盘每航班各 99,计划机位 9、当前机位 1,实际机位移动数量不限;规范未给出数量下限,本设计不做数量假设。ACM2-29 曾采用"全标量宽表、零子表"(GATE1/GATE2 式固定槽位),当日即被推翻——槽位会截断数据,而设计约束要求保留重复资源的全部属性、输入顺序与源序号。结论:标量进主表,集合进明细表。
3.2 表清单
| 表 | 职责 |
|---|---|
FLIGHT_SCHD |
主表,一行一 FLID:标量字段、异常前缀列、SRVT/VIPF/MAFL_TEXT、FDAY、STATE_VERSION、LAST_MESSAGE_ID 与时间戳。 |
| 8 张集合明细表 | flight_gate/flight_checkin/flight_belt/flight_stand_plan/flight_chute/flight_delay/flight_bridge_op/flight_chock_op;当前主键 (FLID, ORDINAL)。 |
FLIGHT_ROUTE_POINT |
第 9 张明细表;ROUT/ERUT 两类路线共用,以 ROUTE_KIND 区分。 |
SCHD_GEN / SCHD_GEN_FLID |
快照代际记账,见 3.3。 |
PIPELINE_LOCK |
单写者行锁:每个处理事务先锁 FLIGHT_SCHD_WRITER 行;缺失时两事务并发读写同一航班会互相覆盖。 |
BACKFILL_TODO |
回填补偿待办,见第 8 节。 |
FLIGHT_SCHD_DISPLAY |
PG 展示视图(前两个登机门/柜台、首个转盘/延误、资源数);仅为投影,非状态来源。 |
9 张明细表承载 10 类集合——路线表一张覆盖两类。
FDAY 的语义。 涉及「日期」的三个概念必须区分:航班业务日期(该航班实例业务上归属哪一天、以什么口径确定——模型不保存,ADFT/动态新增行 FDAY=NULL 正是因为模型不为未上计划的行保存业务日期)、快照覆盖日期(本份快照查询/发布的运营日 D)、名单归属日期(当前由哪一天的快照负责其成员管理与差删)。FLIGHT_SCHD.FDAY 保存的是第三者,由整包快照赋值,与接收日、落库日无关。三者在本协议下通常同值,但那是快照接管规则的结果,不是定义使然。动态事件保留 FDAY;动态新增或 ADFT 新增航班尚未被收录时为 NULL,后续被快照收录再赋值。NULL 不表示航班没有运行日,其清理规则见第 7 节。
过站关联与报文排序(协议事实)。 到港记录携带 TAOP/TAFL/TAID——离开当前机场的过站连接航班的承运人代码/航班号/AODB ID,仅到港有效(§3.16.2);SCHD 报文内离港航班先于到港航班、代码共享航班跟在主航班之后(§3.16.1 排序注释)。注意:V1.1 迁移注释把 TAOP/TAFL/TAID 标注为「实际承运」,与规范的「过站连接」语义不符,待核对修正(ACM2-31)。
3.3 快照记账:每运营日一份版本记录、一份成员名单
记账单位是运营日,同一天可以多次更新。 例如 9 月 9 日的日计划先后收到三份快照,对应同一运营日的版本 1、2、3。“换代”指替换该日的当前版本及名单,不是进入下一个自然日。两张账本表记录当前版,不保存各历史版本的完整航班状态。
三份数据各记什么
| 数据 | 记录粒度 | 内容 | 用途 |
|---|---|---|---|
SCHD_GEN |
每运营日一行 | FDAY、VERSION、LAST_MESSAGE_ID |
记录该日最近成功提交的快照版本及消息;版本用于 CAS,消息 ID 用于最近一条快照的重放判断。 |
SCHD_GEN_FLID |
每运营日、每成员航班一条记录 | 运营日与 FLID 的对应关系 |
保存该日当前版快照包含的完整名单,作为下一次比较的旧名单;新版提交时整体替换。 |
FLIGHT_SCHD |
每 FLID 一行 |
航班当前状态及 FDAY |
保存快照与动态事件合并后的状态;FDAY 表示当前归属,差删时据此排除已跨日迁移的航班。 |
SCHD_GEN.VERSION 是日计划快照版本;FLIGHT_SCHD.STATE_VERSION 是单航班状态版本。动态事件可以推进后者,但不更新快照版本或成员名单。
一份新快照如何更新账本
设快照覆盖运营日为 D,消息 ID 为 M,航班名单为 N。以下动作与航班状态、outbox、处理终态在同一事务内完成:
- 锁内读取 D 的账本和旧名单 O。首次快照没有旧名单,按空集比较,成功提交后版本为 1。
- 若 M 等于账本的
LAST_MESSAGE_ID,按最近快照重放短路:不改航班状态、不替换名单、不推进版本、不新增事件;处理成功并执行提交后回填。 - 计算候选差删名单
O − N。逐项检查当前航班主行,只保留FLIGHT_SCHD.FDAY = D的航班作为实际删除对象。 - 合并写入 N 中各航班的状态,将它们的
FDAY设为 D;删除实际差删对象的主行、全部明细,并登记 tombstone。 - 将 D 的
SCHD_GEN_FLID名单整体替换为 N;以旧版本为条件执行 CAS,将VERSION加 1,并把LAST_MESSAGE_ID更新为 M。版本不符则整个事务回滚。 - 登记状态事件和处理成功状态,提交。任一步失败,旧版本、旧名单和旧航班状态一并保留,不产生半份快照。
因此,版本计数对应成功提交的新快照,不是接收次数;失败重试和最近快照重放不额外加版本。
示例:同日替换与跨日保护
下表以 A、B、C 表示三个 FLID,日期均为 2026 年。
| 顺序 | 输入 | SCHD_GEN 提交后 |
SCHD_GEN_FLID 提交后 |
航班状态变化 |
|---|---|---|---|---|
| 1 | M1:9/9 快照 {A, B} |
9/9:版本 1,消息 M1 | 9/9:{A, B} |
A、B 写入,FDAY=9/9。 |
| 2 | M2:9/9 快照 {A, C} |
9/9:版本 2,消息 M2 | 9/9:{A, C} |
候选差删 {B};B 仍属 9/9,删除 B 并登记 tombstone;A 更新,C 新增。 |
| 3 | M3:9/10 快照 {C} |
9/10:版本 1,消息 M3;9/9 不变 | 9/10:{C};9/9 仍为 {A, C} |
C 被 9/10 快照收录,主行 FDAY 改为 9/10。 |
| 4 | M4:9/9 快照 {A} |
9/9:版本 3,消息 M4 | 9/9:{A};9/10 仍为 {C} |
候选差删 {C};C 当前属 9/10,保留主行和明细,不发删除事件。 |
| 5 | M4 再次进入快照重放检查 | 不变 | 不变 | 命中 9/9 的最近消息 ID,状态和事件不重复写入。 |
第 3 步说明两者为何都需要:旧日名单记着上次快照包含 C,主行 FDAY 记着 C 已归属新日。 名单负责找出消失者,主行归属负责判断能否删除。仅凭旧名单会误删跨日航班;仅扫描主行 FDAY 则丢失了“上次快照包含谁”的依据。
重放的三种去向。 同一条报文重新到达有三种情形,去重与重放守卫各管一层:
- 入口去重:同一消息首次处理时已绑定
identity_key(见 4.1),再次从信箱到达按重复SKIPPED。 - 同一消息重试:事务内失败退回重试时,消息未提交过,正常走全流程;提交成功后因入口去重不会重算。
- 人工重放:历史快照报文(其消息 ID ≠ 当前
LAST_MESSAGE_ID)不会命中重放短路;若已清空入口身份记录而被人工重放,本设计不承诺旧状态不被覆盖——人工重放须携带明确的版本/业务日期策略方可放行(见第 7 节)。
需明确的实现约束:
- 明细表不声明外键。 清理动作由仓储显式执行,不依赖数据库级联。
SOURCE_SEQ可空且不唯一,仅为记录性质——逐条定位更新的 Apply 协议本期不实现(无生产调用方,且定位键不可靠)。 - 差删按代圈定范围,不扫
FDAY。 目标运营日 = 本份快照覆盖的运营日。最终删除集合 =(旧名单 − 新名单)∩ 当前仍FDAY=目标运营日的航班;仅对该集合显式删除明细行与主行。FDAY=NULL、已归属其他运营日、未在旧名单中的航班均不受本次差删影响;删除与名单替换同事务提交。 - 演进纪律。 不为尚无消费者的查询新增类型化时间列、摘要、额外载荷表或索引;时间类字段先保留原始字符串,字段与长度以已应用迁移为准。
- 跨日归属回迁(未决)。 上述流程把新名单 N 中各航班的
FDAY一律置为 D:若某航班已被更晚运营日的快照接管(FDAY=D'),一份迟到的旧日快照再次收录它时会把FDAY改回 D,并以旧值覆盖新日的合并态(示例:C 已属 9/10,迟到的 9/9 快照仍含 C → C 被改回 9/9)。现行「跨日保护」(约束 2)只拦截差删删除,不拦截归属回写与字段回退。归属迁移的时序判断是开放项(ACM2-31);定案前本设计不承诺FDAY单向迁移。
3.4 已知待修
路线表当前主键为 (FLID, ORDINAL):同一航班 ROUT 与 ERUT 各自从序号 1 起写,二者共存即冲突。计划通过新迁移改为 (FLID, ROUTE_KIND, ORDINAL)(见 ACM2-30)。
3.5 一个运营日的数据全景
运营日 D 的快照提交后,库内构成如下整体视图:
| 数据 | 当天形态 |
|---|---|
SCHD_GEN |
一行:D 的最新快照版本与 LAST_MESSAGE_ID,同日内被各次快照顺序覆盖。 |
SCHD_GEN_FLID |
当前版快照的完整名单 N,每次新快照整体替换。 |
FLIGHT_SCHD |
被 D 收录的航班各一行(FDAY=D),入库后可继续被动态事件推进;跨日被新快照收录时 FDAY 改写。 |
| 9 张明细表 | 各 FDAY=D 航班的集合行,随每次写入整组替换。 |
| tombstone / outbox | 差删航班与整态投递事件,与上述状态同事务落账。 |
历史版本不留存(已定决策;下列为四项独立决策,不可互相推论)。 ① 不保存状态历史:账本只记当前版,同日快照版本 1→2→3 的中间完整状态不保存;② 不逐条投递中间版本:KAFKA_SCHD 只按当前整态输出;③ 不提供历史查询:无此接口;④ 不保存原始报文:由共享信箱侧保留策略决定,不是本模块承诺。①② 的理由:权威态唯一,差删与 CAS 只依赖当前名单与版本,消费端按 (FLID, STATE_VERSION, UPDATED_AT) 去重也不依赖中间版本。后果:当日被覆盖的中间态不可从库中恢复;「可凭重放原始报文重建」依赖信箱保留期限、处理顺序与解析器版本,只是理论可能性,不作为恢复承诺。需要中间态历史线或审计留存时须另行设计,本设计不承担。
3.6 跨运营日生命周期
- 差删是历史航班出场的唯一常规路径:旧名单 − 新名单 ∩ 仍属本日的航班,同事务删除主行、明细并登记 tombstone。
- 被新运营日快照接管的航班,其集合经整组替换,仅保留最新值、不保存被替换记录(同 3.5 决策)。
FDAY=NULL未归属行的清理见第 7 节;已归属历史日的数据没有对应的清理规则。- 开放项(未决):旧运营日的
SCHD_GEN/SCHD_GEN_FLID行随日期无限累积,当前无定期清理或归档设计;快照长期停发时历史日留存航班也无兜底清理。清理策略须待真实保留期限需求定案后补齐,清理动作须经FLIGHT_SCHD与账本的一致性校验,不得破坏差删依赖的"旧名单 + 主行FDAY"双依据。跨日归属回迁的时序防护同样未决(§3.3 约束 4)。
4. 状态的两条来路
状态仅有两条来源,各成一条处理线:
- 请求/下载线(基准)。 建立指定运营日航班的基准全量。本系统向 AODB 发送日计划请求,AODB 返回 SCHD 报文——该运营日的全部航班——快照整体落库;该运营日未再出现的航班被差删。
- 动态线(运行事件)。 跟踪运行中的持续变化——实际时间、登机门开关、值机柜台、行李转盘、延误、靠撤桥、轮挡等。FLOP 字段级事件在规范 §3.19–3.43 共 25 类(AODB→RMS 事件总计 44 类),当前仅接入 GTDT 一类;单条报文只变更一个航班的一类资源,其余字段不受影响。
两条线共用同一入口与同一套事务/失败规则:进入事务后统一为"锁内读态 → 合并 → 写库 → 事件入 outbox → 终态 → 提交 → 回填"。
信箱队头 → 解码 → 绑定身份(去重)→ 按 Handler 路由
├─ SCHD-DNLD(已接通)────────→ 请求/下载线
├─ SCHD-RESP / ADFT(未接通)─→ 请求/下载线
└─ FLOP 事件(已接 GTDT)────→ 动态线
图中「已接通/未接通」为适用范围标注,不是完成度承诺:「已接通」仅表示该类型已进入本设计的路由与事务链,解析、出站与应答闭环等环节是否齐备以 §4.2 各处未决标注为准。
4.1 公共入口(所有消息必经)
- 排队。 信箱消息与定时任务共用一个 FIFO 队列,主泵只处理队头;队头失败按退避等待,重试超限或队头滞留超过时限 →
DEAD。一条异常报文会阻塞其后所有航班与定时任务:最大阻塞时限、隔离/旁路规则与告警阈值是运维未决参数(ACM2-31)。 - 解码。 报文非法 →
DEAD(不重试);解码器缺陷类错误 →FAILED(退避重试)。 - 绑定身份。 幂等键 =
发送方|类型|子类型|序号,仅首次处理时绑定;该键已被其他消息占用 →SKIPPED(重复),自身照常回填共享信箱。协议中 SEQN 为发送方设置的 6 位序号(1–999999,规范 §2 元数据),其唯一范围与重置/回绕策略规范未定义;上游序号重置、重启后重复,以及同身份不同载荷时的处理均为未决项,当前实现仅按键判重(ACM2-31)。 - 路由。 按报文类型定位 Handler;无注册 Handler →
FAILED(可重放,不写终态)。
4.2 请求/下载线:基准全量的建立
SIS 循环:本系统向 AODB 发送日计划请求 → AODB 返回 SCHD。三个子类型进入本流程时的规则:
| 子类型 | FDAY 来源 |
是否换代 | 是否参与差删 | 可完成请求 |
|---|---|---|---|---|
| DNLD | 快照覆盖的运营日 | 是 | 是(整包成员差) | 是(运营日匹配时) |
| RESP | 快照覆盖的运营日 | 是 | 是(整包成员差) | 是(且要求对应 REQ_TRACK 存在) |
| ADFT | 无(保持/置为 NULL) | 否 | 否 | 否 |
ADFT 为单条临时航班增量:只增改本条 FLID(整行整集合写入),不触发换代与整包差删,也不算完成任何请求。ADFT 报文不携带运营日,本行也不单独承载它:FDAY=NULL 仅表示该航班未被任何日计划代收录(见 §3.2)——临时航班在现实中有运行日,但模型不为未上计划的行保存该日期。同 FLID 后续被整包快照收录时由快照接管(见 §3.2 FDAY 语义)。
请求登记于 REQ_TRACK(同类并发=1,新请求把旧请求强制置 EXPIRED),出站经 COUTMSGS outbox——出站发送与超时重发尚未实现。
收到下载后的落库步骤:
- 解析整包并校验,单包上限 10000 个航班(超限 →
DEAD;协议口径:SCHD 单包不分段、不超过 CIIMS 10MB 上限,RECS为 1–4 位记录数,§3.16.1)。差删的完整性前提:「本包 = 该运营日完整名单」由协议保证——规范注释明确日计划是「AODB 最新数据的完整快照」(§3.16.1 注释 2)、单包不分段、RECS给出包内记录数(RECS=0即空名单);RECS与实收航班数的核对、同包重复FLID的处理为待补校验(ACM2-31)。 - 进入事务(先锁
PIPELINE_LOCK行):- 重放短路:该消息已提交过(与
SCHD_GEN.LAST_MESSAGE_ID相同,记账见 3.3)→ 直接SUCCEEDED,不加版本、不发事件。 - 算成员差:新快照相对上代消失的航班进入差删名单。
- 合并写入:锁内逐航班读当前态 → 合并 → 整集合替换,
FDAY置为该快照覆盖的运营日(不是报文接收或落库日期)。 - 差删:删除差删名单内航班的明细与主行(规则见 3.3);每个被删航班同事务登记一条 tombstone 事件入 outbox(载荷见第 8 节)。
- CAS 推进日代版本:版本不符则整体回滚 →
FAILED(INFRA)退避。 - 应答匹配:与
REQ_TRACK同事务联动——关联键 =(运营日, 发送方, 请求类型),完成该组合下最新一条 PENDING 请求(协议无请求关联号:RQFD 请求仅含时间范围筛选字段,RESP 也不回指请求报文,此关联键是规范约束下的替代方案,与「同类并发=1、新请求强制 EXPIRED 旧请求」共同收敛错配风险);迟到(已EXPIRED)或重复应答照常执行快照落库(数据仍权威),但不重复置完成、不报错;DNLD 仅在运营日匹配时完成请求,不按类型隐式完成。 - 每个航班一条
KAFKA_SCHD事件入 outbox;PROC_STATE→SUCCEEDED;提交。
- 重放短路:该消息已提交过(与
- 提交后回填共享信箱(失败落
BACKFILL_TODO,见第 8 节);重放短路同样执行回填。
同日重快照的权威规则(M)。 整包快照是同日最高权威:两次快照之间已应用的动态运行更新(实际时间、登机门、柜台等),在重叠字段上以快照值覆盖(含快照显式清空);动态报文更新当前状态,其与快照的冲突按本规则处理。快照省略的字段按「整行替换混合规则」(第 5、7 节)保留合并后当前值,不属于覆盖例外的组成部分。
规则 M 的成立条件(未决)。 本规则隐含两个上游前提:① 快照内容是 AODB 生成/发送时点的完整最新数据(§3.16.1 注释 2 支持生成时点口径);② 两条线的投递顺序与上游发送顺序一致(共享信箱 FIFO + 同一发送方 SEQN 单调)。若前提 ② 不成立(快照生成后延迟投递、SEQN 按类型分段编号等),重叠字段会被旧值回退——例如 10:00 生成的快照 10:03 才入队,会把 10:02 已处理的登机门更新改回旧值。前提 ② 尚未从协议条款逐条核实,把规则 M 作为对外承诺前须经设计评审确认(ACM2-31)。
示例:一次 DNLD 快照的落库。 示例中的 CA100、MU201、CZ300 均为 AODB 分配的 FLID 值(写成形似航班号仅为可读;FLNO 航班号是主表标量列,与 FLID 无恒定关系)。报文为 AODB 下发的运营日 2026-09-09 的整包快照,含 CA100、MU201 两个航班(FDAY 取快照覆盖的运营日 2026-09-09,与该报文何时接收、何时落库无关);库内该运营日的代(SCHD_GEN)当前成员为 CA100、CZ300。进入事务后依次发生:
| 步骤 | 动作 | 结果 |
|---|---|---|
| 重放检查 | 该消息 ID 等于日代 LAST_MESSAGE_ID? |
否,继续 |
| 算成员差 | 上代成员 {CA100, CZ300} − 新快照 {CA100, MU201},∩ 当前仍 FDAY=2026-09-09 |
差删名单 = {CZ300} |
| 合并写入 | CA100 已存在 → 锁内读态、整集合替换,FDAY=2026-09-09;MU201 新航班 → 先插主行再写明细 |
两航班状态就位 |
| 差删 | 删 CZ300 主行 + 全部明细行 | CZ300 消失 |
| CAS 推版本 | SCHD_GEN 版本 7 → 8;不符则整体回滚 FAILED(INFRA) |
版本推进 |
| 事件 | CA100、MU201 各一条 KAFKA_SCHD 入 outbox |
|
| 终态 | PROC_STATE → SUCCEEDED,提交 |
提交后回填共享信箱 |
此后 Dispatcher 按 FLID 聚合这批 KAFKA_SCHD 发出(见第 8 节)。
覆盖范围与未决。 本流程的设计范围当前仅覆盖 DNLD 子类型;RESP 与 ADFT 的 Handler、「请求 → 应答 → 标记完成」闭环为未决设计项,未覆盖类型的处理按 FAILED(UNSUPPORTED) 对齐。参考数据请求(RQRD;参考数据事件见规范 §3.1–3.15)同样登记于 REQ_TRACK,但应答落参考库、由 RequestCoordinator 处理,与航班状态无关。
4.3 动态线:运行事件的落库(以 GTDT 为例)
- Handler 纯函数决策。 输入 = 受影响航班当前态 + 报文,输出 = 字段变更与待发事件;此步不算账、不落库。每类资源的语义在 Handler 内固定,如 GTDT 为登机门集合整体替换(GTNO=0 表示清除)。
- 进入事务(先锁
PIPELINE_LOCK行):- 每个受影响航班:锁内重读最新态 → 合并字段变更 → 整集合替换写入,保留既有
FDAY(保留该行所属的运营日/代;动态事件不携带也不推断运营日,跨运营日的归属更正只由请求/下载线的快照接管完成,见 §3.2)。 - 事件(
KAFKA_MSG变更通知 +KAFKA_SCHD状态推送)入 outbox;PROC_STATE→SUCCEEDED;提交。
- 每个受影响航班:锁内重读最新态 → 合并字段变更 → 整集合替换写入,保留既有
- 提交后回填共享信箱(同上)。
示例:一条 GTDT 报文如何变更登机门。 报文(FLOP 增量,仅携带登机门,其余字段未出现):
META: SNDR=AODB, TYPE=FLOP, STYP=GTDT, SEQN=42
FLOP: FLID=CA100
GTDT GTNO=1 GATE=A1 GTYP=D
GTDT GTNO=3 GATE=B2 GTYP=I
库内 CA100 当前登机门为 G28(序号 1)、G33(序号 2)。处理过程:
- 解析字段集:GTDT = [(GTNO=1, GATE=A1, GTYP=D), (GTNO=3, GATE=B2, GTYP=I)]。其余键未出现 = 不动——延误、机位、时间一概不碰。
- 翻译命令:GTDT 出现 → Replace,整组替换为这 2 条。
- 锁内合并:读当前态 → 整组替换 → 版本 +1、记
LAST_MESSAGE_ID。 - 写库:
flight_gate中 CA100 旧 2 行删除,按输入顺序插入 2 行(ORDINAL=1,2;SOURCE_SEQ=1,3);GTDT 无标量列,主表仅推进版本/消息/更新时间。 - 事件:
KAFKA_MSG变更通知;KAFKA_SCHD载荷"GTDT":[{"GATE":"A1","GTNO":"1","GTYP":"D"},{"GATE":"B2","GTNO":"3","GTYP":"I"}]——JSON 数组、不做二次字符串编码,键按字典序。 - 提交后:回填共享信箱。
读回时按 ORDINAL 组装主行与 flight_gate 2 行为权威态;展示视图取前两个登机门 A1、B2。
清除与拒绝语义示例:
<GTDT GTNO="0"/>单条且全 0 → Clear:flight_gate行删空,KAFKA_SCHD中 GTDT 键消失。[{"GTNO":"1","GATE":"A1"},{"GTNO":"0"}]:0 与正常条目混排 → 非法结构,整条消息失败,不猜测、不截断。- 字段未出现 ≠ 清除:仅收到一条 DELY 报文时登机门不受影响。
- 异常键的空值才构成 Clear:FDIV 出现为
{}→ Clear →FDIV_*列全部置 NULL;SRVT 为空串/null 目前不算 Clear(见第 6 节),只有显式 Clear 命令能清列。
5. 状态变更实现(两条线共用)
所有状态变更统一经加工链:报文 → 命令 → 合并出新状态 → 写库。
5.1 数据统一形态
内存中一个航班的状态即一个模型(FlightNextState):一组标量键值 + 10 类集合(每类为一组条目,每条目一组键值)+ 版本/消息追踪字段。主表存标量、明细表存集合,列映射固定(如 GTDT 的 GATE/GOTM/GCTM → flight_gate 对应列);读回时按 ORDINAL 组装集合,数据库权威态 = 主行 + 明细行。
5.2 第一步:报文到命令
当前 Handler 返回字段集,commandsFromFields 将其翻译为命令;仓储仍会解析异常/文本 JSON,显式命令尚未贯通全部边界。
| 命令 | 标量 | 集合 |
|---|---|---|
| Unchanged(未出现) | 不修改 | 不修改 |
| Set(value)(出现) | 覆盖 | — |
| Clear | 删键,物理列置 NULL | 删除全部明细行 |
| Replace(list) | — | 同事务 DELETE + 批量 INSERT 整集合替换 |
| Apply(item, sourceSeq) | — | 引擎内预留:按首个 SOURCE_SEQ 匹配更新,否则追加;无生产调用方,尚非已确认协议 |
规则:
- 字段未出现 = 不动。 增量报文只携带变化部分,其余不受影响。
- 标量出现 = Set(覆盖)。 异常键(FDIV/FRET/FLAB)载荷为空串、null、空对象时 = Clear。
- 集合出现 = Replace。 整组替换为报文给定条目。条目序号属性(如 GTNO)为 "0" 是协议显式清除标记:仅当集合内全部条目均为 0 时翻译为 Clear;与正常条目混排属非法结构,fail fast 拒绝。
- 空数组
[]= Replace(空集),明细行清空——区别于字段缺失的 Unchanged。 - DELY 无协议序号属性,不支持逐条 Apply;清除走空数组替换。
验收目标。 非法 JSON、未知形状、超容输入不得在截断后成功。当前 parseCollection 未校验数组元素必须是对象,仓储会忽略未知属性;该目标尚未完成。
5.3 第二步:命令合并进当前态(纯函数)
FlightStateEngine.apply 从当前态拷贝一份并逐条应用命令:Set 覆盖键值;Clear 删键并记入 clearedKeys(被清除的标量键清单,供第三步写库时据此将物理列置 NULL);Replace 整组替换。随后版本 +1、记 LAST_MESSAGE_ID,产出 FlightNextState。纯函数:不碰库、不发事件,同样输入恒得同样输出。
5.4 第三步:新状态写库(唯一写入口 persistNextStates)
- 写库写的是合并后完整态,不是报文字段。 主表与集合写入的输入是锁内合并后的完整
FlightNextState;「未提字段一律不动」发生在引擎合并层(5.3),不在写库层。快照线整行替换(FDAY=该快照覆盖的运营日)写出的同样是合并后状态——快照省略的字段以合并流程结束时的当前值持久化。例:航班当前态含ESTT=0900(预计时间)与登机门 {G28},快照给出STD、ACTT=1012(实际时间,进港语义 ATA/出港 ATD)与登机门 {A1},省略ESTT→ 输出 =STD/ACTT/登机门改为快照值,ESTT保留 0900。若快照显式给空(Clear/空数组)才构成清空。FDAY由快照 Set 显式推进,是保留规则唯一的结构性例外。 - 动态线仅 UPDATE 本次出现的列,clearedKeys 对应物理列显式置 NULL;新航班先插一行
FDAY=NULL——动态消息不携带运营日、不触发换代,新行尚未被任何日计划代收录(并非"无运营日"),待后续被某运营日整包快照收录时由快照赋值(清理见第 7 节「未归属行」)。 - 明细集合:每次写航班,9 张明细表对该
FLID先 DELETE 再按合并后集合整组 INSERT——即使某集合本次未变也重写,以保证库内与内存状态严格一致。此即"整集合替换"的落地:ORDINAL 按输入顺序 1..N,SOURCE_SEQ 存协议序号,RECORD_VERSION 记写入时版本。 last_message_id/state_version/updated_at一并更新。
5.5 事件由状态序列化而来
KAFKA_SCHD 载荷 = FlightNextState 序列化(FlightFieldsJson):键按字典序输出保证同状态字节级稳定;集合键输出为 JSON 数组/对象,不做二次字符串编码。快照线的事件与落库共用同一份事务内状态;动态线的事件目前来自 Handler 的锁外预览(见 5.6),偏差收敛前动态事件载荷不承诺与提交状态逐字节一致——对外状态的最终一致性由投递聚合按当前整态投影兜底(第 8 节)。
5.6 已知偏差(待收敛)
GTDT Handler 在事务外先算一遍预览并构造事件,主泵在事务内再算一遍;该偏差收敛前,不能宣称动态事件载荷与提交状态一致(见 5.5,投递侧以整态投影兜底)。目标状态为 Handler 只表达业务变化,事务内产出唯一一份 nextState,落库与 KAFKA_SCHD 共用——尚未实现,列为可靠性验收项。
6. 字段语义矩阵
6.1 16 类结构映射
| 集合键 | 存储载体 | 序号属性 | 出现语义 | 显式清除 | Apply |
|---|---|---|---|---|---|
| GTDT 登机门 | flight_gate |
GTNO | Replace | GTNO=0 全清 | 引擎预留(未启用;GTNO 定位) |
| CKDT 值机柜台 | flight_checkin |
CKNO | Replace | CKNO=0 全清 | 引擎预留(未启用);同 CHKC 多舱位分配按 CKNO 区分,不按资源号去重 |
| CLDT 行李转盘 | flight_belt |
CLNO | Replace | CLNO=0 全清 | 引擎预留(未启用) |
| PSDT 计划机位 | flight_stand_plan |
PSNO | Replace | PSNO=0 全清 | 引擎预留(未启用) |
| CHDT 行李滑槽 | flight_chute |
CHNO | Replace | CHNO=0 全清 | 引擎预留(未启用) |
| DELY 延误 | flight_delay |
无 | Replace(仅保存本次集合,不追加历史) | [] 清空 |
不支持(协议无定位键) |
| ABTM 靠撤桥 | flight_bridge_op |
ASNO | Replace(操作全集) | ASNO=0 全清 | 引擎预留(未启用;ASNO 定位,多次靠/撤桥各占一行) |
| CHOT 轮挡 | flight_chock_op |
CSNO | Replace | CSNO=0 全清 | 引擎预留(未启用;CHID=ON/OFF 为业务属性,非序号) |
| ROUT 计划航路 | flight_route_point(route_kind=ROUT) |
RTNO | Replace | RTNO=0 全清 | 引擎预留(未启用) |
| ERUT 扩展航路 | flight_route_point(route_kind=ERUT) |
RTNO | Replace | RTNO=0 全清 | 引擎预留(未启用) |
| FDIV 备降 | 主表前缀列 FDIV_* |
— | Set(对象) | "null"/{}/空串 → Clear |
— |
| FRET 返航 | 主表前缀列 FRET_* |
— | Set | 同上 | — |
| FLAB 中止 | 主表前缀列 FLAB_* |
— | Set | 同上 | — |
| SRVT 服务 | 主表 SRVT_TEXT |
— | Set | 当前字段转换器不将 null/{}/空串转 Clear;显式 ScalarCommand.Clear 可清列 | — |
| VIPF 贵宾 | 主表 VIPF_TEXT |
— | Set | 同 SRVT | — |
| MAFL 共享航班 | 主表 MAFL_TEXT |
— | Set | 同 SRVT | 无查询需求时维持现有载体 |
公共明细列:FLID(逻辑归属,当前无数据库外键)、ORDINAL(输入顺序,1 起)、SOURCE_SEQ(报文序号属性,如 GTNO=3;仅记录,不参与定位、不唯一)、RECORD_VERSION、CREATED_AT/UPDATED_AT(整组替换为物理重写:CREATED_AT 反映最近一次重建时刻,不是业务记录时间);当前主键 (FLID, ORDINAL)。路线表尚需把 ROUTE_KIND 纳入主键以避免 ROUT/ERUT 冲突。相同资源号不代表同一条分配记录,禁止按资源号去重。表中"引擎预留(未启用)"的 Apply 仅指引擎分支存在,生产只生成 Replace/Clear。
集合全集语义:协议依据与待确认项。 快照线(SCHD)可按全集理解:规范注释明确日计划是「AODB 最新数据的完整快照」,可选字段/段未出现即 AODB 无此信息、子系统应删除本地已有值(§3.16.1 注释 4),DELY 亦明确「该标签对所有已记录延误重复出现」。动态线各事件每次携带全量集合还是仅本次变更,规范未逐条写明——CHOT 的 CSNO 实为机位序号(非轮挡序号),每段记录一次上/下轮挡移动(§3.22.2),若事件仅携带本次移动,Replace 会丢失既有操作记录;各集合「序号=0 全清」的协议出处也需逐类核实。动态线集合语义确认前,Replace 全集语义仅对快照线可视为定案(ACM2-31 未决)。
6.2 清除的库内与线上表达
- 库内:标量/异常/文本键 Clear → 对应列置
NULL;集合键 Clear/Replace(空集) → 明细行删除。 - 线上(
KAFKA_SCHD):清空的表达定为键缺失——Clear 与 Replace(空集) 事件同语义,KAFKA_SCHD载荷不输出该集合键;下游不得把「键缺失」与「[]」作两种解释。库内读回(无键)与 wire 表达按此收敛,黄金样例按此口径固定(投递聚合与 tombstone 见第 8 节)。 - 两个方向的「缺失」语义相反,消费契约必须写明:上游输入报文中字段缺失 = Unchanged(保留原值);下游
KAFKA_SCHD整态投影中字段/键缺失 = 当前态无该数据,消费端应移除本地旧值。下游接收的是整态替换,不得按补丁合并。 - 异常对象 Set 在增量 SQL 中仅写出现的属性;旧对象被省略的属性可能残留,与对象整体替换目标不一致,待回归修正。
6.3 时区与空值
FDAY只记录日期(该行所属代覆盖的运营日,见 §3.2),其来源与日界都按「快照覆盖的运营日」口径:由日计划快照决定,不由报文接收/落库时刻推算——跨午夜接收不改变归属;动态事件不更新FDAY。- SIS 时间串(
DDMONYYHHMM,机场当地时)原值保存;类型化提升按实际查询需要另行定案。 - PG 当前保留字符串空值,集合 JSON null 属性会被解析为空串。Oracle 11g 对零长度字符值按 NULL 处理是官方文档明确的既定行为,不再是待确认的数据库事实;待验证的是本模块在该行为下能否维持字段语义——空串往返、SRVT/VIPF/MAFL「空串不算 Clear」的判断、显式命令的持久化保真,均列为 Oracle 适配的交付门槛验证项。
7. 失败规则与不变量(两条线共用)
事务内任一步失败 → 整体回滚,消息记 FAILED 退避重试。已提交的成功不受后续失败影响:回填与投递失败仅落补偿待办,不降级 SUCCEEDED,不重放业务。
不变量:
- 状态、事件、终态同事务:库内可见的必是完整提交结果。
- 计算是合并不是覆盖;写库是一致不是增量。 报文未提字段一律不动(加工链见第 5 节);写入库的永远是锁内合并后的完整
FlightNextState,其中包括快照线整行替换写出的合并后状态(混合规则与例见 5.4)。快照线更新FDAY(置为该快照覆盖的运营日),动态线保留FDAY(不改变归属、不推断运营日),动态/ADFT 新行FDAY=NULL(尚未被任何运营日代收录,不等于无运营日)。 - 快照是同日最高权威:重叠字段上快照覆盖动态更新(见 4.2 规则 M 及其成立条件标注);动态报文更新当前状态,与快照的冲突按规则 M 处理。
- 快照重放仅一道防线:凭
schd_gen.last_message_id(见 3.3)识别最近一次快照的重放;消息去重凭identity_key;版本号只是顺序令牌。更早历史快照的重入是否安全,依赖人工重放的版本/业务日期策略(见 3.3 重放三种去向)。 - 未归属行(
FDAY=NULL)的生命周期:动态线先于快照到达或从未上日计划的FLID以FDAY=NULL建行,不隶属任何代、差删不触达,不引用SCHD_GEN名单。其清理由定期历史化作业执行:条件 = 最后更新时间超过 N 天(默认 7,可配置)且未被任何待答请求/名单引用;N 的默认值须有真实消费方复核后启用。已归属历史日的账本行与航班清理为另一独立未决项(见 §3.6),不适用本规则。
错误分类与队列行为(目标矩阵):
| 错误类别 | 示例 | 终态 | 重试 | 队头行为 |
|---|---|---|---|---|
| 协议结构非法(解码乱码/0 与正常条目混排/未知形状/缺身份键) | §5.2 非法结构 | DEAD |
不重试 | 立即释放 |
| 容量超限(单包 >10000 航班等) | §4.2 超限 | DEAD |
不重试 | 立即释放 |
| 无注册 Handler | 路由不到 Handler(4.1) | FAILED,不写终态 |
不自动重试,可人工重放 | 队头滞留至超限 |
| 未定义 SCHD 子类型 | 未在子类型规则表中的子类型 | 现状 FAILED(UNSUPPORTED);目标终态待定案(其余行为以本行现状标注为准) |
不自动重试 | 队头滞留至超限 |
| 解码器缺陷/内部错误 | 事务 DB 故障 FAILED(INFRA) |
FAILED → 退避 |
超限转 DEAD |
队头滞留直至超限 |
| 并发版本 CAS 冲突 | 日代推进版本不符 | 事务回滚(无终态) | 同事务或下一轮重读自动重试 | 队头滞留直至成功 |
| 人工重放 | — | 仅 DEAD 消息可人工重放,须携带版本/业务日期策略(见 4.2/3.3) |
— | — |
矩阵中「既有」:DEAD/FAILED 两类终态、CAS 自动回滚、队头失败退避;「待补校验」:超容与结构非法的显式拒绝(§5.2 验收目标)、未定义子类型的终态归属、人工重放的幂等身份策略(失败前已绑定的 identity_key 与重放身份的关系未定义)。每类错误有且只有一个默认路径,以本矩阵行为准。
字段缺失、清除、空数组、异常对象的精确行为见第 6 节。
8. 提交之后:回填与事件
提交成功后有两件外部副作用:回填共享 MySQL 信箱、异步投递 Kafka,二者各自独立重试。
回填恢复依赖一条持久待办:
- 现状:仅在回填失败后写
BACKFILL_TODO;提交后立即崩溃的窗口内没有待办,无法恢复。 - 目标:在业务终态同一事务内登记回填意图,提交后回填成功即删待办。崩溃后自然有账可查,无需额外"重启对账框架"。
- 红线:登记待办后再失败,不得把已提交的
SUCCEEDED降级为FAILED。
KAFKA_SCHD 投递语义(按 FLID 聚合,第 8 节的主体)。 Dispatcher 聚合未发出的同 FLID 事件,按该 FLID 当前整态投影输出——不是逐事件补丁流:
- 同
FLID多条未发事件合并为一次按最新STATE_VERSION生成的整态载荷;中间版本的载荷不逐条补发。 - 旧批次的投递重试可晚于新批次发出;消费端以
(FLID, STATE_VERSION, UPDATED_AT)去重,晚到的旧投影不应覆盖新状态。 KAFKA_MSG仅作变更通知,不承载状态;两个主题之间不承诺顺序。- 未归属行约定(第 7 节)与 tombstone(下)也是事件身份的一部分:
FLID不重复重发。 - tombstone(差删的对外删除语义;自定义删除事件,非 Kafka 原生墓碑):被差删航班的最后一类
KAFKA_SCHD事件以 tombstone 表达——载荷仅含FLID与DELETED标记(无状态字段;value 非 null,Kafka 不会按日志压缩语义处理它);与差删同事务登记,投递失败照常重试、不过期,下游接到后删除该FLID本地数据。删除通知不携带版本号:迟到删除、删除后恢复与旧更新事件的冲突消解由消费端负责;主行物理删除后STATE_VERSION的连续性(删除前与重建后的事件可能同号)未定义——删除事件的版本/世代标识为开放项(ACM2-31)。
9. 读取路径
当前读一个航班 = 主行 1 次 + 10 类集合各 1 次,共 11 次查询;全量读 N 个航班约 1+10N 次(N=航班数,主行批量 1 次 + 每航班 10 类集合各 1 次)。多次读不一定落在同一数据库快照,主子状态不保证一致。
后续在仓储边界按 FLID 批次加载明细,并明确一致性读事务;投递侧 Dispatcher 的整态投影读取受同一缺口影响,一并纳入验收。不引入缓存权威或通用查询框架。GET /all/flights 仍是 Controller TODO——仓储方法与展示视图存在不等于接口已交付。
KAFKA_SCHD 输出结构化 JSON,集合禁止双重编码为字符串。清空的 wire 表达已按第 6 节定案(键缺失);未知属性、异常局部替换与空值往返仍存在验收缺口(见第 6 节);单个全字段样例通过并不等于整个 SIS 协议无损。