迁移与快照:状态持久化链路
概述
在 CubeSandbox 的数据面里,快照能力直接决定了冷启动体验。其本质是把 VM 运行时状态拆解成可持久化的多个子快照,并在恢复时按依赖关系重建。
一句话总结:
快照不是“存一份内存”,而是“保存一个可恢复的系统状态图”。
代码定位
| 目录/文件 | 作用 |
|---|---|
vmm/src/vm.rs | snapshot() / restore() 主入口 |
vmm/src/migration.rs | 迁移与快照协调逻辑 |
vmm/src/memory_manager.rs | 内存状态导出与恢复 |
vmm/src/device_manager.rs | 设备状态导出与恢复 |
vmm/src/cpu.rs | vCPU 上下文保存与恢复 |
vm-migration/ | 通用迁移数据结构与协议抽象 |
快照状态模型
建议把状态分三层理解:
- 配置态(可重建):CPU 数、内存布局、设备拓扑
- 运行态(必须保存):寄存器、队列游标、内存页内容
- 兼容态(用于校验):版本号、feature mask、schema 版本
| 状态类别 | 是否必须持久化 | 说明 |
|---|---|---|
| VM 配置 | 是 | 恢复时用于构造骨架 |
| vCPU 寄存器 | 是 | 决定恢复后执行位置 |
| Guest 内存 | 是 | 应用与内核现场 |
| 设备内部状态 | 是 | 请求队列与设备行为一致性 |
| 临时统计计数器 | 否 | 可在恢复后重建 |
创建快照流程
关键约束:
- 快照窗口内必须冻结可变状态
- 子系统快照顺序要固定,确保可重复恢复
- 失败要可回滚(至少不破坏运行中的 VM)
恢复流程与依赖顺序
恢复顺序通常是:配置骨架 -> 内存 -> 设备 -> vCPU -> 运行。
为什么 vCPU 最后恢复?
- 因为一旦 vCPU 开跑,就会读写内存并触发设备交互
- 只有在内存和设备都就绪后恢复 vCPU,才能保证一致性
兼容性与演进策略
快照格式不可避免会演进。建议采用:
schema_version强制字段- 关键 capability bitmap(CPU/设备)
- 向后兼容读取 + 向前拒绝策略
| 场景 | 建议策略 |
|---|---|
| 小版本升级 | 保持向后兼容读取 |
| 大版本升级 | 显式转换或阻断恢复 |
| feature 不匹配 | 启动时预检并给出可读错误 |
性能优化点
1) 内存快照优化
- 优先使用 mmap/文件映射,减少复制
- 支持脏页跟踪时,增量快照优先
2) 设备快照优化
- 只保存恢复必需状态
- 避免保存可从配置重建的冗余信息
3) 并行恢复
- 在依赖允许的前提下并行恢复多个设备
- 控制恢复并行度,避免启动瞬时抖动
常见故障与排查
1) 恢复后 VM 立即崩溃
- 检查 CPU 上下文是否完整恢复
- 检查内存映射地址与原快照是否一致
- 检查设备 feature 协商是否兼容
2) 恢复后设备不可用
- 检查设备 snapshot 数据是否缺字段
- 检查队列状态(avail/used 索引)
- 检查中断注册是否在恢复后重新绑定
3) 恢复耗时波动大
- 检查 snapshot 体积与存储介质吞吐
- 检查是否发生大量小文件随机 IO
- 检查恢复阶段是否串行化过多
落地检查清单(Runbook)
- [ ] 快照前已暂停 vCPU 与设备
- [ ] 快照包含 config/cpu/memory/device/meta 五类状态
- [ ] 恢复顺序固定且有失败回滚路径
- [ ] 版本与 capability 做了预检
- [ ] 恢复完成后有健康探测(应用层 + 系统层)
小结
快照/恢复的难点不在“保存”,而在“一致恢复”。只要把握住冻结窗口、依赖顺序、版本兼容三件事,就能把这条链路做到稳定且可运维。