本篇為rsync官方推薦技術報告rsync technical report的翻譯,主要內容是Rsync的算法原理以及rsync實現這些原理的方法。翻譯過程中,在某些不易理解的地方加上了譯者本人的注釋。
本人譯作集合: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命令中文手冊)
Rsync 算法
Andrew Tridgell Paul Mackerras
Department of Computer Science
Australian National University
Canberra, ACT 0200, Australia
1.1 摘要
本報告介紹了一種將一台機器上的文件更新到和另一台機器上的文件保持一致的算法。我們假定兩台機器之間通過低帶寬、高延遲的雙向鏈路進行通信。該算法計算出源文件中和目標文件中一致的部分(譯者注:數據塊一致的部分),然后僅發送那些無法匹配(譯者注:即兩端文件中不一致的部分)的部分。實際上,該算法計算出了兩個機器上兩文件之間一系列的不同之處。如果兩文件相似,該算法的工作效率非常高,但即使兩文件差別非常大,也能保證正確且有一定效率的工作。
1.2 The problem
假設你有兩個文件A和B,你希望更新B讓它和A完全相同,最直接的方法是拷貝A變為B。
但想象一下,如果這兩個文件所在機器之間以極慢的通信鏈路進行連接通信,例如使用撥號的IP鏈路。如果A文件很大,拷貝A到B速度是非常慢的。為了提高速度,你可以將A壓縮后發送,但這種方法一般只能獲得20%到40%的提升。
現在假定A和B文件非常相似,也許它們兩者都是從同一個源文件中分離出來的。為了真正的提高速度,你需要利用這種相似性。一個通用的方法是通過鏈路僅發送A和B之間差異的部分,然后根據差異列表重組文件(譯者注:所以還需要創建差異列表,發送差異列表,最后匹配差異列表並重組)。
這種通用方法的問題是,要創建兩個文件之間的差異集合,它依賴於有打開並讀取兩文件的能力。因此,這種方法在讀取兩文件之前,要求事先從鏈路的另一端獲取文件。如果無法從另一端獲取文件,這種算法就會失效(但如果真的從另一端獲取了文件,兩文件將同在一台機器上,此時直接拷貝即可,沒有必要比較這些文件的差異)。rsync就是處理這種問題的。
rsync算法能高效率地計算出源文件和目標已存在文件相同的部分(譯者注:即能匹配出相同的數據塊)。這些相同的部分不需要通過鏈路發送出去;所有需要做的是引用目標文件中這些相同的部分來做匹配參照物(譯者注:即基准文件basis file)。只有源文件中不能匹配上的部分才會以純數據的方式被逐字節發送出去。之后,接收端就可以通過這些已存在的相同部分和接收過來的逐字節數據組建成一個源文件的副本。
一般來說,發送到接收端的數據可以使用任意一種常見的壓縮算法進行壓縮后傳輸,以進一步提高速度。
1.3 The rsync algorithm
假設我們有兩台計算機α和β,α上有可以訪問的文件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算法過程中做了什么事就夠了。)
1.4 Rolling checksum
rsync算法使用的弱滾動校驗(rolling checksum)需要能夠快速、低消耗地根據給定的緩沖區X1 ...Xn 的校驗值以及X1、Xn+1的字節值計算出緩沖區的校驗值。
我們在rsync中使用的弱校驗算法設計靈感來自Mark Adler的adler-32校驗。我們的校驗定義公式為:
其中s(k,l)是字節的滾動校驗碼。為了簡單快速地計算出滾動校驗碼,我們使用
。
此校驗算法最重要的特性是能使用遞歸關系非常高效地計算出連續的值。
因此可以為文件中任意偏移位置的S長度的數據塊計算出校驗碼,且計算次數非常少。
盡管該算法足夠簡單,但它已經足夠作為兩個文件數據塊匹配時的第一層檢查。我們在實踐中發現,當數據塊內容不同時,校驗碼(rolling checksum)能匹配上的概率是很低的。這一點非常重要,因為每個弱校驗能匹配上的數據塊都必須去計算強校驗碼,而計算強校驗碼是非常昂貴的。(譯者注:也就是說,數據塊內容不同,弱校驗碼還是可能會相同(盡管概率很低),也就是rolling checksum出現了碰撞,所以還要對弱校驗碼相同的數據塊計算強校驗碼以做進一步匹配)
1.5 Checksum searching
當α收到了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的一系列數據塊,這時對數據塊索引號進行編碼將是一件很簡單的事。
1.6 Pipelining
上面幾個小章節描述了在遠程系統上組建一個文件副本的過程。如果我們要拷貝一系列文件,我們可以將過程流水線化(pipelining the process)以期獲得很可觀的延遲上的優勢。
這要求β上啟動的兩個獨立的進程。其中一個進程負責生成和發送校驗碼給α,另一個進程則負責從α接收不同的信息數據以便重組文件副本。(譯者注:即generator-->sender-->receiver)
如果鏈路上的通信是被緩沖的,兩個進程可以相互獨立地不斷向前工作,並且大多數時間內,可以保持鏈路在雙方向上被充分利用。
1.7 Results
為了測試該算法,創建了兩個不同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 |