本文是《从 IP 到 SRv6 计算编排》新手教材第 7/13 编。 总目录 · 上一篇:第六编:Anycast 从逻辑服务选择实例 · 下一篇:第八编:链路变差时的三级自主降档
48. SPP是什么
**SPP(Satellite Processing Protocol,卫星处理协议)**是本项目为图像Tile定义的业务层 二进制协议。它不是IPv6扩展头,不是SRH,也不是控制面消息。
各层关系是:
Ethernet
└── IPv6
└── SRH
└── UDP或项目承载
└── SPP header
└── RGB/Gray/Edge payload
IPv6/SRH负责“包经过哪里”,SPP负责“包里面是哪一个任务、对象、Tile和图像格式”。算子只 在验证外层边界后读取SPP。
49. 为什么不直接发送一整张图片
把图片切成Tile有四个好处:
- 单包大小受MTU约束,Tile便于控制包长;
- 不同Tile可流水并行,而不必等整张图;
- 丢失影响可局部量化;
- 算子可按Tile或邻域逐步产生输出。
代价是系统必须显式管理对象身份、Tile坐标、epoch和不完整窗口。SPP正是为这些语义提供 共同契约。
50. SPP 64-byte固定头
当前头长固定为64 byte。多字节整数按网络字节序编码。
| 字节偏移 | 字段 | 长度 | 含义 |
|---|---|---|---|
| 0 | magic | 4 | 固定魔数,用于识别SPP |
| 4 | version | 1 | 协议版本 |
| 5 | header_length | 1 | 头长度,当前为64 |
| 6 | dimensions | 1 | 维度信息 |
| 7 | payload_format | 1 | RGB24、GRAY8或EDGE1 |
| 8 | flags | 2 | 标志位 |
| 10 | payload_length | 2 | SPP负载字节数 |
| 12 | task_id | 8 | 任务身份,必须非零 |
| 20 | object_id | 8 | 对象身份,必须非零 |
| 28 | tile_id | 8 | Tile的Morton编码 |
| 36 | original_width | 4 | 原对象宽度 |
| 40 | original_height | 4 | 原对象高度 |
| 44 | tile_width | 2 | 当前Tile宽度 |
| 46 | tile_height | 2 | 当前Tile高度 |
| 48 | grid_columns | 2 | Tile网格列数 |
| 50 | grid_rows | 2 | Tile网格行数 |
| 52 | object_epoch | 4 | 同一object的新版本序号 |
| 56 | reserved | 8 | 保留,供协议演进 |
接收者必须先验证magic、version、header length、payload length与实际帧边界,再访问后续 字段。不能因为以太网帧“看起来够长”就假定里面一定是合法SPP。
51. 三个身份字段不能相互替代
51.1 task_id
表示这份数据属于哪一次计算编排。相同图片同时进入两个不同DAG时,task_id不同,缓存也 必须分开。
51.2 object_id
表示任务内的哪一个逻辑对象,例如第42张图。一个task可连续处理许多object。
51.3 object_epoch
表示同一个object的哪一版。对象被重新采集、重传或更新时epoch增加。旧epoch迟到,不能 污染新epoch的窗口。
因此正确缓存键是:
(task_id, object_id, object_epoch)
只用task会把整条业务的所有图片混在一起;只用object会把不同任务和版本混在一起;用 五元组也不够,因为路径变化后五元组可能变化,而业务身份没有变化。
52. Morton编码
**Morton Code(莫顿码,又称Z-order curve,Z序曲线)**把二维坐标的二进制位交错成一个 整数,使空间相近的Tile通常仍有接近的编号。
若x和y的低位分别为:
x = ... x2 x1 x0
y = ... y2 y1 y0
则Morton码交错为:
... y2 x2 y1 x1 y0 x0
例如x=2即二进制10,y=1即01:
x位:x1=1, x0=0
y位:y1=0, y0=1
交错:y1 x1 y0 x0 = 0 1 1 0 = 6
算子不能把tile_id+1简单理解成“右边一块”。必须先Morton decode(解码)得到(x,y),
检查grid_columns/grid_rows,再计算邻域。
53. 包、Tile、窗口和任务不是同一个成功单位
这四层需要分别计数:
- 包成功:某个网络包到达下一处理点;
- Tile成功:某个Tile完成要求的算子链并到达目的端;
- 对象窗口完整:某个有状态算子收到其计算所需的Tile集合;
- 任务结果:任务观察期内所有预期Tile的完成比例。
若预期100个Tile,最终有90个完成,则该任务是90%成功,不是“任务失败”。公式为:
task_success_ratio = completed_expected_tiles / total_expected_tiles
只有协议或实验定义明确要求all-or-nothing(全有或全无)时,才可把任一Tile丢失折算成整个 task失败。本项目对SPP采用可量化的部分成功语义。
这也解释了为什么“不完整窗口被丢弃”是局部损失证据,而不是自动宣判整条任务为0。
54. SPP与Anycast的根本张力
无状态算子使用Anycast很自然:每个Tile无论落到哪个同类型实例,都得到相同结果。
有状态算子不同。假设一个2×2 group的四个Tile经逻辑Anycast服务转发:
tile A、B → Mean@node-20
tile C、D → Mean@node-44
两个实例都只看见半个group,都会等待并最终超时。网络没有丢包,但应用状态被拆散了。 这不是“Anycast把一个包复制到五个容器”,而是不同包在不同时刻根据当时FIB选择了不同 provider。
可以引入会话粘性、共享状态或一致性哈希,但这些都形成新的系统机制和研究问题。当前不应 为了某个测试结果临时写“同task永远绑死某节点”的补丁。教材中的正确结论是:
- SPP可以与Exact或Resilient结合;
- 有状态算子在provider漂移时天然可能产生窗口损失;
- 该损失按Tile完成比例计入可靠性;
- 更高层的状态安置理论留给后续研究;
- 观测必须保留完整身份,才能量化损失来自何处。