4. 增量快照与断点续传结合:增量传输的断点续传模型、一致性保障、数据校验机制
增量快照做完了,数据也压缩好了。但网络不是完美的,传输过程中随时可能断。我见过太多系统,增量数据传了一半,连接断了,然后从头再来——这在大数据量下简直是灾难。
所以这一章,我们聊聊怎么把断点续传和增量快照结合起来。说白了,就是让传输过程也能“增量”。
4.1 增量传输的断点续传模型
先想一个问题:断点续传的本质是什么?
是记录“我已经传到了哪里”。对于全量文件,我们记录字节偏移量。但对于增量快照,情况复杂得多——增量数据是逻辑分片的,不是连续的字节流。
我个人习惯把增量快照的断点续传模型分成三层:
- 分片层:把增量数据切成固定大小的逻辑块(比如 4MB 一块)
- 索引层:记录每个分片的元数据(分片ID、起始偏移、哈希值)
- 状态层:记录哪些分片已确认、哪些待传输
举个例子,假设一次增量快照产生了 200MB 的变更数据。我会把它切成 50 个 4MB 的分片。每个分片独立计算 SHA-256,然后按顺序传输。接收方每收到一个分片,就回传一个 ACK。发送方维护一个位图(bitmap),标记哪些分片已确认。
连接断了怎么办?很简单——发送方读取位图,找到第一个未确认的分片,从这个分片开始继续传。已经传完的分片不需要重传。
核心模型:增量快照断点续传 = 逻辑分片 + 位图确认 + 最小重传粒度
这里有个关键点:分片大小怎么定?我在项目中踩过坑。分片太小(比如 64KB),位图太大,管理开销高;分片太大(比如 256MB),断点后重传的代价高。我一般建议 4MB~16MB,具体看网络带宽和延迟。
4.2 一致性保障
断点续传最怕什么?怕数据不一致。你想想看,增量快照本身就是在某个时间点的一致性快照基础上做的。如果传输过程中,源端又发生了新的变更,那接收方拼出来的数据可能是个“四不像”。
我遇到过这样一个案例:某系统在做增量传输时,源端同时在进行数据 compaction。结果接收方收到的增量数据,一部分是 compaction 前的,一部分是 compaction 后的——数据直接乱套了。
怎么解决?核心思路是快照隔离。
4.2.1 快照版本号
每次增量快照生成时,分配一个全局唯一的版本号(比如递增的 LSN 或时间戳)。这个版本号贯穿整个传输过程:
- 发送方:在增量快照的元数据中记录版本号
- 传输过程:每个分片都携带这个版本号
- 接收方:校验所有分片的版本号是否一致
如果传输中断后恢复,发送方会检查当前增量快照的版本号是否仍然有效。如果源端已经产生了新的快照(版本号变了),那就放弃旧的断点,重新开始。
注意:版本号变更不一定要重新全量传输。如果增量快照是 append-only 的(比如 WAL 日志),可以只传输新增的部分。但如果是原地更新的(比如 LSM-Tree 的 compaction),旧快照可能已经失效,必须重新生成。
4.2.2 事务边界对齐
增量快照的断点续传,必须保证事务边界完整。你不能让接收方拿到半个事务。
我个人的做法是:在分片时,以事务为最小不可分割单元。一个事务的变更数据,必须完整地放在同一个分片里。如果分片大小到了阈值,但当前事务还没结束,那就等事务提交后再切分片。
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 事务跨分片 | 断点后只恢复部分事务,数据不完整 | 分片以事务边界对齐,禁止跨分片事务 |
| 源端并发写入 | 增量快照包含未提交事务 | 只捕获已提交事务,使用 MVCC 快照 |
| 分片乱序到达 | 接收方按分片 ID 乱序写入,违反事务顺序 | 接收方缓存乱序分片,按事务顺序回放 |
4.3 数据校验机制
传输过程中,数据可能损坏。网络丢包、磁盘静默错误、内存 bit flip——这些我都遇到过。所以校验不是可选项,是必选项。
我一般用三层校验:
4.3.1 分片级校验
每个分片传输完成后,接收方计算 SHA-256,与发送方携带的哈希值对比。不一致就发 NAK,要求重传这个分片。
这里有个细节:哈希值怎么传?我习惯把哈希值放在分片的元数据头部,和分片数据一起传输。这样接收方收到后可以立即校验,不需要额外的通信开销。
// 分片数据结构
struct Chunk {
uint64_t chunk_id; // 分片ID
uint64_t version; // 快照版本号
uint64_t offset; // 在增量快照中的偏移
uint32_t data_len; // 数据长度
uint8_t data[data_len]; // 实际数据
uint8_t sha256[32]; // 数据的 SHA-256 哈希
};
// 校验逻辑
bool verify_chunk(const Chunk* chunk) {
uint8_t computed[32];
sha256(chunk->data, chunk->data_len, computed);
return memcmp(computed, chunk->sha256, 32) == 0;
}
4.3.2 分片链式校验
分片级校验只能保证单个分片的数据完整性。但增量快照是有序的——分片之间有依赖关系。比如分片 5 的变更可能依赖于分片 4 的变更结果。
我建议在分片元数据中加入前一个分片的哈希值,形成哈希链。这样接收方不仅能校验单个分片,还能校验分片之间的顺序和完整性。
小技巧:哈希链的最后一个分片的哈希值,可以作为整个增量快照的“指纹”。接收方在全部接收完成后,可以用这个指纹做一次全局校验,确保没有遗漏任何分片。
4.3.3 端到端校验
分片校验和链式校验都做完了,是不是就万无一失了?不一定。我遇到过一种情况:所有分片校验都通过了,但接收方应用增量数据后,发现数据总量对不上。
原因是增量快照生成时,源端记录了总数据量和总行数。接收方在应用完所有分片后,应该对比这些总量指标。如果对不上,说明增量快照本身可能有问题(比如生成过程中遗漏了某些变更)。
端到端校验的典型做法:
- 发送方在增量快照元数据中记录:总数据量、总行数、总事务数
- 接收方在应用完成后,统计本地数据的变化量
- 对比两个值,不一致则标记为失败,触发全量重传
经验之谈:端到端校验是最后一道防线。虽然开销大(需要全量统计),但能发现分片校验和链式校验都发现不了的问题。我建议在关键业务场景中开启,非关键场景可以只做分片校验。
4.4 避坑指南
最后分享几个我踩过的坑:
- 位图持久化不及时:我曾经遇到过发送方进程崩溃,位图没来得及刷盘。重启后位图丢失,所有分片重传。解决方案是每收到一个 ACK 就 fsync 位图文件。
- 分片大小固定导致尾部浪费:最后一个分片可能只有几 KB,但元数据开销一样大。我后来改用动态分片——前 N-1 个分片固定大小,最后一个分片自适应。
- 校验算法选择不当:CRC32 速度快但碰撞概率高,不适合关键数据。SHA-256 安全但计算开销大。我一般折中:分片校验用 CRC32C(硬件加速),端到端校验用 SHA-256。
- 忽略接收方的写入顺序:接收方如果乱序写入,可能导致数据不一致。我要求接收方按分片 ID 顺序写入,乱序分片先缓存。
嗯,这一章的内容就这些。增量快照的断点续传,说白了就是三个东西:分片、位图、校验。把这三件事做好,传输过程基本不会出大问题。