Rsync Adler-32 算法

Rsync为了只同步文件变化的部分,使用了两种 hash 算法:弱校验算法和强校验算法。

弱校验用于快速分辨出不同的块,使用的是 Adler-32 算法。
强校验用于确保数据块是真的相同,因为弱校验很有可能hash冲突,加一层强校验,双保险!
强校验使用的是 MD5 算法。

这篇文章记录自己学习弱检验 Adler-32 算法的体会。

Adler-32算法简介

Adler-32 算法是由 Mark Adler 在1995年发明的一种滚动校验算法,算法因此也以作者名字命名。
除了Rsync,还有大名鼎鼎的 zlib 也在使用这一算法。

算法原理

这个算法的原理说起来也简单。

假设我们要为数组:A1, A2, A3, … An 计算出一个32 位的hash值。

sum = adler32(A1, A2, A3, … An)

记 sum 的低16位为:s1
记 sum 的高16位为:s2
sum = s2 × 216 + s1

其中s1 是指从A1到An每个数字依次相加得到的和。
s2 指每处理一位数字,s2的值就加上s1 (难以用文字描述,还是看公式吧)

s1 = A1 + A2 + A3 + … + An
s2 = A1 + (A1 + A2) + (A1 + A2 + A3) + … + (A1 + A2 + … + An)
     = A1 × n + A2 × (n - 1) + A3 × (n - 2) + … +An

我们来看下Rsync-1.0 版本中实现的 Adler32 算法

uint32 get_checksum1(char *buf,int len)
{
    int i;
    uint32 s1, s2;

    s1 = s2 = 0;
    for (i = 0; i < len; i++) {
	      s1 += buf[i];
	      s2 += s1;
    }
    return (s1 & 0xffff) + (s2 << 16);
}

怎么样?比起上面眼花缭乱的数学公式,代码看起来是不是更简单,更容易懂?

Rsync之后的版本对Adler32算法的实现进行了优化,最新的Rsync-3.2.3中Adler32算法实现如下:

/* a non-zero CHAR_OFFSET makes the rolling sum stronger, but is
   incompatible with older versions :-( */
#define CHAR_OFFSET 0

uint32 get_checksum1(char *buf1, int32 len)
{
	int32 i;
	uint32 s1, s2;
	schar *buf = (schar *)buf1;

	s1 = s2 = 0;
	for (i = 0; i < (len-4); i+=4) {
		s2 += 4*(s1 + buf[i]) + 3*buf[i+1] + 2*buf[i+2] + buf[i+3] + 10*CHAR_OFFSET;
		s1 += (buf[i+0] + buf[i+1] + buf[i+2] + buf[i+3] + 4*CHAR_OFFSET);
	}
	for (; i < len; i++) {
		s1 += (buf[i]+CHAR_OFFSET); s2 += s1;
	}
	return (s1 & 0xffff) + (s2 << 16);
}

不同于原始算法中一个字节一个字节的处理,优化后的算法一次处理4个字节,可以显著降低运算量,提高性能。
另外一次处理4个字节,是不是32位CPU刚好一次能容纳4个字节宽度的原因?我不确定。

算法中还引入了一个宏变量:CHAR_OFFSET
通过注释得知如果CHAR_OFFSET设置为非0值,可以提高算法健壮性,这一点也不明白为什么。

滚动校验

能计算出一组数据的校验和不算本事,市面上有很多种算法都能计算出校验和,甚至自己也可以设计一种算法。

Rsync对算法的要求是
在已知 [A1, A2, … An] 校验和的情况下,如果再来个数据:An+1,能够在之前校验和的基础上迅速推导出 [A2, A3, … An, An+1] 的校验和,也就是数据块向后滑动1字节后,迅速计算出下一个块的校验和, 而不是逐字节的计算[A2, A3, … An, An+1]的校验和,那样就太慢了。
在这里插入图片描述
我们记 [A1, A2, … An] 的校验和为: sum[1, n]
记 [A2, A3, … An, An+1] 的校验和为: sum[2, n+1]

sum[2, n+1]校验和求值的基本思路是:
在 sum[1, n]的基础上,去除 A1 相关部分,再加上 An+1 相关部分。

前面说过,sum 由低16位的s1和高16位的s2组成,我们做如下标记:
记滑动前的块 sum[1, n] s1 部分为:     s1[1, n]
记滑动前的块 sum[1, n] s2 部分为:     s2[1, n]
记滑动后的块 sum[2, n+1] s1 部分为: s1[2, n+1]
记滑动后的块 sum[2, n+1] s2 部分为: s2[2, n + 1]

那么我们可以做出如下数学推导:
sum[2, n+1] = s1[2, n+1] + s2[2, n + 1] × 216
                     = (s1[1, n] - A1 + An+1 ) + (s2[1, n] - n × A1 + (s1[1, n] - A1 + An+1 ) ) × 216

再来看看 Rsync-1.0 代码中关于块向后滑动一字节后,如何根据前一个块的校验和计算下一个块的校验和 (代码来自 match.c: hash_search() 函数,为了便于描述,做了抽取和变量名修改)

uint32 s1, s2, sum;

/* 1. 计算出第一块校验和, n代表数据块长度 */
sum = get_checksum1(buf, n);
s1 = sum & 0xFFFF;
s2 = sum >> 16;

/* 2. 数据块向后滑动1字节 */

/* 2.1 去除被滑动掉的第一个字节相关部分 */
/* Trim off the first byte from the checksum */
s1 -= buf[offset];
s2 -= n * buf[offset];

/* 2.2 添加新被滑入字节的相关部分 */
s1 += buf[offset+n];
s2 += s1;

/* 2.3 计算出滑动1字节后新块的校验和 */
sum = (s1 & 0xffff) + (s2 << 16);

代码和数学式对的上了么?我觉得直接看代码,反而更容易理解。

参考文章:
[1] Rsync官网对于adler-32算法的描述

rsync是一个用于文件同步和备份的实用工具,而rsync--daemon是rsync的守护进程模式。通过启动rsync守护进程,可以在Linux系统上运行rsync服务,允许其他计算机通过网络连接并与之通信。 要在Linux上启动rsync守护进程,可以按照以下步骤进行操作: 1. 确保你已经安装了rsync软件包。如果没有安装,可以使用适合你的Linux发行版的包管理器进行安装。 2. 编辑rsync的配置文件,通常位于/etc/rsyncd.conf。可以使用文本编辑器(如vi或nano)打开该文件。 3. 在配置文件中,你可以定义rsync守护进程的参数和模块。模块是指定要同步的目录或文件集合。你可以为每个模块指定不同的参数和权限。 以下是一个示例配置文件的简单示例: ``` # /etc/rsyncd.conf # 全局配置 uid = nobody gid = nobody use chroot = yes max connections = 10 log file = /var/log/rsyncd.log # 模块定义 [module1] path = /path/to/module1 comment = Module 1 read only = yes list = yes [module2] path = /path/to/module2 comment = Module 2 read only = no list = yes ``` 在这个例子中,我们定义了两个模块(module1和module2),分别指向不同的目录,并设置了相应的权限和注释。 4. 保存并关闭配置文件。 5. 启动rsync守护进程。在终端中执行以下命令: ``` rsync --daemon ``` 如果一切顺利,rsync守护进程将会启动,并开始监听指定的端口(默认为873)。 现在,其他计算机可以使用rsync客户端连接到你的Linux系统上的rsync守护进程,并进行文件同步和备份操作。例如,可以使用以下命令从客户端同步文件到服务器: ``` rsync -avz /path/to/source username@server_ip::module_name ``` 其中,/path/to/source是要同步的源文件或目录,username是具有合适权限的用户名,server_ip是运行rsync守护进程的服务器IP地址,module_name是在配置文件中定义的模块名称。 这是关于在Linux上使用rsync--daemon的简要说明。有关更多详细信息和参数选项,请查阅rsync的官方文档或执行`man rsync`命令。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

duanbeibei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值