rsync的核心原理,可以概括为:一种在不直接传输整个文件的前提下,高效找出文件差异并进行同步的算法。它的精髓在于,它不需要两个文件在同一台机器上就能完成"差异化"操作,因此非常适合远程文件同步。
这个过程主要由下面三个步骤构成,巧妙地结合了"校验和"与"滑动窗口"技术:
🔍 第一步:目标端生成文件"指纹"
同步目标端(假设为B,拥有旧文件)不会直接发送整个文件,而是先为它制作一份精简的"指纹"清单,也就是sign文件,并发送给源端A。制作过程如下:
- 分块:将旧文件按固定大小(例如512字节或2KB,最后一块可能较小)切分成多个数据块。
- 生成"指纹" :对每一个数据块 ,都计算两种校验和:
- 弱校验和 (Rolling Checksum) :例如32位的
adler-32算法,计算速度非常快,主要用于快速筛选。 - 强校验和 (Strong Checksum) :例如128位的
MD5算法,几乎可以保证100%准确,用于最终确认数据块相同。
- 弱校验和 (Rolling Checksum) :例如32位的
- 发送清单:B机器将这个包含"数据块编号、弱校验和、强校验和"的清单发送给A机器。
📡 第二步:源端进行差异检测
源端(A,拥有新文件)收到这个"指纹"清单后,会在本地的新文件中,以滑动窗口的方式,寻找与清单里指纹匹配的数据块。
这个过程就像用一把固定长度的"尺子"(窗口)从文件开头滑到结尾,每到一个新位置就测量一下:
- 初始窗口:从新文件的第1个字节开始,取一块同样大小(例如512字节)的数据。
- 计算并查找:计算这个窗口数据的弱校验和,然后去B发来的清单中快速查找(通常用哈希表实现O(1)的查找速度)。如果找不到,说明这块数据大概率有变化。
- 滑动窗口 :如果找不到匹配,窗口就向后滑动1个字节,然后对新位置的数据块再次计算和查找。
- 双重确认:如果弱校验和找到了匹配,为了防止碰撞,还会进一步计算并比对强校验和(MD5)。只有两个校验和都匹配,才最终认定这个数据块在两端是相同的。
通过这个高效的滚动查找过程,源端A就能定位出所有与目标端相同的数据块。
🔄 第三步:构建并传输差异文件
最后,A端会根据检测结果,生成一个delta文件传给B端。这个delta文件包含了重建新文件所需的全部指令:
- 匹配到的块:只需记录下这个块在B端旧文件中的编号(例如"参考第5号块"),这部分数据无需传输。
- 未匹配到的块 :对于无法匹配上的"差异"数据,会将其原始字节内容直接打包进
delta文件。
目标端B收到delta文件后,根据指令,从自己的旧文件中提取匹配的数据块,并插入收到的新的数据块,最终就能完整地重建出和源端A一模一样的新文件。
💡 总结一下
rsync的原理可以类比为**"用拼图来同步"**:目标端先把自己手里的拼图块(旧文件)编上号、拍下照片(生成指纹)发给源端。源端拿到照片后,在自己的新拼图里寻找一模一样的块,找到的就不用再寄了,只需要把找不到的新块和一份"用几号旧块拼在哪里"的说明书(delta文件)寄回去。目标端按照说明书,就能用旧块和新寄来的块,完整地拼出新图案(新文件)。
Adler-32如何通过0-1024计算1-1025
这个问题问得很到位,触及了rsync高效运作的核心秘密。
简单来说,Adler-32之所以能从0~1024的校验值快速计算出1~1025的校验值,靠的不是重新计算,而是一个极其简单的数学更新公式 。它让校验和的计算复杂度,从O(n)(和窗口大小成正比)骤降为O(1)(常数时间)。
🧮 Adler-32算法回顾
Adler-32的校验和由两部分组成,都是32位整数:
- A:窗口中所有字节的累加和(模65521)。
- B :窗口中所有字节的加权累加和(每个字节乘以其从窗口起始位置算起的权重,再累加,最后模65521)。
最终的校验和就是 A + (B << 16)。
🔄 如何滑动:从 0~1024 到 1~1025
假设窗口大小为1024字节,我们用 x0 表示第0个字节,x1024 表示第1024个字节。
旧窗口 0~1024 的校验和是:
A_old = x0 + x1 + ... + x1023B_old = 1*x0 + 2*x1 + ... + 1024*x1023
新窗口 1~1025 的校验和是:
A_new = x1 + x2 + ... + x1024B_new = 1*x1 + 2*x2 + ... + 1024*x1024
关键步骤来了,我们来看如何由旧值得到新值:
-
更新 A 值:
- 新窗口比旧窗口,移出了 第一个字节
x0,移入了 新字节x1024。 - 公式是:
A_new = A_old - x0 + x1024
- 新窗口比旧窗口,移出了 第一个字节
-
更新 B 值:
- 移出
x0后,窗口内所有剩余字节的权重都减1。 - 公式是:
B_new = B_old - (x0 + x1 + ... + x1023) + 1024*x1024 - 看,括号里的
x0 + x1 + ... + x1023不正是A_old吗! - 所以公式可以化简为:
B_new = B_old - A_old + 1024 * x1024
- 移出
⚡️ 为什么这很重要?
如果没有这个特性,每次窗口滑动1个字节,都需要重新计算1024个字节的校验和,那rsync的"滚动"扫描将慢得无法接受。
而有了这个O(1)的更新公式,rsync就能以极快的速度,一边滑动窗口,一边实时更新校验和,然后去哈希表中查找匹配。这使得它在处理大文件时依然能有惊人的效率。
🚨 一个重要的修正
最后想悄悄修正一个小细节,这其实并不影响你刚才问的"滑动"原理,但能让我们对rsync的理解更精确:
在rsync的实际实现中,用于快速查找的"弱校验和"并非标准的Adler-32,而是一个经过简化的、专门为滚动计算优化的变体 。不过,它的核心数学思想------利用滑动窗口实现校验和的增量更新------与Adler-32完全一致,你刚才理解的滑动更新逻辑依然是完全正确的。
希望这个"数学小魔术"能帮你更清晰地看到rsync高效背后的原理。如果对其中某个计算步骤还想深入聊聊,我们可以继续探讨~😊