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。多字节整数按网络字节序编码。

图 1 SPP 64 byte 固定头的字段布局(每行 8 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 完成比例计入可靠性;
- 更高层的状态安置理论留给后续研究;
- 观测必须保留完整身份,才能量化损失来自何处。