先看下圖中的場景,客戶端A和B,以及服務器server都保存了同一個文件,最初,A、B和server上的文件內容都是相同的(記為File.1)。某一時刻,B修改了文件內容,上傳到SERVER上(記為File.2)。客戶端A這時試圖向服務器SERVER更新文件到最新內容,也就是File.1更新為File.2。


上面這個場景很常見,例如現在流行的網盤。假設我有一個文件a.txt在網盤上,上班時在公司的單位PC上更新了文件a.txt,下班后回到家里,家里PC硬盤上的a.txt就不是最新的內容,這時網盤就試圖從服務器上去拿最新的a.txt了。


那么問題來了,如果在公司電腦上我只是更新了a.txt里很少的一部分內容,例如a.txt共有20M,我只更新了10個字節,難道家里的電腦上,網盤要從服務器上下載20M大小的文件?這明顯很浪費帶寬。

更有用的場景,假設我的手機android上也用了這個網盤(手機上網費貴得多),只改了幾十字節的內容,就要下載20M的文件,得不償失。或者我把這個文件共享給其他朋友,也有同樣的問題:修改少量的內容,卻同步完整的文件!


rsync算法就是用來解決上述問題的。client A發送它所保存的舊文件File.1少量的rsync摘要,server拿到后對比本地的File.2內容,得到File.2相對于File.1的變化,然后通過僅發送這個變化來代替發送完整的File.2內容,這樣大大減少了網絡傳輸數據。client A收到這個變化后,更新本地的File.1到最新的File.2。就是這么簡單。下面詳述rsync算法的步驟。


rsync首先需要客戶端與服務器之間約定一個塊大小,例如1K。然后把File.1等分成多個1K大小的字符串塊,每塊各計算出MD5摘要和Alder32校驗和,如下圖。


這里簡單介紹下MD5和校驗和。MD5是種哈希算法,用于把任意長度的字符串轉化為固定為128位的定長字符串,這里可以保證,相同的字符串不可能計算出不同的MD5值。MD5的碰撞率是有的,就是說,兩個不同的字符串有可能計算出相同的MD5值,但是這個機率非常小,這里我們忽略不計。例如,在rsync算法里,同一個文件按1K切分成多塊,每塊都有一個MD5值,如果兩塊字符串的MD5值相同,則我們認為這兩塊數據完全相同。

校驗和是把上述1K塊數據映射為32位大小整型數字上,我們采用Alder32算法,這里同樣可以保證,相同的字符串不可能計算出不同的Alder32值。Alder32有兩個優點:1、計算非常快,比MD5快多了,成本小;2、當我們有了從0-1024長度的校驗和后,計算出1-1025或者2-1026等其他校驗和非常方便,只要少量運算即可。當然,它的缺點也很明顯,就是碰撞率比MD5高多了,所以,我們要把每個rynsc塊同時計算出Alder32校驗和與MD5值。Alder32算法我會在本文最后解釋。


客戶端按1K大小劃分File.1文件為許多塊,并對每塊計算出MD5、Alder32校驗和。最后不滿1K的數據不做計算。之后,客戶端把這些MD5、Alder32校驗和依序通過網絡傳輸給服務器,最后不滿1K的數據直接發給服務器。那么,服務器收到數據后怎么處理呢?看下圖。


首先重申,計算Alder32校驗和非常快!

所以,服務器先把最新文件File.2從0字節開始,按1K切分成許多塊,每塊計算出Alder32校驗和,然后與客戶端發來的File.1切分出來的Alder32校驗和相比,如果alder32值都不一樣,毫無疑問,文件內容是不相同的。接著,把File.2從1字節開始,按1K切分成許多塊,每塊計算出Alder32校驗和,再與客戶端的校驗和比。如此循環下去,直到某個校驗和相同了,那么把這段字符串再計算出MD5值,再與客戶端過來的對應的MD5值相比(還記得吧?客戶端對每個塊既計算出Alder32又計算出MD5值),如果不同,則繼續往后移1字節,繼續比Alder32、MD5值。如果相同,則認為這1K數據,服務器與客戶端保存的一致,忽略這塊數據(例如1K字節),繼續向下看。


全部處理完后,按File.2的文件順序,向客戶端發送以下數據:對于不能夠在客戶端File.1數據塊中找到相同塊的字符串,直接列上發出;如果可以找到,則寫上MD5和Alder32值,代替原來1024字節的數據塊。同樣,最后不足1K大小的部分直接列上發出。


純理論讀起來會有些吃力,我再把它簡化了舉個例子吧。假設客戶端與服務器間約定的字符塊大小不是1K,而是4個字節。客戶端的文件內容是:

taohuiissoman

而服務器的文件內容是:

itaohuiamsoman

現在我們來看看,rsync算法是怎么運作的。


首先,客戶端開始分塊并計算出MD5和Alder32值。


如上圖,像taoh是一塊,對taoh分別計算出MD5和alder32值。以此類推,最后一個n字母不足4位保留。于是,客戶端把計算出的MD5和alder32按順序發出,最后發出字符n。


服務器收到后,先把自己保存的File.2的內容按4字節劃分。


劃分出itao、huia、msom、an,當然,這些串的Alder32值肯定無法從File.1里劃分出的:taoh、uiis、soma、n找出相同的。于是向后移一個字節,從t開始繼續按4字節劃分。


從taoh上找到了alder32相同的塊,接著再比較MD5值,也相同!于是記下來,跳過taoh這4個字符,看uiam,又找不到File.1上相同的塊了。繼續向后跳1個字節從i開始看。還是沒有找到Alder32相同,繼續向后移,以此類推。


到了soma,又找到相同的塊了。


重復上面的步驟,直到File.2文件結束。


那么,最終客戶端與服務器間傳輸的數據如下圖所示。


上面這個例子很簡單,可由此推導出復雜的情況,包括File.2對File.1在任意位置上做了增、改、刪,都能夠完成。

如果這是個大文本文件,應用rsync算法就非常有意義,例如20M的文件,實際可能只傳輸1M的數據量!這樣用戶體驗會好很多,特別是網速慢的場景。

同時增加的消耗,就是在PC上計算的MD5值和Alder32校驗和,這只消耗少量的CPU和內存而已。


最后列下Alder32的算法:

  1. A = 1 + D1 + D2 + ... + Dn (mod 65521)  
  2. B = (1 + D1) + (1 + D1 + D2) + ... + (1 + D1 + D2 + ... + Dn) (mod 65521)  
  3.   = n×D1 + (n−1)×D2 + (n−2)×D3 + ... + Dn + n (mod 65521)  
  4.   
  5. Adler-32(D) = B × 65536 + A  

D1到Dn就是待計算的字符串塊,所有位上的ASC字符。它的C代碼實現為:

  1. const int MOD_ADLER = 65521;  
  2.    
  3. unsigned long adler32(unsigned char *data, int len) /* where data is the location of the data in physical memory and  
  4.                                                        len is the length of the data in bytes */  
  5. {  
  6.     unsigned long a = 1, b = 0;  
  7.     int index;  
  8.    
  9.     /* Process each byte of the data in order */  
  10.     for (index = 0; index < len; ++index)  
  11.     {  
  12.         a = (a + data[index]) % MOD_ADLER;  
  13.         b = (b + a) % MOD_ADLER;  
  14.     }  
  15.    
  16.     return (b << 16) | a;  
  17. }  





分享到: 
13
0
查看評論
11樓 zj304292653 2012-03-09 17:40發表 [回復]
非常好!
10樓 rmm0811 2012-03-05 09:38發表 [回復]
有疑問請教博主,呼喚博主~~~
這個方法只能解決文件同步到本地的減少帶寬的問題,解決不了文件上傳的問題,若是本地修改為file2,那么應該只上傳修改部分,這部分有沒有什么比較好的方法,求解~~~
Re: russell_tao 2012-03-05 10:27發表 [回復]
回復rmm0811:把CLIENT和SERVER的角色掉個位置就可以了
Re: rmm0811 2012-03-06 14:06發表 [回復]
回復russell_tao:不太明白怎么掉個,客戶端只保存有現有的文件即file2,沒有原始文件file1,怎么能夠執行滑動窗口算法呢?
難道是客戶端本地做了類似版本控制的東東么?
Re: russell_tao 2012-03-06 19:36發表 [回復]
回復rmm0811:比如,client發請求給server,server再發rsyn摘要到client,client對比本地文件找到變化,再發變化到server,然后server根據變化更新文件
9樓 leehark 2012-03-03 19:53發表 [回復]
現在Rsync算法在網盤的文件同步上會減少多少百分點的數據傳輸呢?
按我的習慣,我的大部分文件傳輸都消耗在大文件傳輸上,如很多視頻、音頻文件或者rar文件。
Re: russell_tao 2012-03-05 10:26發表 [回復]
回復leehark: RSYNC的發明者寫過測試報告,大概20M的LOG,做簡單的修改,大約20M的文件,傳輸只有1M左右大小。
8樓 chhxlqqx41 2012-03-02 16:03發表 [回復]
cwRsync 也是基于cygwin的,還有DeltaCopy等。
我們在開發一款跨平臺的網絡軟件,其中希望使用rsync算法作同步。控制的粒度比較細微。
Re: russell_tao 2012-03-02 16:36發表 [回復]
回復chhxlqqx41:rsync算法不復雜,可以自己寫啊
7樓 chhxlqqx41 2012-03-02 15:38發表 [回復]
請教一下博主:
請教一下博主:
請教一下博主:

知不知道有沒有一款軟件是把rsync算法實現在windows上?
不包括基于cygwin等環境運行的。
Re: russell_tao 2012-03-02 15:53發表 [回復]
回復chhxlqqx41:是cwRsync?對windows上的rsync沒了解過
6樓 canye 2012-03-02 12:17發表 [回復]
要是是圖片或者視頻文件我更改了大小等操作就不太好比較了。還是這個要是我在中間隨機的插入了內容也不太好比較了。只有在文本文件的最后面追加了內容的時候才能體現最好的效果
Re: russell_tao 2012-03-02 14:42發表 [回復]
回復canye:只是對文檔、日志類文件才有效
Re: forum_gogo 2012-03-06 00:49發表 [回復]
回復russell_tao:如果只是文本,不如使用svn的增量保存方法
服務器就簡單多了,不需要多少計算
不過svn跟網盤應用場景不同
5樓 oliveevilo 2012-03-02 08:55發表 [回復]
方法講得很明白,就是最后一幅圖有點暈
4樓 beijiguangyong 2012-03-02 00:35發表 [回復]
蒙了,呵呵
3樓 jianlajidexiaohuo 2012-03-01 08:41發表 [回復]
這圖畫的,真棒
2樓 wapjia43106140 2012-03-01 08:36發表 [回復]
不過還是會循環進行字符查找,與算法比較,可能N!次。、。個過程算過沒有。要進行多少字節的數據傳送。還有要進行多少次比較。
Re: russell_tao 2012-03-01 08:41發表 [回復]
回復wapjia43106140:如果只是在文件的首、尾、中增加少量內容,比較還是很少的。如果文件許多地方都發生了變動,會發生比較多的計算alder32值
Re: lrj2005 2012-03-01 09:34發表 [回復]
回復russell_tao:我認為這種算法只對在文件尾追加或刪除的有效,如果在文件首部修改少量字符,都會導致后面每k算出的md5值不一樣,這樣的結果和整個文件傳輸的效果也出不多,效率不高
Re: russell_tao 2012-03-01 10:01發表 [回復]
回復lrj2005:不會的,比如1M的文件,如果我們每塊按1K劃分,現在在首部加1個字符,那么比較時,會0-1024找不到alder32相同的,然后在1-1025上找,就找到了!然后直接跳到1026-2049,又找到了。在首尾加都一樣的
Re: hnwyllmm 2012-03-02 09:26發表 [回復]
回復russell_tao:這一點太經典了,讓我茅塞頓開
Re: wapjia43106140 2012-03-01 08:49發表 [回復]
回復russell_tao:比如你WORD可進行幾個回車的可能改變一大片內容。
我感覺SVN不錯。不知道他怎么弄的。
1樓 margaretMYQ 2012-02-28 21:51發表 [回復] [引用] [舉報]
還是有圖比較容易懂
頂一下
Re: russell_tao 2012-02-29 09:29發表 [回復] [引用] [舉報]
回復margaretMYQ:畫圖其實比碼字速度更快:-)