最近在學習rsync工具的核心算法,主要參考的是collshell上的一篇文章。
rsync主要解決的是文件的同步問題,比如有A,B兩臺機器,要把A上的一個文件src更新到B上,假如B上已經有一個較舊的版本dest,這時怎么傳輸最省流量。
用過svn等版本工具的都知道,只需要把差異化的部分傳輸過去就夠了,比如src比dest就多了一行內容,那么把這一行傳輸過去就夠了,也就是文件做diff。可是現在有一個問題,src與dest分別在兩臺不同的機器上,而文件要做diff,得同時有兩個文件才行,這就相當于要先把dest傳回給A,這恐怕不是我們想要的。
有什么更好的辦法呢?
可以把文件分塊,比如128字節一個塊,然后這個塊的內容計算一個hash值,dest文件做了這些事情后,把hash信息發送到src處,src也這樣干,然后兩者逐一匹配,不就知道哪一塊不同,然后就可以把差異傳過去了,對吧。
問題來了,如果src文件在某個地方增加了一個字母A,這樣分塊的時候,src計算出來的hash值,整個都錯亂了,會傳輸很多無用的數據,其實我們應該只需要傳輸一個字母A給dest,怎么解決呢?
采取一個滑動窗口的形式,比如分塊是128字節,src先計算第一個塊的hash值,如果在dest發過來的hash信息中找到了,得到這個塊的id,如果沒找到,把窗口向后移動一個字節,繼續計算該block的hash信息,到dest發來的信息中匹配,同上了。
大概流程:
1.dest文件分塊,計算兩個信息,一個弱的校驗hash,一個強的md5hash,帶上塊id,發送到src處。
2.src計算第一個塊的弱hash值,到dest發來的hash信息中查找:
3.如果找到,很好,說明這一塊的內容很有可能在dest中存在,計算塊的md5進行強校驗。
4.如果沒有找到,說明該塊有差異化內容,窗口往后滑動一個字節,計算該塊的弱hash值,即重復步驟2
最后發給dest的信息是這樣的:
?10|tttt|1|2|5|8|ddddd|1
其中數字表示塊id,其他為差異化內容,dest收到這些信息后,自己重組文件,收工。
其最核心的內容為:窗口往后滑動一個字節計算弱hash值的時候,不再是手動計算,而是可以根據前一個窗口的hash信息推算出來,這樣效率要高很多。假設 h(i,j)表示字節i到j的hash信息,那么h(i+1,j+1)可以根據 A[i],h(i,j),A[j+1]計算出來,我采用的是zlib的adler32算法。
寫了個測試代碼: