天天看點

FastDFS合并存儲政策

FastDFS提供了合并存儲功能的實作,所有的配置都在tracker.conf檔案之中,具體摘錄如下:

trunk功能啟動與配置:通過tracker.conf檔案啟動與配置,個配置項如下:

use_trunk_file = false#是否啟用trunk存儲

slot_min_size = 256#trunk檔案最小配置設定單元

slot_max_size = 16MB#trunk内部存儲的最大檔案,超過該值會被獨立存儲

trunk_file_size = 64MB#trunk檔案大小

trunk_create_file_advance = false#是否預先建立trunk檔案

trunk_create_file_time_base = 02:00#預先建立trunk檔案的基準時間

trunk_create_file_interval = 86400#預先建立trunk檔案的時間間隔

trunk_create_file_space_threshold = 20G#trunk建立檔案的最大空閑空間

trunk_init_check_occupying = false#啟動時是否檢查每個空閑空間清單項已經被使用

trunk_init_reload_from_binlog = false#是否純粹從trunk-binlog重建空閑空間清單

trunk_compress_binlog_min_interval = 0#對trunk-binlog進行壓縮的時間間隔

合并存儲檔案命名與檔案結構

    我們知道向FastDFS上傳檔案成功時,伺服器傳回該檔案的存取ID叫做file_id,當沒有啟動合并存儲時該file_id和磁盤上實際存儲的檔案一一對應,當采用合并存儲時就不再一一對應而是多個file_id對應的檔案被存儲成一個大檔案。

注:下面将采用合并存儲後的大檔案統稱為Trunk檔案,沒有合并存儲的檔案統稱為源檔案;

區分三個概念:

    1)Trunk檔案:storage伺服器磁盤上存儲的實際檔案,預設大小為64MB

    2)合并存儲檔案的file_id:表示伺服器啟用合并存儲後,每次上傳傳回給用戶端的file_id,注意此時該file_id與磁盤上的檔案沒有一一對應關系;

    3)沒有合并存儲的file_id:表示伺服器未啟用合并存儲時,Upload時傳回的file_id

    Trunk檔案檔案名格式:fdfs_storage1/data/00/00/000001 檔案名從1開始遞增,類型為int;

1、在啟動合并存儲時服務傳回給用戶端的file_id也會有所變化

具體如下:

1) 沒有合并存儲時file_id:group1/M00/00/00/CmQPRlP0T4-AA9_ECDsoXi21HR0.tar.gzCmQPRlP0T4-AA9_ECDsoXi21HR0.tar.gz

這個檔案名中,除了.tar.gz 為檔案字尾,CmQPRlP0T4-AA9_ECDsoXi21HR0 這部分是一個base64編碼緩沖區,組成如下:

storage_id(ip的數值型)

timestamp(建立時間)

file_size(若原始值為32位則前面加入一個随機值填充,最終為64位)

crc32(檔案内容的檢驗碼)

2)合并存儲時file_id:group1/M00/00/00/CgAEbFQWWbyIPCu1AAAFr1bq36EAAAAAQAAAAAAAAXH82.conf

采用合并的檔案ID更長,因為其中需要加入儲存的大檔案id以及偏移量,具體包括了如下資訊:

file_size:占用大檔案的空間(注意按照最小slot-256位元組進行對齊)

mtime:檔案修改時間

crc32:檔案内容的crc32碼

formatted_ext_name:檔案擴充名

alloc_size:檔案大小與size相等

id:大檔案ID如000001

offset:檔案内容在trunk檔案中的偏移量

size:檔案大小

2、Trunk檔案内部結構

    trunk内部是由多個小檔案組成,每個小檔案都會有一個trunkHeader(可認為是中繼資料),以及緊跟在其後的真實資料,結構如下:

|||——————————————————— 24bytes——————-—————————|||

|—1byte   —|—4bytes    —|—4bytes —|—4bytes—|—4bytes —|—7bytes                      —|

|—filetype—|—alloc_size—|—filesize—|—crc32  —|—mtime —|—formatted_ext_name—|

|||——————file_data filesize bytes——————————————————————|||

|———————file_data————————————————————————————|

    每個Trunk-Header從上圖可以看到,占用了72位元組。

合并存儲空閑空間管理

1、概述

    Trunk檔案為64MB(預設),是以每次建立一次Trunk檔案總是會産生空餘空間,比如為存儲一個10MB檔案,建立一個Trunk檔案,那麼就會剩下接近54MB的空間(TrunkHeader 會24位元組,後面為了友善叙述暫時忽略其所占空間),下次要想再次存儲10MB檔案時就不需要建立新的檔案,存儲在已經建立的Trunk檔案中即可。另外當删除一個存儲的檔案時,也會産生空餘空間。

    在Storage内部會為每個store_path構造一顆以空閑塊大小作為關鍵字的空閑平衡樹,相同大小的空閑塊儲存在連結清單之中。每當需要存儲一個檔案時會首先到空閑平衡樹中查找大于并且最接近的空閑塊,然後試着從該空閑塊中分割出多餘的部分作為一個新的空閑塊,加入到空閑平衡樹中。例如:要求存儲檔案為300KB,通過空閑平衡樹找到一個350KB的空閑塊,那麼就會将350KB的空閑塊分裂成兩塊,前面300KB傳回用于存儲,後面50KB則繼續放置到空閑平衡樹之中。

    假若此時找不到可滿足的空閑塊,那麼就會建立一個新的trunk檔案64MB,将其加入到空閑平衡樹之中,再次執行上面的查找操作(此時總是能夠滿足了)。

2、TrunkServer

    假若所有的Storage都具有配置設定空閑空間的能力(upload檔案時自主決定存儲到哪個TrunkFile之中),那麼可能會由于同步延遲導緻資料沖突,例如:Storage-A:Upload一個檔案A.txt 100KB,将其儲存到000001這個TrunkFile的開頭,與此同時,Storage-B也接受Upload一個檔案B.txt 200KB,将其儲存在000001這個TrunkFile檔案的開頭,當Storage-B收到Storage-A的同步資訊時,他無法将A.txt 儲存在000001這個trunk檔案的開頭,是以這個位置已經被B.txt占用。

   為了處理這種沖突,引入了TrunkServer概念,隻有TrunkServer才有權限配置設定空閑空間,決定檔案應該儲存到哪個TrunkFile的什麼位置。TrunkServer由Tracker指定,并且在心跳資訊中通知所有的Storage。

    引入TrunkServer之後,一次Upload請求,Storage的處理流程圖如下: 

FastDFS合并存儲政策

3、TrunkFile同步

    開啟了合并存儲服務後,除了原本的源檔案同步之外,TrunkServer還多了TrunkBinlog的同步(非TrunkServer沒有TrunkBinlog同步)。源檔案的同步與沒有開啟合并存儲時過程完全一樣,都是從binlog觸發同步檔案。

    TrunkBinlog記錄了TrunkServer所有配置設定與回收空閑塊的操作,由TrunkServer同步給同組中的其他Storage。TrunkServer為同組中的其他Storage各建立一個同步線程,每秒将TrunkBinlog的變化同步出去。同組的Storage接收到TrunkBinlog隻是儲存到檔案中,不做其他任何操作。

TrunkBinlog檔案檔案記錄如下:

1410750754 A 0 0 0 1 0 67108864

1410750754 D 0 0 0 1 0 67108864

各字段含義如下:

時間戳

操作類型(A:增加,D:删除)

store_path_index

sub_path_high

sub_path_low

file.id(TrunkFile檔案名,比如000001)

offset(在TrunkFile檔案中的偏移量)

size(占用的大小,按照slot對齊)

4、空閑平衡樹重建

    當作為TrunkServer的Storage啟動時可以從TrunkBinlog檔案中中加載所有的空閑塊配置設定與加入操作,這個過程就可以實作空閑平衡書的重建。

    當長期運作時,随着空閑塊的不斷删除添加會導緻TrunkBinlog檔案很大,那麼加載時間會很長,FastDFS引入了檢查點檔案storage_trunk.dat,每次TrunkServer程序退出時會将目前記憶體裡的空閑平衡樹導出為storage_trunk.dat檔案,該檔案的第一行為TrunkBinlog的offset,也就是該檢查點檔案負責到這個offset為止的TrunkBinlog。也就是說下次TrunkServer啟動的時候,先加載storage_trunk.dat檔案,然後繼續加載這個offset之後的TrunkBinlog檔案内容。   

下面為TrunkServer初始化的流程圖:

FastDFS合并存儲政策

5、TrunkBinlog壓縮

    上文提到的storage_trunk.dat既是檢查點檔案,其實也是一個壓縮檔案,因為從記憶體中将整個空閑平衡樹直接導出,沒有了中間步驟,是以檔案就很小。這種方式雖然實作了TrunkServer自身重新開機時快速加載空閑平衡樹的目的,但是并沒有實際上縮小TrunkBinlog檔案的大小。假如這台TrunkServer當機後,Tracker會選擇另外一台機器作為新的TrunkServer,這台新的TrunkServer就必須從很龐大的TrunkBinlog中加載空閑平衡樹,由于TrunkBinlog檔案很大,這将是一個很漫長的過程。

    為了減少TrunkBinlog,可以選擇壓縮檔案,在TrunkServer初始化完成後,或者退出時,可以将storage_trunk.dat與其負責偏移量之後的TrunkBinlog進行合并,産生一個新的TrunkBinlog。由于此時的TrunkBinlog已經從頭到尾整個修改了,就需要将該檔案完成的同步給同組内的其他Storage,為了達到該目的,FastDFS使用了如下方法:

      1)TrunkServer将TrunkBinlog同步給組内其他Storage時會将同步的最後狀态記錄到一個mark檔案之中,比如同步給A,則記錄到A.mark檔案(其中包括最後同步成功的TrunkBinlog偏移量)。

      2)TrunkServer在将storage_trunk.dat與TrunkBinlog合并之後,就将本地記錄TrunkBinlog最後同步狀态的所有mark檔案删除,如一組有A、B、C,其中A為TrunkServer,則A此時删除B.mark、C.mark。

      3)當下次TrunkServer要同步TrunkBinlog到B、C時,發現找不到B.mark、C.mark檔案,就會自動從頭轉換成從頭開始同步檔案。

      4)當TrunkServer判斷需要從頭開始同步TrunkBinlog,由于擔心B、C已經有舊的檔案,是以就需要向B、C發送一個删除舊的TrunkBinlog的指令。

      5)發送删除指令成功之後,就可以從頭開始将TrunkBinlog同步給B、C了。

大家發現了麼,這裡的删除TrunkBinlog檔案,會有一個時間視窗,就是删除B、C的TrunkBinlog檔案之後,與将TrunkBinlog同步給他們之前,假如TrunkBinlog當機了,那麼組内的B、C都會沒有TrunkBinlog可使用。

流程圖如下:

FastDFS合并存儲政策

Tracker-Leader選擇TrunkServer

1、描述

      在一個FastDFS叢集之中,在開啟合并存儲時,為了配置設定空間引入了一個TrunkServer角色,該TrunkServer是該Group中的一個Storage,隻是該Storage要負責為該組内的所有Upload操作配置設定空間。為了避免不同Tracker為該Group選擇了不同的TrunkServer,此時引入了Tracker-Leader角色,也就是TrunkServer最終是由Tracker-Leader來選擇的,然後通知給該組内的所有Storage。

2、 Tracker -Leader選擇TrunkServer時機

    1)當組内的一個Storage狀态變成Active時,并且該組還沒有指定TrunkServer,在tracker_mem_active_store_server函數中觸發。

    2)某個Tracker在經過選擇,被設定成Leader時,則為目前還沒有指定TrunkServer的組選擇TrunkServer,在relationship_select_leader函數中觸發。

    3)在定期指定的任務tracker_mem_check_alive函數中,預設該函數100秒指定一次。會嘗試着為每個目前還沒有指定TrunkServer的組選擇TrunkServer。對已經指定的組檢查其TrunkServer是否還處于活動狀态(根據TrunkServer與Tracker的最後心跳時間計算),若不處于活動狀态,則會嘗試着給該組選擇一個新的TrunkServer。在tracker_mem_check_alive函數中觸發。

3、Tracker-Leader選擇TrunkServer的過程

    該過程由tracker_mem_find_trunk_server函數負責,具體操作步驟如下:

    1)依次向組内目前狀态為ACTIVE的Storage發送TRUNK_GET_BINLOG_SIZE指令的消息,來查詢每個Storage目前儲存的Trunk-Binlog的檔案大小。來找到Trunk-Binlog檔案最大的Storage。

    2)若該Group的最後一個TrunkServer與要設定的新的TrunkServer并非同一個,則像該新的TrunkServer發送TRUNK_DELETE_BINLOG_MARKS指令,讓其删除trunk-binlog同步的狀态mark檔案。(既然這個TrunkServer是新的,那麼就要清除同步trunk-binlog的狀态,使其從頭同步trunk-binlog給組内的其他Storage)。

    3)變更該組的TrunkServer,并将修改寫入到storage_groups_new.dat檔案之中,更新該組的最後TrunkServer設定,設定TrunkServer已經變更标志,該标志使得在與Storage的心跳中通知對方TrunkServer已經變更。

繼續閱讀