3、Rsync算法详解:Rsync工作流程、签名生成、差异计算、数据重组、实际案例分析

Rsync 这个工具,做传输的兄弟应该都用过。但很多人只是「cp 的升级版」这么理解,其实它的核心算法非常巧妙。我当年第一次看 Rsync 源码时,心里就一个想法:这玩意儿的设计者真是个天才。

说白了,Rsync 算法解决的是个经典问题:两个机器上有同一个文件的旧版和新版,怎么只传差异部分?而且接收方只有旧版,发送方只有新版,双方不能提前交换整个文件。

嗯,咱们一步步拆开看。

3.1 Rsync 工作流程:四步走

Rsync 的整个传输过程,可以归纳为四个阶段。我习惯把它叫做「切、签、比、拼」。

  1. 切分(Split):接收方(目标端)把旧文件切成固定大小的块。默认是 700 字节,但实际会根据文件大小动态调整。
  2. 签名(Sign):对每个块计算两个签名——弱校验(滚动 checksum)和强校验(MD4/MD5)。
  3. 比对(Diff):发送方(源端)拿着新文件,用同样的块大小去滑动比对。找到匹配的块就发「引用」,没匹配的就发「原始数据」。
  4. 重组(Reconstruct):接收方根据收到的指令,把旧文件里的块和新数据拼起来,生成完整的新文件。

核心思想:传输的不是文件本身,而是「如何从旧文件构造新文件」的指令集。

我刚开始接触这个流程时,总觉得第三步「滑动比对」很玄乎。你想想看,如果每个块都去全量比对,那计算量不就炸了吗?Rsync 的精妙之处就在于——它用了两级校验来加速。

Rsync 四步工作流程 ① 切分 接收方切旧文件 ② 签名 弱校验 + 强校验 ③ 比对 发送方滑动匹配 ④ 重组 接收方拼装新文件 接收方持有旧文件 发送方持有新文件 网络只传签名和差异 匹配的块 → 传引用 不匹配的块 → 传原始数据 最终传输量 ≈ 差异大小

3.2 签名生成:弱校验与强校验

签名这一步,是 Rsync 性能的关键。它用了两套校验算法,分工明确。

校验类型 算法 长度 作用
弱校验 滚动 Adler-32 4 字节 快速筛选,排除明显不匹配的块
强校验 MD4 / MD5 16 字节 精确确认,防止哈希碰撞

弱校验用的是 Adler-32 算法。它有个特别牛的特性——滚动更新。什么意思呢?

假设你有一个 700 字节的窗口,从位置 0 滑动到位置 1。普通哈希得重新算 700 个字节,但 Adler-32 只需要:
新校验 = 旧校验 - 出去的字节 + 进来的字节

就这么简单。计算量从 O(n) 降到了 O(1)。

我的经验:我曾经在一个日志同步项目里,文件动辄几百 MB。如果用全量哈希,CPU 直接拉满。换成滚动校验后,CPU 占用降了 80% 以上。这就是算法的力量。

强校验为什么还要存在?因为弱校验只有 4 字节,碰撞概率是 1/2^32。对于大文件来说,这个概率并不低。强校验 MD5 有 16 字节,碰撞概率几乎可以忽略。两级校验配合,既快又准。

3.3 差异计算:滑动窗口匹配

这是 Rsync 最精彩的部分。发送方拿到接收方传来的签名列表后,开始在新文件上滑动窗口。

具体过程是这样的:

  1. 从新文件开头取 700 字节,计算弱校验。
  2. 在签名列表里查这个弱校验值。
  3. 如果命中,再计算强校验做二次确认。
  4. 如果强校验也匹配,说明找到了一个相同的块,记录「块编号 + 偏移量」。
  5. 如果弱校验没命中,或者强校验不匹配,就把当前字节标记为「差异数据」,窗口向前滑动 1 字节,继续。

你可能会问:为什么不直接滑 700 字节?

因为插入或删除一个字节后,后面的所有块都会错位。滑动 1 字节才能捕捉到这种「偏移」。这就是滚动校验的用武之地——每次只移 1 字节,但计算量几乎不变。

注意:滑动窗口的步长是 1 字节,不是块大小。这是 Rsync 能处理任意插入/删除操作的根本原因。我曾经见过有人自己实现「简化版 Rsync」,步长设成了块大小,结果文件里加一个字符就全崩了。

匹配结果会生成一组指令,格式大致如下:

# 匹配指令
MATCH block_id=5 offset=3500 length=700

# 差异指令
LITERAL offset=4200 length=3 data="abc"

# 最终指令序列示例
LITERAL offset=0 length=12 data="Hello World"
MATCH block_id=1 offset=700 length=700
MATCH block_id=2 offset=1400 length=700
LITERAL offset=2100 length=5 data="Update"
MATCH block_id=3 offset=2100 length=700

3.4 数据重组:拼装新文件

接收方收到指令序列后,开始重组。这个过程很直观:

  • 遇到 LITERAL 指令,就把后面的原始数据直接写入输出文件。
  • 遇到 MATCH 指令,就从旧文件的对应位置拷贝 700 字节到输出文件。

重组完成后,新文件就诞生了。整个过程不需要发送方传输完整的新文件,只传了签名和差异数据。

我算过一笔账:一个 100MB 的文件,如果只改了 1MB 内容,Rsync 实际传输量大约在 1.1MB 左右(1MB 差异数据 + 签名开销)。相比直接传 100MB,省了 99%。

3.5 实际案例分析

说个我亲身经历的项目。当时给某电商平台做静态资源增量更新,前端资源包大概 500MB,每天发版 3-4 次。每次改动其实就几个 JS 文件,总共不到 10MB。

一开始他们用的全量更新,CDN 回源带宽经常被打满。后来我引入了 Rsync 算法做差分更新。

改造前后的对比:

指标 全量更新 Rsync 差分更新
平均传输量 500 MB 8-15 MB
传输耗时 12 秒 0.3 秒
CDN 带宽峰值 2.5 Gbps 0.1 Gbps
服务器 CPU 负载 5% 12%(但可接受)

你看,传输量降了 97%,耗时降了 97.5%。代价是服务器 CPU 多花了 7%,但这点代价跟带宽成本比起来,根本不值一提。

关键结论:Rsync 算法特别适合「大文件、小改动」的场景。如果文件每次都是全量重写,那 Rsync 的优势就不明显了。选型时要看你的业务特征。

嗯,Rsync 算法就讲到这里。它的核心思想——「用计算换带宽」——在后来的很多差分工具里都能看到影子。理解了 Rsync,你就掌握了差分更新的基本功。