本篇爲rsync官方推薦技術報告rsync technical report的翻譯,主要內容是Rsync的算法原理以及rsync實現這些原理的方法。翻譯過程當中,在某些不易理解的地方加上了譯者本人的註釋。html
本人譯做集合:http://www.cnblogs.com/f-ck-need-u/p/7048359.html算法
如下是rsync系列篇:
1.rsync(一):基本命令和用法
2.rsync(二):inotify+rsync詳細說明和sersync
3.rsync算法原理和工做流程分析
4.rsync技術報告(翻譯)
5.rsync工做機制(翻譯)
6.man rsync翻譯(rsync命令中文手冊)測試
本報告介紹了一種將一臺機器上的文件更新到和另外一臺機器上的文件保持一致的算法。咱們假定兩臺機器之間經過低帶寬、高延遲的雙向鏈路進行通訊。該算法計算出源文件中和目標文件中一致的部分(譯者注:數據塊一致的部分),而後僅發送那些沒法匹配(譯者注:即兩端文件中不一致的部分)的部分。實際上,該算法計算出了兩個機器上兩文件之間一系列的不一樣之處。若是兩文件類似,該算法的工做效率很是高,但即便兩文件差異很是大,也能保證正確且有必定效率的工做。編碼
假設你有兩個文件A和B,你但願更新B讓它和A徹底相同,最直接的方法是拷貝A變爲B。spa
但想象一下,若是這兩個文件所在機器之間以極慢的通訊鏈路進行鏈接通訊,例如使用撥號的IP鏈路。若是A文件很大,拷貝A到B速度是很是慢的。爲了提升速度,你能夠將A壓縮後發送,但這種方法通常只能得到20%到40%的提高。翻譯
如今假定A和B文件很是類似,也許它們二者都是從同一個源文件中分離出來的。爲了真正的提升速度,你須要利用這種類似性。一個通用的方法是經過鏈路僅發送A和B之間差別的部分,而後根據差別列表重組文件(譯者注:因此還須要建立差別列表,發送差別列表,最後匹配差別列表並重組)。設計
這種通用方法的問題是,要建立兩個文件之間的差別集合,它依賴於有打開並讀取兩文件的能力。所以,這種方法在讀取兩文件以前,要求事先從鏈路的另外一端獲取文件。若是沒法從另外一端獲取文件,這種算法就會失效(但若是真的從另外一端獲取了文件,兩文件將同在一臺機器上,此時直接拷貝便可,沒有必要比較這些文件的差別)。rsync就是處理這種問題的。3d
rsync算法能高效率地計算出源文件和目標已存在文件相同的部分(譯者注:即能匹配出相同的數據塊)。這些相同的部分不須要經過鏈路發送出去;全部須要作的是引用目標文件中這些相同的部分來作匹配參照物(譯者注:即基準文件basis file)。只有源文件中不能匹配上的部分纔會以純數據的方式被逐字節發送出去。以後,接收端就能夠經過這些已存在的相同部分和接收過來的逐字節數據組建成一個源文件的副本。htm
通常來講,發送到接收端的數據可使用任意一種常見的壓縮算法進行壓縮後傳輸,以進一步提升速度。blog
假設咱們有兩臺計算機α和β,α上有能夠訪問的文件A,β上有能夠訪問的文件B,且A和B兩文件是類似的。α和β之間以低速鏈路通訊。
rsync算法由如下過程組成:
1.β將文件B分割爲一系列不重疊且大小固定爲S字節(譯者注:做者們備註說500到1000字節比較適合)的數據塊。固然,最後一個數據塊可能小於S字節。
2.β對每一個這樣的數據塊都計算出兩個校驗碼:32位的弱校驗碼rolling-checksum和128位的強校驗碼MD4-checksum(譯者注:如今的rsync使用的是128位的MD5-checksum)。
3.β將這些校驗碼發送給α。
4.α將搜索文件A,從中查找出全部長度爲S字節且和B中兩個校驗碼相同的數據塊(從任意偏移量搜索)。這個搜索和比較的過程能夠經過使用弱滾動校驗(rolling checksum)的特殊功能很是快速地完成。
(譯者注:以字符串123456爲例,要搜索出包含3個字符的字符串,若是以任意偏移量的方式搜索全部3個字符長度的字符串,最基本方法是從1開始搜索獲得123,從2開始搜索獲得234,從3開始搜索獲得345,直到搜索完成。這就是任意偏移量的意思,即從任意位置搜索長度爲S的數據塊)
(譯者再注:之因此要以任意偏移量搜索,考慮一種狀況,現有兩個徹底相同的文件A和B,如今向A文件的中間插入一段數據,那麼A中從這段數據開始,緊跟其後的全部數據塊的偏移量都向後挪動了一段長度,若是不以任意偏移量搜索固定長度數據塊,那麼重新插入的這段數據開始,全部的數據塊都和B不同,在rsync中意味着這些數據塊都要傳輸,但實際上A和B不一樣的數據塊只有插入在中間的那一段而已)
5.α將一系列的指令發送給β以使其構造出A文件的副本。每一個指令要麼是對B中數據塊的引用,要麼是純數據。這些純數據是A中沒法匹配到B中數據塊的數據塊部分(譯者注:就是A中不一樣於B的數據塊)。
最終的結果是β獲取到了A的副本,但卻僅發送了A中存在B中不存在的數據部分(還包括一些校驗碼以及數據塊索引數據)。該算法也僅只要求一次鏈路往返(譯者注:第一次鏈路通訊是β發送校驗碼給α,第二次通訊是α發送指令、校驗碼、索引和A中存在B中不存在的純數據給β),這能夠最小化鏈路延遲致使的影響。
該算法最重要的細節是滾動校驗(rolling checksum)以及與之相關的多備選(multi-alternate)搜索機制,它們保證了全部偏移校驗(all-offsets checksum,即上文步驟4中從任意偏移位置搜索數據塊)的搜索過程能夠處理的很是迅速。下文將更詳細地討論它們。
(譯者注:若是不想看算法理論,下面的算法具體內容能夠跳過不看(能夠看下最後的結論),只要搞懂上面rsync算法過程當中作了什麼事就夠了。)
rsync算法使用的弱滾動校驗(rolling checksum)須要可以快速、低消耗地根據給定的緩衝區X1 ...Xn 的校驗值以及X1、Xn+1的字節值計算出緩衝區的校驗值。
咱們在rsync中使用的弱校驗算法設計靈感來自Mark Adler的adler-32校驗。咱們的校驗定義公式爲:
其中s(k,l)是字節的滾動校驗碼。爲了簡單快速地計算出滾動校驗碼,咱們使用。
此校驗算法最重要的特性是能使用遞歸關係很是高效地計算出連續的值。
所以能夠爲文件中任意偏移位置的S長度的數據塊計算出校驗碼,且計算次數很是少。
儘管該算法足夠簡單,但它已經足夠做爲兩個文件數據塊匹配時的第一層檢查。咱們在實踐中發現,當數據塊內容不一樣時,校驗碼(rolling checksum)能匹配上的機率是很低的。這一點很是重要,由於每一個弱校驗能匹配上的數據塊都必須去計算強校驗碼,而計算強校驗碼是很是昂貴的。(譯者注:也就是說,數據塊內容不一樣,弱校驗碼仍是可能會相同(儘管機率很低),也就是rolling checksum出現了碰撞,因此還要對弱校驗碼相同的數據塊計算強校驗碼以作進一步匹配)
當α收到了B文件數據塊的校驗碼列表,它必需要去搜索A文件的數據塊(以任意偏移量搜索),目的是找出能匹配B數據塊校驗碼的數據塊部分。基本的策略是從A文件的每一個字節開始依次計算長度爲S字節的數據塊的32位弱滾動校驗碼(rolling checksum),而後對每個計算出來的弱校驗碼,都拿去和B文件校驗碼列表中的校驗碼進行匹配。在咱們實現的算法上,使用了簡單的3層搜索檢查(譯者注:3步搜索過程)。
第一層檢查,對每一個32位弱滾動校驗碼都計算出一個16位長度的hash值,並將每216個這樣的hash條目組成一張hash表。根據這個16位hash值對校驗碼列表(例如接收到的B文件數據塊的校驗碼集合)進行排序。hash表中的每一項都指向校驗碼列表中對應hash值的第一個元素(譯者注:即數據塊ID),或者當校驗碼列表中沒有對應的hash值時,此hash表項將是一個空值(譯者注:之因此有空校驗碼以及對應空hash值出現的可能,是由於β會將那些α上有而β上沒有的文件的校驗碼設置爲空並一同發送給α,這樣α在搜索文件時就會知道β上沒有該文件,而後直接將此文件整個發送給β)。
對文件中的每一個偏移量,都會計算它的32位滾動校驗碼和它的16位hash值。若是該hash值的hash表項是一個非空值,將調用第二層檢查。
(譯者注:也就是說,第一層檢查是比較匹配16位的hash值,能匹配上則認爲該數據塊有潛在相同的可能,而後今後數據塊的位置處進入第二層檢查)
第二層檢查會對已排序的校驗碼列表進行掃描,它將從hash表項指向的條目處(譯者注:此條目對應第一層檢查結束後能匹配的數據塊)開始掃描,目的是查找出能匹配當前值的32位滾動校驗碼。當掃描到相同32位弱滾動校驗值時或者直到出現不一樣16位hash值都沒有匹配的32位弱校驗碼時,掃描終止(譯者注:因爲hash值和弱校驗碼重複的機率很低,因此基本上向下再掃描1項最多2項就能發現沒法匹配的弱滾動校驗碼)。若是搜索到了能匹配的結果,則調用第三層檢查。
(譯者注:也就是說,第二層檢查是比較匹配32位的弱滾動校驗碼,能匹配上則表示仍是有潛在相同的可能性,而後今後位置處開始進入第三層檢查,若沒有匹配的弱滾動校驗碼,則說明不一樣數據塊內容的hash值出現了重複,但好在弱滾動校驗的匹配將其排除掉了)
第三層檢查會對文件中當前偏移量的數據塊計算強校驗碼,並將其與校驗碼列表中的強校驗碼進行比較。若是兩個強校驗碼能匹配上,咱們認爲A中的數據塊和B中的數據塊徹底相同。理論上,這些數據塊仍是有可能會不一樣,可是機率是極其微小的,所以在實踐過程當中,咱們認爲這是一個合理的假設。
當發現了能匹配上的數據塊,α會將A文件中此數據塊的偏移量和前一個匹配數據塊的結束偏移地址發送給β,還會發送這段匹配數據塊在B中數據塊的索引(譯者注:即數據塊的ID,chunk號碼)。當發現能匹配的數據時,這些數據(譯者注:包括匹配上的數據塊相關的重組指令以及處於兩個匹配塊中間未被匹配的數據塊的純數據)會當即被髮送,這使得咱們能夠將通訊與進一步的計算並行執行。
若是發現文件中當前偏移量的數據塊沒有匹配上時,弱校驗碼將向下滾動到下一個偏移地址而且繼續開始搜索(譯者注:也就是說向下滾動了一個字節)。若是發現能匹配的值時,弱校驗碼搜索將從匹配到的數據塊的終止偏移地址從新開始(譯者注:也就是說向下滾動了一個數據塊)。對於兩個幾乎一致的文件(這是最多見的狀況),這種策略節省了大量的計算量。另外,對於最多見的狀況,A的一部分數據能依次匹配上B的一系列數據塊,這時對數據塊索引號進行編碼將是一件很簡單的事。
上面幾個小章節描述了在遠程系統上組建一個文件副本的過程。若是咱們要拷貝一系列文件,咱們能夠將過程流水線化(pipelining the process)以期得到很可觀的延遲上的優點。
這要求β上啓動的兩個獨立的進程。其中一個進程負責生成和發送校驗碼給α,另外一個進程則負責從α接收不一樣的信息數據以便重組文件副本。(譯者注:即generator-->sender-->receiver)
若是鏈路上的通訊是被緩衝的,兩個進程能夠相互獨立地不斷向前工做,而且大多數時間內,能夠保持鏈路在雙方向上被充分利用。
爲了測試該算法,建立了兩個不一樣Linux內核版本的源碼文件的tar包。這兩個內核版本分別是1.99.10和2.0.0。這個tar包大約有24M且5個不一樣版本的補丁分隔。
在1.99.10版本中的2441個文件中,2.0.0版本中對其中的291個文件作了更改,並刪除了19個文件,以及添加了25個新文件。
使用標準GNU diff程序對這兩個tar包進行"diff"操做,結果產生了超過32000行總共2.1 MB的輸出。
下表顯示了兩個文件間使用不一樣數據塊大小的rsync的結果。
block size |
matches |
tag hits |
false alarms |
data |
written |
read |
300 |
64247 |
3817434 |
948 |
5312200 |
5629158 |
1632284 |
500 |
46989 |
620013 |
64 |
1091900 |
1283906 |
979384 |
700 |
33255 |
571970 |
22 |
1307800 |
1444346 |
699564 |
900 |
25686 |
525058 |
24 |
1469500 |
1575438 |
544124 |
1100 |
20848 |
496844 |
21 |
1654500 |
1740838 |
445204 |
在每種狀況下,所佔用的CPU時間都比在兩個文件間直接運行"diff"所需時間少。
表中各列的意思是:
block size:計算校驗和的數據塊大小,單位字節。
matches:從A中匹配出B中的某個數據塊所匹配的次數。(譯者注:相比於下面的false matches,這個是true matches)
tag hits:A文件中的16位hash值能匹配到B的hash表項所需的匹配次數。
false alarms:32位弱滾動校驗碼能匹配但強校驗碼不能匹配的匹配次數。(譯者注:即不一樣數據塊的rolling checksum出現小几率假重複的次數)
data:逐字節傳輸的文件純數據,單位字節。
written:α所寫入的總字節數,包括協議開銷。這幾乎全是文件中的數據。
read:α所讀取的總字節數,包括協議開銷,這幾乎全是校驗碼信息數據。
結果代表,當塊大小大於300字節時,僅傳輸了文件的一小部分(大約5%)數據。並且,相比使用diff/patch方法更新遠端文件,rsync所傳輸的數據也要少不少。
每一個校驗碼對佔用20個字節:弱滾動校驗碼佔用4字節,128位的MD4校驗碼佔用16字節。所以,那些校驗碼自己也佔用了很多空間,儘管相比於傳輸的純數據大小而言,它們要小的多。
false alarms少於true matches次數的1/1000,這說明32位滾動校驗碼能夠很好地檢測出不一樣數據塊。
tag hits的數值代表第二層校驗碼搜索檢查算法大體每50個字符被調用一次(譯者注:以block size=1100那行數據爲例,50W*50/1024/1024=23M)。這已經很是高了,由於在這個文件中全部數據塊的數量是很是大的,所以hash表也是很是大的。文件越小,咱們指望tag hit的比例越接近於匹配次數。對於極端狀況的超大文件,咱們應該合理地增大hash表的大小。
下一張表顯示了更小文件的結果。這種狀況下,衆多文件並無被歸檔到一個tar包中,而是經過指定選項使得rsync向下遞歸整個目錄樹。這些文件是以另外一個稱爲Samba的軟件包爲源抽取出來的兩個版本,源碼大小爲1.7M,對這兩個版本的文件運行diff程序,結果輸出4155行共120kB大小。
block size |
matches |
tag hits |
false alarms |
data |
written |
read |
300 |
3727 |
3899 |
0 |
129775 |
153999 |
83948 |
500 |
2158 |
2325 |
0 |
171574 |
189330 |
50908 |
700 |
1517 |
1649 |
0 |
195024 |
210144 |
36828 |
900 |
1156 |
1281 |
0 |
222847 |
236471 |
29048 |
1100 |
921 |
1049 |
0 |
250073 |
262725 |
23988 |