rsync算法原理和工做流程分析

本文經過示例詳細分析rsync算法原理和rsync的工做流程,是對rsync官方技術報告官方推薦文章的解釋。本文不會介紹如何使用rsync命令(見rsync基本用法),而是詳細解釋它如何實現高效的增量傳輸。html

如下是rsync系列篇:
  1.rsync(一):基本命令和用法
  2.rsync(二):inotify+rsync詳細說明和sersync
  3.rsync算法原理和工做流程分析
  4.rsync技術報告(翻譯)
  5.rsync工做機制(翻譯)
  6.man rsync翻譯(rsync命令中文手冊)算法


在開始分析算法原理以前,簡單說明下rsync的增量傳輸功能。shell

假設待傳輸文件爲A,若是目標路徑下沒有文件A,則rsync會直接傳輸文件A,若是目標路徑下已存在文件A,則發送端視狀況決定是否要傳輸文件A。rsync默認使用"quick check"算法,它會比較源文件和目標文件(若是存在)的文件大小和修改時間mtime,若是兩端文件的大小或mtime不一樣,則發送端會傳輸該文件,不然將忽略該文件。數據庫

若是"quick check"算法決定了要傳輸文件A,它不會傳輸整個文件A,而是隻傳源文件A和目標文件A所不一樣的部分,這纔是真正的增量傳輸。網絡

也就是說,rsync的增量傳輸體如今兩個方面:文件級的增量傳輸和數據塊級別的增量傳輸。文件級別的增量傳輸是指源主機上有,但目標主機上沒有將直接傳輸該文件,數據塊級別的增量傳輸是指只傳輸兩文件所不一樣的那一部分數據。但從本質上來講,文件級別的增量傳輸是數據塊級別增量傳輸的特殊狀況。通讀本文後,很容易理解這一點。併發

1.1 須要解決的問題

假設主機α上有文件A,主機β上有文件B(實際上這兩文件是同名文件,此處爲了區分因此命名爲A和B),如今要讓B文件和A文件保持同步。app

最簡單的方法是將A文件直接拷貝到β主機上。但若是文件A很大,且B和A是類似的(意味着兩文件實際內容只有少部分不一樣),拷貝整個文件A可能會消耗很多時間。若是能夠拷貝A和B不一樣的那一小部分,則傳輸過程會很快。rsync增量傳輸算法就充分利用了文件的類似性,解決了遠程增量拷貝的問題。ssh

假設文件A的內容爲"123xxabc def",文件B的內容爲"123abcdefg"。A與B相比,相同的數據部分有123/abc/def,A中多出的內容爲xx和一個空格,但文件B比文件A多出了數據g。最終的目標是讓B和A的內容徹底相同。ide

若是採用rsync增量傳輸算法,α主機將只傳輸文件A中的xx和空格數據給β主機,對於那些相同內容123/abc/def,β主機會直接從B文件中拷貝。根據這兩個來源的數據,β主機就能組建成一個文件A的副本,最後將此副本文件重命名並覆蓋掉B文件就保證了同步。工具

雖然看上去過程很簡單,但其中有不少細節須要去深究。例如,α主機如何知道A文件中哪些部分和B文件不一樣,β主機接收了α主機發送的A、B不一樣部分的數據,如何組建文件A的副本。

1.2 rsync增量傳輸算法原理

假設執行的rsync命令是將A文件推到β主機上使得B文件和A文件保持同步,即主機α是源主機,是數據的發送端(sender),β是目標主機,是數據的接收端(receiver)。在保證B文件和A文件同步時,大體有如下6個過程:

(1).α主機告訴β主機文件A待傳輸。

(2).β主機收到信息後,將文件B劃分爲一系列大小固定的數據塊(建議大小在500-1000字節之間),並以chunk號碼對數據塊進行編號,同時還會記錄數據塊的起始偏移地址以及數據塊長度。顯然最後一個數據塊的大小可能更小。

對於文件B的內容"123abcdefg"來講,假設劃分的數據塊大小爲3字節,則根據字符數劃分紅了如下幾個數據塊:

count=4 n=3 rem=1    這表示劃分了4個數據塊,數據塊大小爲3字節,剩餘1字節給了最後一個數據塊
chunk[0]:offset=0 len=3 該數據塊對應的內容爲123
chunk[1]:offset=3 len=3 該數據塊對應的內容爲abc
chunk[2]:offset=6 len=3 該數據塊對應的內容爲def
chunk[3]:offset=9 len=1 該數據塊對應的內容爲g

固然,實際信息中確定是不會包括文件內容的。

(3).β主機對文件B的每一個數據塊根據其內容都計算兩個校驗碼:32位的弱滾動校驗碼(rolling checksum)和128位的MD4強校驗碼(如今版本的rsync使用的已是128位的MD5強校驗碼)。並將文件B計算出的全部rolling checksum和強校驗碼跟隨在對應數據塊chunk[N]後造成校驗碼集合,而後發送給主機α。

也就是說,校驗碼集合的內容大體以下:其中sum1爲rolling checksum,sum2爲強校驗碼。

chunk[0] sum1=3ef2c827 sum2=3efa923f8f2e7
chunk[1] sum1=57ac2aaf sum2=aef2dedba2314
chunk[2] sum1=92d7edb4 sum2=a6sd6a9d67a12
chunk[3] sum1=afe74939 sum2=90a12dfe7485c

須要注意,不一樣內容的數據塊計算出的rolling checksum是有可能相同的,可是機率很是小。

(4).當α主機接收到文件B的校驗碼集合後,α主機將對此校驗碼集合中的每一個rolling checksum計算16位長度的hash值,並將每216個hash值按照hash順序放入一個hash table中,hash表中的每個hash條目都指向校驗碼集合中它所對應的rolling checksum的chunk號碼,而後對校驗碼集合根據hash值進行排序,這樣排序後的校驗碼集合中的順序就能和hash表中的順序對應起來。

因此,hash表和排序後的校驗碼集合對應關係大體以下:假設hash表中的hash值是根據首個字符按照[0-9a-f]的順序進行排序的。

一樣須要注意,不一樣rolling checksum計算出的hash值也是有可能會相同的,機率也比較小,但比rolling checksum出現重複的機率要大一些。

(5).隨後主機α將對文件A進行處理。處理的過程是從第1個字節開始取相同大小的數據塊,並計算它的校驗碼和校驗碼集合中的校驗碼進行匹配。若是能匹配上校驗碼集合中的某個數據塊條目,則表示該數據塊和文件B中數據塊相同,它不須要傳輸,因而主機α直接跳轉到該數據塊的結尾偏移地址,今後偏移處繼續取數據塊進行匹配。若是不能匹配校驗碼集合中的數據塊條目,則表示該數據塊是非匹配數據塊,它須要傳輸給主機β,因而主機α將跳轉到下一個字節,今後字節處繼續取數據塊進行匹配。注意,匹配成功時跳過的是整個匹配數據塊,匹配不成功時跳過的僅是一個字節。能夠結合下一小節的示例來理解。

上面說的數據塊匹配只是一種描述,具體的匹配行爲須要進行細化。rsync算法將數據塊匹配過程分爲3個層次的搜索匹配過程。

首先,主機α會對取得的數據塊根據它的內容計算出它的rolling checksum,再根據此rolling checksum計算出hash值。

而後,將此hash值去和hash表中的hash條目進行匹配,這是第一層次的搜索匹配過程,它比較的是hash值。若是在hash表中能找到匹配項,則表示該數據塊存在潛在相同的可能性,因而進入第二層次的搜索匹配。

第二層次的搜索匹配是比較rolling checksum。因爲第一層次的hash值匹配到告終果,因此將搜索校驗碼集合中與此hash值對應的rolling checksum。因爲校驗碼集合是按照hash值排序過的,因此它的順序和hash表中的順序一致,也就是說只需今後hash值對應的rolling chcksum開始向下掃描便可。掃描過程當中,若是A文件數據塊的rolling checksum能匹配某項,則表示該數據塊存在潛在相同的可能性,因而中止掃描,並進入第三層次的搜索匹配以做最終的肯定。或者若是沒有掃描到匹配項,則說明該數據塊是非匹配塊,也將中止掃描,這說明rolling checksum不一樣,但根據它計算的hash值卻發生了小几率重複事件。

第三層次的搜索匹配是比較強校驗碼。此時將對A文件的數據塊新計算一個強校驗碼(在第三層次以前,只對A文件的數據塊計算了rolling checksum和它的hash值),並將此強校驗碼與校驗碼集合中對應強校驗碼匹配,若是能匹配則說明數據塊是徹底相同的,不能匹配則說明數據塊是不一樣的,而後開始取下一個數據塊進行處理。

之因此要額外計算hash值並放入hash表,是由於比較rolling checksum的性能不及hash值比較,且經過hash搜索的算法性能很是高。因爲hash值重複的機率足夠小,因此對絕大多數內容不一樣的數據塊都能直接經過第一層次搜索的hash值比較出來,即便發生了小几率hash值重複事件,還能迅速定位並比較更小几率重複的rolling checksum。即便不一樣內容計算的rolling checksum也可能出現重複,但它的重複機率比hash值重複機率更小,因此經過這兩個層次的搜索就能比較出幾乎全部不一樣的數據塊。假設不一樣內容的數據塊的rolling checksum仍是出現了小几率重複,它將進行第三層次的強校驗碼比較,它採用的是MD4(如今是MD5),這種算法具備"雪崩效應",只要一點點不一樣,結果都是天翻地覆的不一樣,因此在現實使用過程當中,徹底能夠假設它能作最終的比較。

數據塊大小會影響rsync算法的性能。若是數據塊大小過小,則數據塊的數量就太多,須要計算和匹配的數據塊校驗碼就太多,性能就差,並且出現hash值重複、rolling checksum重複的可能性也增大;若是數據塊大小太大,則可能會出現不少數據塊都沒法匹配的狀況,致使這些數據塊都被傳輸,下降了增量傳輸的優點。因此劃分合適的數據塊大小是很是重要的,默認狀況下,rsync會根據文件大小自動判斷數據塊大小,但rsync命令的"-B"(或"--block-size")選項支持手動指定大小,若是手動指定,官方建議大小在500-1000字節之間。

(6).當α主機發現是匹配數據塊時,將只發送這個匹配塊的附加信息給β主機。同時,若是兩個匹配數據塊之間有非匹配數據,則還會發送這些非匹配數據。當β主機陸陸續續收到這些數據後,會建立一個臨時文件,並經過這些數據重組這個臨時文件,使其內容和A文件相同。臨時文件重組完成後,修改該臨時文件的屬性信息(如權限、全部者、mtime等),而後重命名該臨時文件替換掉B文件,這樣B文件就和A文件保持了同步。

1.3 經過示例分析rsync算法

前面說了這麼多理論,可能已經看的雲裏霧裏,下面經過A和B文件的示例來詳細分析上一小節中的增量傳輸算法原理,因爲上一小節中的過程(1)-(4),已經給出了示例,因此下面將繼續分析過程(5)和過程(6)。

先看文件B(內容爲"123abcdefg")排序後的校驗碼集合以及hash表。

當主機α開始處理文件A時,對於文件A的內容"123xxabc def"來講,從第一個字節開始取大小相同的數據塊,因此取得的第一個數據塊的內容是"123",因爲和文件B的chunk[0]內容徹底相同,因此α主機對此數據塊計算出的rolling checksum值確定是"3ef2e827",對應的hash值爲"e827"。因而α主機將此hash值去匹配hash表,匹配過程當中發現指向chunk[0]的hash值能匹配上,因而進入第二層次的rolling checksum比較,也即今後hash值指向的chunk[0]的條目處開始向下掃描。掃描過程當中發現掃描的第一條信息(即chunk[0]對應的條目)的rollign checksum就能匹配上,因此掃描終止,因而進入第三層次的搜索匹配,這時α主機將對"123"這個數據塊新計算一個強校驗碼,與校驗碼集合中chunk[0]對應的強校驗碼作比較,最終發現能匹配上,因而肯定了文件A中的"123"數據塊是匹配數據塊,不須要傳輸給β主機。

雖然匹配數據塊不用傳輸,但匹配的相關信息須要當即傳輸給β主機,不然β主機不知道如何重組文件A的副本。匹配塊須要傳輸的信息包括:匹配的是B文件中的chunk[0]數據塊,在文件A中偏移該數據塊的起始偏移地址爲第1個字節,長度爲3字節。

數據塊"123"的匹配信息傳輸完成後,α主機將取第二個數據塊進行處理。原本應該是從第2個字節開始取數據塊的,但因爲數據塊"123"中3個字節徹底匹配成功,因此能夠直接跳過整個數據塊"123",即從第4個字節開始取數據塊,因此α主機取得的第2個數據塊內容爲"xxa"。一樣,須要計算它的rolling checksum和hash值,並搜索匹配hash表中的hash條目,發現沒有任何一條hash值能夠匹配上,因而當即肯定該數據塊是非匹配數據塊。

此時α主機將繼續向前取A文件中的第三個數據塊進行處理。因爲第二個數據塊沒有匹配上,因此取第三個數據塊時只跳過了一個字節的長度,即從第5個字節開始取,取得的數據塊內容爲"xab"。通過一番計算和匹配,發現這個數據塊和第二個數據塊同樣是沒法匹配的數據塊。因而繼續向前跳過一個字節,即從第6個字節開始取第四個數據塊,此次取得的數據塊內容爲"abc",這個數據塊是匹配數據塊,因此和第一個數據塊的處理方式是同樣的,惟一不一樣的是第一個數據塊到第四個數據塊,中間兩個數據塊是非匹配數據塊,因而在肯定第四個數據塊是匹配數據塊後,會將中間的非匹配內容(即123xxabc中間的xx)逐字節發送給β主機。

(前文說過,hash值和rolling checksum是有小几率發生重複,出現重複時匹配如何進行?見本小節的尾部)

依此方式處理完A中全部數據塊,最終有3個匹配數據塊chunk[0]、chunk[1]和chunk[2],以及2段非匹配數據"xx"和" "。這樣β主機就收到了匹配數據塊的匹配信息以及逐字節的非匹配純數據,這些數據是β主機重組文件A副本的關鍵信息。它的大體內容以下:

chunk[0] of size 3 at 0 offset=0
data receive 2 at 3
chunk[1] of size 3 at 3 offset=5
data receive 1 at 8
chunk[2] of size 3 at 6 offset=9

爲了說明這段信息,首先看文件A和文件B的內容,並標出它們的偏移地址。

對於"chunk[0] of size 3 at 0 offset=0",這一段表示這是一個匹配數據塊,匹配的是文件B中的chunk[0],數據塊大小爲3字節,關鍵詞at表示這個匹配塊在文件B中的起始偏移地址爲0,關鍵詞offset表示這個匹配塊在文件A中起始偏移地址也爲0,它也能夠認爲是重組臨時文件中的偏移。也就是說,在β主機重組文件時,將從文件B的"at 0"偏移處拷貝長度爲3字節的chunk[0]對應的數據塊,並將這個數據塊內容寫入到臨時文件中的offset=0偏移處,這樣臨時文件中就有了第一段數據"123"。

對於"data receive 2 at 3",這一段表示這是接收的純數據信息,不是匹配數據塊。2表示接收的數據字節數,"at 3"表示在臨時文件的起始偏移3處寫入這兩個字節的數據。這樣臨時文件就有了包含了數據"123xx"。

對於"chunk[1] of size 3 at 3 offset=5",這一段表示是匹配數據塊,表示從文件B的起始偏移地址at=3處拷貝長度爲3字節的chunk[1]對應的數據塊,並將此數據塊內容寫入在臨時文件的起始偏移offset=5處,這樣臨時文件就有了"123xxabc"。

對於"data receive 1 at 8",這一說明接收了純數據信息,表示將接收到的1個字節的數據寫入到臨時文件的起始偏移地址8處,因此臨時文件中就有了"123xxabc "。

最後一段"chunk[2] of size 3 at 6 offset=9",表示從文件B的起始偏移地址at=6處拷貝長度爲3字節的chunk[2]對應的數據塊,並將此數據塊內容寫入到臨時文件的起始偏移offset=9處,這樣臨時文件就包含了"123xxabc def"。到此爲止,臨時文件就重組結束了,它的內容和α主機上A文件的內容是徹底一致的,而後只需將此臨時文件的屬性修改一番,並重命名替換掉文件B便可,這樣就將文件B和文件A進行了同步。

整個過程以下圖:

須要注意的是,α主機不是搜索完全部數據塊以後纔將相關數據發送給β主機的,而是每搜索出一個匹配數據塊,就會當即將匹配塊的相關信息以及當前匹配塊和上一個匹配塊中間的非匹配數據發送給β主機,並開始處理下一個數據塊,當β主機每收到一段數據後會當即將將其重組到臨時文件中。所以,α主機和β主機都儘可能作到了不浪費任何資源。

1.3.1 hash值和rolling checksum重複時的匹配過程

在上面的示例分析中,沒有涉及hash值重複和rolling checksum重複的狀況,但它們有可能會重複,雖然重複後的匹配過程是同樣的,但可能不那麼容易理解。

仍是看B文件排序後的校驗碼集合。

當文件A處理數據塊時,假設處理的是第2個數據塊,它是非匹配數據塊,對此數據塊會計算rolling checksum和hash值。假設此數據塊的hash值從hash表中匹配成功,例如匹配到了上圖中"4939"這個值,因而會將此第二個數據塊的rolling checksum與hash值"4939"所指向的chunk[3]的rolling checksum做比較,hash值重複且rolling checksum重複的可能性幾乎趨近於0,因此就能肯定此數據塊是非匹配數據塊。

考慮一種極端狀況,假如文件B比較大,劃分的數據塊數量也比較多,那麼B文件自身包含的數據塊的rolling checksum就有可能會出現重複事件,且hash值也可能會出現重複事件。

例如chunk[0]和chunk[3]的rolling checksum不一樣,但根據rolling checksum計算的hash值卻相同,此時hash表和校驗碼集合的對應關係大體以下:

若是文件A中正好有數據塊的hash值能匹配到"c827",因而準備比較rolling checksum,此時將從hash值"c827"指向的chunk[0]向下掃描校驗碼集合。當掃描過程當中發現數據塊的rolling checksum正好能匹配到某個rolling checksum,如chunk[0]或chunk[3]對應的rolling checksum,則掃描終止,並進入第三層次的搜索匹配。若是向下掃描的過程當中發現直到chunk[2]都沒有找到能匹配的rolling checksum,則掃描終止,由於chunk[2]的hash值和數據塊的hash值已經不一樣,最終肯定該數據塊是非匹配數據塊,因而α主機繼續向前處理下一個數據塊。

假如文件B中數據塊的rolling checksum出現了重複(這隻說明一件事,你太幸運),將只能經過強校驗碼來匹配。

1.4 rsync工做流程分析

上面已經把rsync增量傳輸的核心分析過了,下面將分析rsync對增量傳輸算法的實現方式以及rsync傳輸的整個過程。在這以前,有必要先解釋下rsync傳輸過程當中涉及的client/server、sender、receiver、generator等相關概念。

1.4.1 幾個進程和術語

rsync有3種工做方式。一是本地傳輸方式,二是使用遠程shell鏈接方式,三是使用網絡套接字鏈接rsync daemon模式。

使用遠程shell如ssh鏈接方式時,本地敲下rsync命令後,將請求和遠程主機創建遠程shell鏈接如ssh鏈接,鏈接創建成功後,在遠程主機上將fork遠程shell進程調用遠程rsync程序,並將rsync所需的選項經過遠程shell命令如ssh傳遞給遠程rsync。這樣兩端就都啓動了rsync,以後它們將經過管道的方式(即便它們之間是本地和遠程的關係)進行通訊。

使用網絡套接字鏈接rsync daemon時,當經過網絡套接字和遠程已運行好的rsync創建鏈接時,rsync daemon進程會建立一個子進程來響應該鏈接並負責後續該鏈接的全部通訊。這樣兩端也都啓動了鏈接所需的rsync,此後通訊方式是經過網絡套接字來完成的。

本地傳輸實際上是一種特殊的工做方式,首先rsync命令執行時,會有一個rsync進程,而後根據此進程fork另外一個rsync進程做爲鏈接的對端,鏈接創建以後,後續全部的通訊將採用管道的方式。

不管使用何種鏈接方式,發起鏈接的一端被稱爲client,也即執行rsync命令的一段,鏈接的另外一端稱爲server端。注意,server端不表明rsync daemon端。server端在rsync中是一個通用術語,是相對client端而言的,只要不是client端,都屬於server端,它能夠是本地端,也能夠是遠程shell的對端,還能夠是遠程rsync daemon端,這和大多數daemon類服務的server端不一樣。

rsync的client和server端的概念存活週期很短,當client端和server端都啓動好rsync進程並創建好了rsync鏈接(管道、網絡套接字)後,將使用sender端和receiver端來代替client端和server端的概念。sender端爲文件發送端,receiver端爲文件接收端。

當兩端的rsync鏈接創建後,sender端的rsync進程稱爲sender進程,該進程負責sender端全部的工做。receiver端的rsync進程稱爲receiver進程,負責接收sender端發送的數據,以及完成文件重組的工做。receiver端還有一個核心進程——generator進程,該進程負責在receiver端執行"--delete"動做、比較文件大小和mtime以決定文件是否跳過、對每一個文件劃分數據塊、計算校驗碼以及生成校驗碼集合,而後將校驗碼集合發送給sender端。

rsync的整個傳輸過程由這3個進程完成,它們是高度流水線化的,generator進程的輸出結果做爲sender端的輸入,sender端的輸出結果做爲recevier端的輸入。即:

generator進程-->sender進程-->receiver進程

雖然這3個進程是流水線式的,但不意味着它們存在數據等待的延遲,它們是徹底獨立、並行工做的。generator計算出一個文件的校驗碼集合發送給sender,會當即計算下一個文件的校驗碼集合,而sender進程一收到generator的校驗碼集合會當即開始處理該文件,處理文件時每遇到一個匹配塊都會當即將這部分相關數據發送給receiver進程,而後當即處理下一個數據塊,而receiver進程收到sender發送的數據後,會當即開始重組工做。也就是說,只要進程被建立了,這3個進程之間是不會互相等待的。

另外,流水線方式也不意味着進程之間不會通訊,只是說rsync傳輸過程的主要工做流程是流水線式的。例如receiver進程收到文件列表後就將文件列表交給generator進程。

1.4.2 rsync整個工做流程

假設在α主機上執行rsync命令,將一大堆文件推送到β主機上。

1.首先client和server創建rsync通訊的鏈接,遠程shell鏈接方式創建的是管道,鏈接rsync daemon時創建的是網絡套接字。

2.rsync鏈接創建後,sender端的sender進程根據rsync命令行中給出的源文件收集待同步文件,將這些文件放入文件列表(file list)中並傳輸給β主機。在建立文件列表的過程當中,有幾點須要說明:

(1).建立文件列表時,會先按照目錄進行排序,而後對排序後的文件列表中的文件進行編號,之後將直接使用文件編號來引用文件。

(2).文件列表中還包含文件的一些屬性信息,包括:權限mode,文件大小len,全部者和所屬組uid/gid,最近修改時間mtime等,固然,有些信息是須要指定選項後才附帶的,例如不指定"-o"和"-g"選項將不包含uid/gid,指定"--checksum"公還將包含文件級的checksum值。

(3).發送文件列表時不是收集完成後一次性發送的,而是按照順序收集一個目錄就發送一個目錄,同理receiver接收時也是一個目錄一個目錄接收的,且接收到的文件列表是已經排過序的。

(4).若是rsync命令中指定了exclude或hide規則,則被這些規則篩選出的文件會在文件列表中標記爲hide(exclude規則的本質也是hide)。帶有hide標誌的文件對receiver是不可見的,因此receiver端會認爲sender端沒有這些被hide的文件。

3.receiver端從一開始接收到文件列表中的內容後,當即根據receiver進程fork出generator進程。generator進程將根據文件列表掃描本地目錄樹,若是目標路徑下文件已存在,則此文件稱爲basis file。

generator的工做整體上分爲3步:

(1).若是rsync命令中指定了"--delete"選項,則首先在β主機上執行刪除動做,刪除源路徑下沒有,但目標路徑下有的文件。

(2).而後根據file list中的文件順序,逐個與本地對應文件的文件大小和mtime作比較。若是發現本地文件的大小或mtime與file list中的相同,則表示該文件不須要傳輸,將直接跳過該文件。

(3).若是發現本地文件的大小或mtime不一樣,則表示該文件是須要傳輸的文件,generator將當即對此文件劃分數據塊並編號,並對每一個數據塊計算弱滾動校驗碼(rolling checksum)和強校驗碼,並將這些校驗碼跟隨數據塊編號組合在一塊兒造成校驗碼集合,而後將此文件的編號和校驗碼集合一塊兒發送給sender端。發送完畢後開始處理file list中的下一個文件。

須要注意,α主機上有而β主機上沒有的文件,generator會將文件列表中的此文件的校驗碼設置爲空發送給sender。若是指定了"--whole-file"選項,則generator會將file list中的全部文件的校驗碼都設置爲空,這樣將使得rsync強制採用全量傳輸功能,而再也不使用增量傳輸功能。

 

從下面的步驟4開始,這些步驟在前文分析rsync算法原理時已經給出了很是詳細的解釋,因此此處僅歸納性地描述,若有不解之處,請翻到前文查看相關內容。

4.sender進程收到generator發送的數據,會讀取文件編號和校驗碼集合。而後根據校驗碼集合中的弱滾動校驗碼(rolling checksum)計算hash值,並將hash值放入hash表中,且對校驗碼集合按照hash值進行排序,這樣校驗碼集合和hash表的順序就能徹底相同。

5.sender進程對校驗碼集合排序完成後,根據讀取到的文件編號處理本地對應的文件。處理的目的是找出能匹配的數據塊(即內容徹底相同的數據塊)以及非匹配的數據。每當找到匹配的數據塊時,都將當即發送一些匹配信息給receiver進程。當發送完文件的全部數據後,sender進程還將對此文件生成一個文件級的whole-file校驗碼給receiver。

6.receiver進程接收到sender發送的指令和數據後,當即在目標路徑下建立一個臨時文件,並按照接收到的數據和指令重組該臨時文件,目的是使該文件和α主機上的文件徹底一致。重組過程當中,能匹配的數據塊將從basis file中copy並寫入到臨時文件,非匹配的數據則是接收自sender端。

7.臨時文件重組完成後,將對此臨時文件生成文件級的校驗碼,並與sender端發送的whole-file校驗碼比較,若是能匹配成功則表示此臨時文件和源文件是徹底相同的,也就表示臨時文件重組成功,若是校驗碼匹配失敗,則表示重組過程當中可能出錯,將徹底從頭開始處理此源文件。

8.當臨時文件重組成功後,receiver進程將修改該臨時文件的屬性信息,包括權限、全部者、所屬組、mtime等。最後將此文件重命名並覆蓋掉目標路徑下已存在的文件(即basis file)。至此,文件同步完成。

1.5 根據執行過程分析rsync工做流程

爲了更直觀地感覺上文所解釋的rsync算法原理和工做流程,下面將給出兩個rsync執行過程的示例,並分析工做流程,一個是全量傳輸的示例,一個是增量傳輸的示例。

要查看rsync的執行過程,執行在rsync命令行中加上"-vvvv"選項便可。

1.5.1 全量傳輸執行過程分析

要執行的命令爲:

[root@xuexi ~]# rsync -a -vvvv /etc/cron.d /var/log/anaconda /etc/issue longshuai@172.16.10.5:/tmp

目的是將/etc/cron.d目錄、/var/log/anaconda目錄和/etc/issue文件傳輸到172.16.10.5主機上的/tmp目錄下,因爲/tmp目錄下不存在這些文件,因此整個過程是全量傳輸的過程。但其本質仍然是採用增量傳輸的算法,只不過generator發送的校驗碼集合全爲空而已。

如下是/etc/cron.d目錄和/var/log/anaconda目錄的層次結構。

如下是執行過程。

[root@xuexi ~]# rsync -a -vvvv /etc/cron.d /var/log/anaconda /etc/issue longshuai@172.16.10.5:/tmp
 
# 使用ssh(ssh爲默認的遠程shell)執行遠程rsync命令創建鏈接
cmd=<NULL> machine=172.16.10.5 user=longshuai path=/tmp
cmd[0]=ssh cmd[1]=-l cmd[2]=longshuai cmd[3]=172.16.10.5 cmd[4]=rsync cmd[5]=--server cmd[6]=-vvvvlogDtpre.iLsf cmd[7]=. cmd[8]=/tmp
opening connection using: ssh -l longshuai 172.16.10.5 rsync --server -vvvvlogDtpre.iLsf . /tmp
note: iconv_open("UTF-8", "UTF-8") succeeded.
longshuai@172.16.10.5's password:
 
# 雙方互相發送協議版本號,並協商使用二者較低版本
(Server) Protocol versions: remote=30, negotiated=30
(Client) Protocol versions: remote=30, negotiated=30
 
######### sender端生成文件列表併發送給receiver端 #############
sending incremental file list
[sender] make_file(cron.d,*,0)       # 第一個要傳輸的文件目錄:cron.d文件,注意,此處cron.d是待傳輸的文件,而不認爲是目錄
[sender] make_file(anaconda,*,0)     # 第二個要傳輸的文件目錄:anaconda文件
[sender] make_file(issue,*,0)        # 第三個要傳輸的文件目錄:issue文件
 
# 指明從文件列表的第1項開始,並肯定此次要傳輸給receiver的項共有3個
[sender] flist start=1, used=3, low=0, high=2   
# 爲這3項生成列表信息,包括此文件id,所在目錄,權限模式,長度,uid/gid,最後還有一個修飾符
[sender] i=1 /etc issue mode=0100644 len=23 uid=0 gid=0 flags=5      
[sender] i=2 /var/log anaconda/ mode=040755 len=4096 uid=0 gid=0 flas=5
[sender] i=3 /etc cron.d/ mode=040755 len=51 uid=0 gid=0 flags=5     
send_file_list done
file list sent
# 惟一須要注意的是文件所在目錄,例如/var/log anaconda/,但實際在命令行中指定的是/var/log/anaconda。
# 此處信息中log和anaconda使用空格分開了,這個空格很是關鍵。空格左邊的表示隱含目錄(見man rsync"-R"選項),
# 右邊的是待傳輸的整個文件或目錄,默認狀況下將會在receiver端生成anaconda/目錄,但左邊隱含目錄則不會建立。
# 但能夠經過指定特殊選項(如"-R"),讓rsync也能在receiver端同時建立隱含目錄,以便建立整個目錄層次結構。
# 舉個例子,若是A主機的/a目錄下有b、c等衆多子目錄,而且b目錄中有d文件,如今只想傳輸/a/b/d並保留/a/b的目錄層次結構,
# 那麼能夠經過特殊選項讓此處的文件所在目錄變爲"/ a/",關於具體的實現方法,見"rsync -R選項示例"。
 
############ sender端發送文件屬性信息 #####################
# 因爲前面的文件列表中有兩個條目是目錄,所以還要爲目錄中的每一個文件生成屬性信息併發送給receiver端
send_files starting
[sender] make_file(anaconda/anaconda.log,*,2)
[sender] make_file(anaconda/syslog,*,2)
[sender] make_file(anaconda/program.log,*,2)
[sender] make_file(anaconda/packaging.log,*,2)
[sender] make_file(anaconda/storage.log,*,2)
[sender] make_file(anaconda/ifcfg.log,*,2)
[sender] make_file(anaconda/ks-script-1uLekR.log,*,2)
[sender] make_file(anaconda/ks-script-iGpl4q.log,*,2)
[sender] make_file(anaconda/journal.log,*,2)
[sender] flist start=5, used=9, low=0, high=8
[sender] i=5 /var/log anaconda/anaconda.log mode=0100600 len=6668 uid=0 gid=0 flags=0
[sender] i=6 /var/log anaconda/ifcfg.log mode=0100600 len=3826 uid=0 gid=0 flags=0
[sender] i=7 /var/log anaconda/journal.log mode=0100600 len=1102699 uid=0 gid=0 flags=0
[sender] i=8 /var/log anaconda/ks-script-1uLekR.log mode=0100600 len=0 uid=0 gid=0 flags=0
[sender] i=9 /var/log anaconda/ks-script-iGpl4q.log mode=0100600 len=0 uid=0 gid=0 flags=0
[sender] i=10 /var/log anaconda/packaging.log mode=0100600 len=160420 uid=0 gid=0 flags=0
[sender] i=11 /var/log anaconda/program.log mode=0100600 len=27906 uid=0 gid=0 flags=0
[sender] i=12 /var/log anaconda/storage.log mode=0100600 len=78001 uid=0 gid=0 flags=0
[sender] i=13 /var/log anaconda/syslog mode=0100600 len=197961 uid=0 gid=0 flags=0
[sender] make_file(cron.d/0hourly,*,2)
[sender] make_file(cron.d/sysstat,*,2)
[sender] make_file(cron.d/raid-check,*,2)
[sender] flist start=15, used=3, low=0, high=2
[sender] i=15 /etc cron.d/0hourly mode=0100644 len=128 uid=0 gid=0 flags=0
[sender] i=16 /etc cron.d/raid-check mode=0100644 len=108 uid=0 gid=0 flags=0
[sender] i=17 /etc cron.d/sysstat mode=0100600 len=235 uid=0 gid=0 flags=0
# 從上述結果中發現,沒有i=4和i=14的文件信息,由於它們是目錄anaconda和cron.d的文件信息
# 還發現沒有發送/etc/issue文件的信息,由於issue自身是普通文件而非目錄,所以在發送目錄前就發送了
############# 文件列表全部內容發送完畢 ####################
 
############### server端相關活動內容 ################
# 首先在server端啓動rsync進程
server_recv(2) starting pid=13309
# 接收client第一次傳輸的數據,這次傳輸server端收到3條數據,它們是傳輸中根目錄下的文件或目錄
received 3 names
[receiver] flist start=1, used=3, low=0, high=2
[receiver] i=1 1 issue mode=0100644 len=23 gid=(0) flags=400
[receiver] i=2 1 anaconda/ mode=040755 len=4096 gid=(0) flags=405
[receiver] i=3 1 cron.d/ mode=040755 len=51 gid=(0) flags=405
recv_file_list done
# 第一次接收數據完成
############ 在receiver端啓動generator進程 ########
get_local_name count=3 /tmp   # 獲取本地路徑名
generator starting pid=13309  # 啓動generator進程
delta-transmission enabled    # 啓用增量傳輸算法
############ generator進程設置完畢 ################
 
############# 首先處理接收到的普通文件 ##############
recv_generator(issue,1)       # generator收到receiver進程通知的文件id=1的文件issue
send_files(1, /etc/issue)
count=0 n=0 rem=0             # 此項爲目標主機上的文件issue分割的數據塊信息,count表示數量,n表示數據塊的固定大小,
                              # rem是remain的意思,表示剩餘的數據長度,也即最後一個數據塊的大小,
                              # 此處由於目標端不存在issue文件,所以所有設置爲0
send_files mapped /etc/issue of size 23  # sender端映射/etc/issue,使得sender能夠獲取到該文件的相關內容
calling match_sums /etc/issue            # sender端調用校驗碼匹配功能
issue
sending file_sum                         # 匹配結束後,再發送文件級的checksum給receiver端
false_alarms=0 hash_hits=0 matches=0     # 輸出數據塊匹配的相關統計信息
sender finished /etc/issue
# 文件/etc/issue發送完畢,由於目標上不存在issue文件,因此整個過程很是簡單,直接傳輸issue中的所有數據便可
 
############## 開始處理目錄格式的文件列表 #############
# 首先接收到兩個id=2和id=3的文件
recv_generator(anaconda,2)
recv_generator(cron.d,3)
# 而後開始從文件列表的目錄中獲取其中的文件信息
recv_files(3) starting
# 先獲取的是dir 0的目錄中的文件信息
[receiver] receiving flist for dir 0
[generator] receiving flist for dir 0
received 9 names                                    # 表示從該目錄中收到了9條文件信息
[generator] flist start=5, used=9, low=0, high=8    # 文件的id號從5開始,總共有9個條目
[generator] i=5 2 anaconda/anaconda.log mode=0100600 len=6668 gid=(0) flags=400
[generator] i=6 2 anaconda/ifcfg.log mode=0100600 len=3826 gid=(0) flags=400
[generator] i=7 2 anaconda/journal.log mode=0100600 len=1102699 gid=(0) flags=400
[generator] i=8 2 anaconda/ks-script-1uLekR.log mode=0100600 len=0 gid=(0) flags=400
[generator] i=9 2 anaconda/ks-script-iGpl4q.log mode=0100600 len=0 gid=(0) flags=400
[generator] i=10 2 anaconda/packaging.log mode=0100600 len=160420 gid=(0) flags=400
[generator] i=11 2 anaconda/program.log mode=0100600 len=27906 gid=(0) flags=400
[generator] i=12 2 anaconda/storage.log mode=0100600 len=78001 gid=(0) flags=400
[generator] i=13 2 anaconda/syslog mode=0100600 len=197961 gid=(0) flags=400
recv_file_list done                                 # dir 0目錄中的文件信息接收完畢
[receiver] receiving flist for dir 1                # 而後獲取的是dir 1的目錄中的文件信息
[generator] receiving flist for dir 1
received 3 names
[generator] flist start=15, used=3, low=0, high=2
[generator] i=15 2 cron.d/0hourly mode=0100644 len=128 gid=(0) flags=400
[generator] i=16 2 cron.d/raid-check mode=0100644 len=108 gid=(0) flags=400
[generator] i=17 2 cron.d/sysstat mode=0100600 len=235 gid=(0) flags=400
recv_file_list done                                 # dir 1目錄中的文件信息接收完畢
 
################# 開始傳輸目錄dir 0及其內文件 #############
recv_generator(anaconda,4)             # generator接收目錄anaconda信息,它的id=4,是否還記得上文sender未發送過id=4和
                                       # id=14的目錄信息?只有先接收該目錄,才能繼續接收該目錄中的文件
send_files(4, /var/log/anaconda)       # 由於anaconda是要在receiver端建立的目錄,因此sender端先發送該目錄文件
anaconda/                              # anaconda目錄發送成功
set modtime of anaconda to (1494476557) Thu May 11 12:22:37 2017   # 而後再設置目錄anaconda的mtime(即modify time)
 
# receiver端的anaconda目錄已經創建,如今開始傳輸anaconda中的文件
# 如下的第一個anaconda目錄中的第一個文件處理過程
recv_generator(anaconda/anaconda.log,5)          # generator進程接收id=5的anaconda/anaconda.log文件    
send_files(5, /var/log/anaconda/anaconda.log)   
count=0 n=0 rem=0                                # 計算該文件數據塊相關信息
send_files mapped /var/log/anaconda/anaconda.log of size 6668    # sender端映射anaconda.log文件
calling match_sums /var/log/anaconda/anaconda.log                # 調用校驗碼匹配功能
anaconda/anaconda.log
sending file_sum                                 # 數據塊匹配結束後,再發送文件級別的checksum給receiver端
false_alarms=0 hash_hits=0 matches=0             # 輸出匹配過程當中的統計信息
sender finished /var/log/anaconda/anaconda.log   # anaconda.log文件傳輸完成
 
recv_generator(anaconda/ifcfg.log,6)             # 開始處理anaconda中的第二個文件
send_files(6, /var/log/anaconda/ifcfg.log)
count=0 n=0 rem=0
send_files mapped /var/log/anaconda/ifcfg.log of size 3826
calling match_sums /var/log/anaconda/ifcfg.log
anaconda/ifcfg.log
sending file_sum
false_alarms=0 hash_hits=0 matches=0
sender finished /var/log/anaconda/ifcfg.log      # 第二個文件傳輸完畢
 
recv_generator(anaconda/journal.log,7)           # 開始處理anaconda中的第三個文件
send_files(7, /var/log/anaconda/journal.log)
count=0 n=0 rem=0
send_files mapped /var/log/anaconda/journal.log of size 1102699
calling match_sums /var/log/anaconda/journal.log
anaconda/journal.log
sending file_sum
false_alarms=0 hash_hits=0 matches=0
sender finished /var/log/anaconda/journal.log    # 第二個文件傳輸完畢
 
#如下相似過程省略
......
recv_generator(anaconda/syslog,13)               # 開始處理anaconda中的最後一個文件
send_files(13, /var/log/anaconda/syslog)
count=0 n=0 rem=0
send_files mapped /var/log/anaconda/syslog of size 197961
calling match_sums /var/log/anaconda/syslog
anaconda/syslog
sending file_sum
false_alarms=0 hash_hits=0 matches=0
sender finished /var/log/anaconda/syslog        # anaconda目錄中全部文件傳輸完畢
 
################# 開始傳輸目錄dir 1及其內文件 #############
recv_generator(cron.d,14)
send_files(14, /etc/cron.d)
cron.d/
set modtime of cron.d to (1494476430) Thu May 11 12:20:30 2017
recv_generator(cron.d/0hourly,15)
send_files(15, /etc/cron.d/0hourly)
count=0 n=0 rem=0
send_files mapped /etc/cron.d/0hourly of size 128
calling match_sums /etc/cron.d/0hourly
cron.d/0hourly
sending file_sum
false_alarms=0 hash_hits=0 matches=0
sender finished /etc/cron.d/0hourly
......相似過程省略......
recv_generator(cron.d/sysstat,17)
send_files(17, /etc/cron.d/sysstat)
count=0 n=0 rem=0
send_files mapped /etc/cron.d/sysstat of size 235
calling match_sums /etc/cron.d/sysstat
cron.d/sysstat
sending file_sum
false_alarms=0 hash_hits=0 matches=0
sender finished /etc/cron.d/sysstat
 
############## 如下是receiver端文件重組相關過程 ################
generate_files phase=1     # generator進程進入第一階段
 
# 重組第一個文件issue
recv_files(issue)        
data recv 23 at 0   # data recv關鍵字表示從sender端獲取的純文件數據,23表示接收到的這一段純數據大小爲23字節,
                    # at 0表示接收的這段數據放在臨時文件的起始偏移0處。
got file_sum        # 獲取到sender端最後發送的文件級的checksum,並進行檢查,檢查經過則表示重組正式完成
set modtime of .issue.RpT9T9 to (1449655155) Wed Dec  9 17:59:15 2015  # 臨時文件重組完成後,設置臨時文件的mtime
renaming .issue.RpT9T9 to issue        # 最後將臨時文件重命名爲目標文件
# 至此,第一個文件真正完成同步
 
# 重組第二個文件列表anaconda及其內文件
recv_files(anaconda)  # 重組目錄anaconda
 
recv_files(anaconda/anaconda.log)   # 重組目錄anaconda中的第一個文件
data recv 6668 at 0
got file_sum
set modtime of anaconda/.anaconda.log.LAR2t1 to (1494476557) Thu May 11 12:22:37 2017
renaming anaconda/.anaconda.log.LAR2t1 to anaconda/anaconda.log     # anaconda目錄中的第一個文件同步完成
 
recv_files(anaconda/ifcfg.log)               # 重組目錄anaconda中的第二個文件
data recv 3826 at 0
got file_sum
set modtime of anaconda/.ifcfg.log.bZDW3S to (1494476557) Thu May 11 12:22:37 2017
renaming anaconda/.ifcfg.log.bZDW3S to anaconda/ifcfg.log     # anaconda目錄中的第二個文件同步完成
 
recv_files(anaconda/journal.log)     # 重組目錄anaconda中的第三個文件
data recv 32768 at 0                 # 因爲每次傳輸的數據量最大爲32kB,所以對於較大的journal.log分紅了屢次進行傳輸
data recv 32768 at 32768
data recv 32768 at 65536
..............
got file_sum
set modtime of anaconda/.journal.log.ylpZDK to (1494476557) Thu May 11 12:22:37 2017
renaming anaconda/.journal.log.ylpZDK to anaconda/journal.log       # anaconda目錄中的第三個文件同步完成
.........中間相似過程省略...........
recv_files(anaconda/syslog)
data recv 32768 at 0
data recv 32768 at 32768
data recv 32768 at 65536
................
got file_sum
set modtime of anaconda/.syslog.zwQynW to (1494476557) Thu May 11 12:22:37 2017
renaming anaconda/.syslog.zwQynW to anaconda/syslog   
# 至此,anaconda及其內全部文件都同步完畢
 
# 重組第三個文件列表cron.d及其內文件
recv_files(cron.d)
recv_files(cron.d/0hourly)
......中間相似過程省略..........
recv_files(cron.d/sysstat)
data recv 235 at 0
got file_sum
set modtime of cron.d/.sysstat.m4hzgx to (1425620722) Fri Mar  6 13:45:22 2015
renaming cron.d/.sysstat.m4hzgx to cron.d/sysstat
# 至此,cron.d及其內全部文件都同步完畢
 
send_files phase=1        
touch_up_dirs: cron.d (1)  # sender進程修改上層目錄cron.d的各類時間戳
set modtime of cron.d to (1494476430) Thu May 11 12:20:30 2017   # 設置cron.d目錄的mtime
recv_files phase=1        
generate_files phase=2    
send_files phase=2        
send files finished        # sender進程消逝,並輸出匹配的統計信息以及傳輸的總的純數據量
total: matches=0  hash_hits=0  false_alarms=0 data=1577975
recv_files phase=2
generate_files phase=3
recv_files finished
generate_files finished
client_run waiting on 13088 
 
sent 1579034 bytes  received 267 bytes  242969.38 bytes/sec    # 總共發送了1579034字節的數據,此項統計包括了純文件數據以
                                                               # 及各類非文件數據,接收到了來自receiver端的267字節的數據
total size is 1577975  speedup is 1.00     # sender端全部文件總大小爲1577975字節,由於receiver端徹底沒有basis file,
                                           # 因此總大小等於傳輸的純數據量
[sender] _exit_cleanup(code=0, file=main.c, line=1052): entered
[sender] _exit_cleanup(code=0, file=main.c, line=1052): about to call exit(0)

1.5.2 增量傳輸執行過程分析

要執行的命令爲:

[root@xuexi ~]# rsync -vvvv /tmp/init 172.16.10.5:/tmp

目的是將/etc/init文件傳輸到172.16.10.5主機上的/tmp目錄下,因爲/tmp目錄下已經存在該文件,因此整個過程是增量傳輸的過程。

如下是執行過程。

[root@xuexi ~]# rsync -vvvv /tmp/init 172.16.10.5:/tmp
 
# 使用ssh(ssh爲默認的遠程shell)執行遠程rsync命令創建鏈接
cmd=<NULL> machine=172.16.10.5 user=<NULL> path=/tmp
cmd[0]=ssh cmd[1]=172.16.10.5 cmd[2]=rsync cmd[3]=--server cmd[4]=-vvvve.Lsf cmd[5]=. cmd[6]=/tmp
opening connection using: ssh 172.16.10.5 rsync --server -vvvve.Lsf . /tmp
note: iconv_open("UTF-8", "UTF-8") succeeded.
root@172.16.10.5's password:
 
# 雙方互相發送協議版本號,並協商使用二者較低版本
(Server) Protocol versions: remote=30, negotiated=30
(Client) Protocol versions: remote=30, negotiated=30
[sender] make_file(init,*,0)
[sender] flist start=0, used=1, low=0, high=0
[sender] i=0 /tmp init mode=0100644 len=8640 flags=0
send_file_list done
file list sent                       
 
send_files starting  
server_recv(2) starting pid=13689    # 在遠程啓動receiver進程
received 1 names
[receiver] flist start=0, used=1, low=0, high=0
[receiver] i=0 1 init mode=0100644 len=8640 flags=0
recv_file_list done
get_local_name count=1 /tmp
generator starting pid=13689         # 在遠程啓動generator進程
delta-transmission enabled
recv_generator(init,0)
recv_files(1) starting  
gen mapped init of size 5140         # generator進程映射basis file文件(即本地的init文件),只有映射後各進程才能獲取該文件相關數據塊
generating and sending sums for 0    # 生成init文件的弱滾動校驗碼和強校驗碼集合,併發送給sender端
send_files(0, /tmp/init)             # 如下generator生成的校驗碼集合信息
count=8 rem=240 blength=700 s2length=2 flength=5140 
count=8 n=700 rem=240                # count=8表示該文件總共計算了8個數據塊的校驗碼,n=700表示固定數據塊的大小爲700字節,
                                     # rem=240(remain)表示最終剩240字節,即最後一個數據塊的長度
chunk[0] offset=0 len=700 sum1=3ef2e827
chunk[0] len=700 offset=0 sum1=3ef2e827
chunk[1] offset=700 len=700 sum1=57aceaaf
chunk[1] len=700 offset=700 sum1=57aceaaf
chunk[2] offset=1400 len=700 sum1=92d7edb4
chunk[2] len=700 offset=1400 sum1=92d7edb4
chunk[3] offset=2100 len=700 sum1=afe7e939
chunk[3] len=700 offset=2100 sum1=afe7e939
chunk[4] offset=2800 len=700 sum1=fcd0e7d5
chunk[4] len=700 offset=2800 sum1=fcd0e7d5
chunk[5] offset=3500 len=700 sum1=0eaee949
chunk[5] len=700 offset=3500 sum1=0eaee949
chunk[6] offset=4200 len=700 sum1=ff18e40f
chunk[6] len=700 offset=4200 sum1=ff18e40f
chunk[7] offset=4900 len=240 sum1=858d519d
chunk[7] len=240 offset=4900 sum1=858d519d
 
# sender收到校驗碼集合後,準備開始數據塊匹配過程
send_files mapped /tmp/init of size 8640 # sender進程映射本地的/tmp/init文件,只有映射後各進程才能獲取該文件相關數據塊
calling match_sums /tmp/init             # 開始調用校驗碼匹配功能,對/tmp/init文件進行搜索匹配
init
built hash table                         # sender端根據接收到的校驗碼集合中的滾動校驗碼生成16位長度的hash值,並將hash值放入hash表
hash search b=700 len=8640               # 第一層hash搜索,搜索的數據塊大小爲700字節,總搜索長度爲8640,即整個/tmp/init的大小
sum=3ef2e827 k=700
hash search s->blength=700 len=8640 count=8
potential match at 0 i=0 sum=3ef2e827           # 在chunk[0]上發現潛在的匹配塊,其中i表示的是sender端匹配塊的編號
match at 0 last_match=0 j=0 len=700 n=0         # 最終肯定起始偏移0上的數據塊能徹底匹配上,j表示的是校驗碼集合中的chunk編號。
                                                # 此過程當中可能還進行了rolling checksum以及強校驗碼的匹配
potential match at 700 i=1 sum=57aceaaf
match at 700 last_match=700 j=1 len=700 n=0     # last_match的值是上一次匹配塊的終止偏移
potential match at 1400 i=2 sum=92d7edb4
match at 1400 last_match=1400 j=2 len=700 n=0
potential match at 2100 i=3 sum=afe7e939
match at 2100 last_match=2100 j=3 len=700 n=0
potential match at 7509 i=6 sum=ff18e40f         # 在chunk[6]上發現潛在的匹配塊,
match at 7509 last_match=2800 j=6 len=700 n=4709 # 這次匹配塊的起始偏移地址是7509,而上一次匹配塊的結尾偏移是2800,
                                                 # 中間4709字節的數據都是未匹配上的,這些數據須要以純數據的方式發送給receiver端
done hash search      # 匹配結束
sending file_sum      # sender端匹配結束後,再生成文件級別的checksum,併發送給receiver端
false_alarms=0 hash_hits=5 matches=5    # 輸出這次匹配過程當中的統計信息,總共有5個匹配塊,全都是hash匹配出來的,
                                        # 沒有進行第二層次的rolling checksum檢查
sender finished /tmp/init     # sender端完成搜索和匹配過程
send_files phase=1            # sender進程進入第一階段
# sender進程暫時告一段落
# 進入receiver端進行操做
generate_files phase=1        # generator進程進入第一階段
recv_files(init)              # receiver進程讀取本地init文件
recv mapped init of size 5140                # receiver進程映射init文件,即basis file
##################### 如下是文件重組過程 #####################
chunk[0] of size 700 at 0 offset=0           # receiver進程從basis file中拷貝chunk[0]對應的數據塊到臨時文件中
chunk[1] of size 700 at 700 offset=700       # receiver進程從basis file中拷貝chunk[1]對應的數據塊到臨時文件中
chunk[2] of size 700 at 1400 offset=1400
chunk[3] of size 700 at 2100 offset=2100
data recv 4709 at 2800                       # receiver進程從2800偏移處開始接收sender發送的純數據到臨時文件中,共4709字節
chunk[6] of size 700 at 4200 offset=7509     # receiver進程從basis file起始偏移4200處拷貝chunk[6]對應的數據塊到臨時文件中
data recv 431 at 8209                        # receiver進程從8209偏移處開始接收sender發送的純數據到臨時文件中,共431字節
got file_sum                      # 獲取文件級的checksum,並與sender進程發送的文件級checksum做比較
renaming .init.gd5hvw to init     # 重命名重組成功的臨時文件爲目標文件init
###################### 文件重組完成 ###########################
recv_files phase=1               
generate_files phase=2          
send_files phase=2               
send files finished               # sender進程結束,並在sender端輸出報告:搜索過程當中發現5個匹配塊,且都是由16位的hash值匹配出來的,
                                  # 第二層弱檢驗碼檢查次數爲0,也就是說沒有hash值衝突的小几率事件發生。總共傳輸的純數據爲5140字節
total: matches=5  hash_hits=5  false_alarms=0 data=5140
recv_files phase=2               
generate_files phase=3         
recv_files finished               # receiver進程結束
generate_files finished           # generator進程結束
client_run waiting on 13584 
 
sent 5232 bytes  received 79 bytes  2124.40 bytes/sec  # sender端總共發送5232字節,其中包括純數據5140字節和非文件數據,接收到79字節
total size is 8640  speedup is 1.63
[sender] _exit_cleanup(code=0, file=main.c, line=1052): entered
[sender] _exit_cleanup(code=0, file=main.c, line=1052): about to call exit(0)

1.6 從工做原理分析rsync的適用場景

(1).rsync兩端耗費計算機的什麼資源比較嚴重?

從前文中已經知道,rsync的sender端由於要屢次計算、屢次比較各類校驗碼而對cpu的消耗很高,receiver端由於要從basis file中複製數據而對io的消耗很高。但這只是rsync增量傳輸時的狀況,若是是全量傳輸(如第一次同步,或顯式使用了全量傳輸選項"--whole-file"),那麼sender端不用計算、比較校驗碼,receiver端不用copy basis file,這和scp消耗的資源是同樣的。

(2).rsync不適合對數據庫文件進行實時同步。

像數據庫文件這樣的大文件,且是頻繁訪問的文件,若是使用rsync實時同步,sender端要計算、比較的數據塊校驗碼很是多,cpu會長期居高不下,從而影響數據庫提供服務的性能。另外一方面,receiver端每次都要從巨大的basis file(通常提供服務的數據庫文件至少都幾十G)中複製大部分相同的數據塊重組新文件,這幾乎至關於直接cp了一個文件,它必定沒法扛住巨大的io壓力,再好的機器也扛不住。

因此,對頻繁改變的單個大文件只適合用rsync偶爾同步一次,也就是備份的功能,它不適合實時同步。像數據庫文件,要實時同步應該使用數據庫自帶的replication功能。

(3).可使用rsync對大量小文件進行實時同步。

因爲rsync是增量同步,因此對於receiver端已經存在的和sender端相同的文件,sender端是不會發送的,這樣就使得sender端和receiver端都只須要處理少許的文件,因爲文件小,因此不管是sender端的cpu仍是receiver端的io都不是問題。

可是,rsync的實時同步功能是藉助工具來實現的,如inotify+rsync,sersync,因此這些工具要設置合理,不然實時同步同樣效率低下,不過這不是rsync致使的效率低,而是這些工具配置的問題。

相關文章
相關標籤/搜索