詳解rsync算法--如何減小同步文件時的網絡傳輸量

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


上面這個場景很常見,例如如今流行的網盤。假設我有一個文件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算法的步驟。ide


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


這裏簡單介紹下MD5和校驗和。MD5是種哈希算法,用於把任意長度的字符串轉化爲固定爲128位的定長字符串,這裏能夠保證,相同的字符串不可能計算出不一樣的MD5值。MD5的碰撞率是有的,就是說,兩個不一樣的字符串有可能計算出相同的MD5值,可是這個機率很是小,這裏咱們忽略不計。例如,在rsync算法裏,同一個文件按1K切分紅多塊,每塊都有一個MD5值,若是兩塊字符串的MD5值相同,則咱們認爲這兩塊數據徹底相同。spa

校驗和是把上述1K塊數據映射爲32位大小整型數字上,咱們採用Alder32算法,這裏一樣能夠保證,相同的字符串不可能計算出不一樣的Alder32值。Alder32有兩個優勢:一、計算很是快,比MD5快多了,成本小;二、當咱們有了從0-1024長度的校驗和後,計算出1-1025或者2-1026等其餘校驗和很是方便,只要少許運算便可。固然,它的缺點也很明顯,就是碰撞率比MD5高多了,因此,咱們要把每一個rynsc塊同時計算出Alder32校驗和與MD5值。Alder32算法我會在本文最後解釋。.net


客戶端按1K大小劃分File.1文件爲許多塊,並對每塊計算出MD五、Alder32校驗和。最後不滿1K的數據不作計算。以後,客戶端把這些MD五、Alder32校驗和依序經過網絡傳輸給服務器,最後不滿1K的數據直接發給服務器。那麼,服務器收到數據後怎麼處理呢?看下圖。orm


首先重申,計算Alder32校驗和很是快!server

因此,服務器先把最新文件File.2從0字節開始,按1K切分紅許多塊,每塊計算出Alder32校驗和,而後與客戶端發來的File.1切分出來的Alder32校驗和相比,若是alder32值都不同,毫無疑問,文件內容是不相同的。接着,把File.2從1字節開始,按1K切分紅許多塊,每塊計算出Alder32校驗和,再與客戶端的校驗和比。如此循環下去,直到某個校驗和相同了,那麼把這段字符串再計算出MD5值,再與客戶端過來的對應的MD5值相比(還記得吧?客戶端對每一個塊既計算出Alder32又計算出MD5值),若是不一樣,則繼續日後移1字節,繼續比Alder3二、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的算法:

[cpp]  view plain copy
  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代碼實現爲:

[cpp]  view plain copy
  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. }  
相關文章
相關標籤/搜索