1. 程式人生 > >TFS分布式文件系統

TFS分布式文件系統

宕機 http 最短 擴展 自己 擁有 air 問題 需要

簡介

TFS(Taobao !FileSystem)是一個高可擴展、高可用、高性能、面向互聯網服務的分布式文件系統,主要針對海量的非結構化數據,它構築在普通的Linux機器集群上,可為外部提供高可靠和高並發的存儲訪問。TFS為淘寶提供海量小文件存儲,通常文件大小不超過1M,滿足了淘寶對小文件存儲的需求,被廣泛地應用在淘寶各項應用中。它采用了HA架構和平滑擴容,保證了整個文件系統的可用性和擴展性。同時扁平化的數據組織結構,可將文件名映射到文件的物理地址,簡化了文件的訪問流程,一定程度上為TFS提供了良好的讀寫性能。

TFS的總體結構

一個TFS集群由兩個!NameServer節點(一主一備)和多個!DataServer節點組成。這些服務程序都是作為一個用戶級的程序運行在普通Linux機器上的。

在TFS中,將大量的小文件(實際數據文件)合並成為一個大文件,這個大文件稱為塊(Block), 每個Block擁有在集群內唯一的編號(Block Id), Block Id在!NameServer在創建Block的時候分配, !NameServer維護block與!DataServer的關系。Block中的實際數據都存儲在!DataServer上。而一臺!DataServer服務器一般會有多個獨立!DataServer進程存在,每個進程負責管理一個掛載點,這個掛載點一般是一個獨立磁盤上的文件目錄,以降低單個磁盤損壞帶來的影響。

!NameServer主要功能是: 管理維護Block和!DataServer相關信息,包括!DataServer加入,退出, 心跳信息, block和!DataServer的對應關系建立,解除。正常情況下,一個塊會在!DataServer上存在, 主!NameServer負責Block的創建,刪除,復制,均衡,整理, !NameServer不負責實際數據的讀寫,實際數據的讀寫由!DataServer完成。

!DataServer主要功能是: 負責實際數據的存儲和讀寫。

同時為了考慮容災,!NameServer采用了HA結構,即兩臺機器互為熱備,同時運行,一臺為主,一臺為備,主機綁定到對外vip,提供服務;當主機器宕機後,迅速將vip綁定至備份!NameServer,將其切換為主機,對外提供服務。圖中的HeartAgent就完成了此功能。

技術分享

TFS的塊大小可以通過配置項來決定,通常使用的塊大小為64M。TFS的設計目標是海量小文件的存儲,所以每個塊中會存儲許多不同的小文件。!DataServer進程會給Block中的每個文件分配一個ID(File ID,該ID在每個Block中唯一),並將每個文件在Block中的信息存放在和Block對應的Index文件中。這個Index文件一般都會全部load在內存,除非出現!DataServer服務器內存和集群中所存放文件平均大小不匹配的情況。

另外,還可以部署一個對等的TFS集群,作為當前集群的輔集群。輔集群不提供來自應用的寫入,只接受來自主集群的寫入。當前主集群的每個數據變更操作都會重放至輔集群。輔集群也可以提供對外的讀,並且在主集群出現故障的時候,可以接管主集群的工作。

1. 平滑擴容

原有TFS集群運行一定時間後,集群容量不足,此時需要對TFS集群擴容。由於DataServer與NameServer之間使用心跳機制通信,如果系統擴容,只需要將相應數量的新!DataServer服務器部署好應用程序後啟動即可。這些!DataServer服務器會向!NameServer進行心跳匯報。!NameServer會根據!DataServer容量的比率和!DataServer的負載決定新數據寫往哪臺!DataServer的服務器。根據寫入策略,容量較小,負載較輕的服務器新數據寫入的概率會比較高。同時,在集群負載比較輕的時候,!NameServer會對!DataServer上的Block進行均衡,使所有!DataServer的容量盡早達到均衡。

進行均衡計劃時,首先計算每臺機器應擁有的blocks平均數量,然後將機器劃分為兩堆,一堆是超過平均數量的,作為移動源;一類是低於平均數量的,作為移動目的。

移動目的的選擇:首先一個block的移動的源和目的,應該保持在同一網段內,也就是要與另外的block不同網段;另外,在作為目的的一定機器內,優先選擇同機器的源到目的之間移動,也就是同臺!DataServer服務器中的不同!DataServer進程。
當有服務器故障或者下線退出時(單個集群內的不同網段機器不能同時退出),不影響TFS的服務。此時!NameServer會檢測到備份數減少的Block,對這些Block重新進行數據復制。

在創建復制計劃時,一次要復制多個block, 每個block的復制源和目的都要盡可能的不同,並且保證每個block在不同的子網段內。因此采用輪換選擇(roundrobin)算法,並結合加權平均。

由於DataServer之間的通信是主要發生在數據寫入轉發的時候和數據復制的時候,集群擴容基本沒有影響。假設一個Block為64M,數量級為1PB。那麽NameServer上會有 1 * 1024 * 1024 * 1024 / 64 = 16.7M個block。假設每個Block的元數據大小為0.1K,則占用內存不到2G。

2. 存儲機制

在TFS中,將大量的小文件(實際用戶文件)合並成為一個大文件,這個大文件稱為塊(Block)。TFS以Block的方式組織文件的存儲。每一個Block在整個集群內擁有唯一的編號,這個編號是由NameServer進行分配的,而DataServer上實際存儲了該Block。在!NameServer節點中存儲了所有的Block的信息,一個Block存儲於多個!DataServer中以保證數據的冗余。對於數據讀寫請求,均先由!NameServer選擇合適的!DataServer節點返回給客戶端,再在對應的!DataServer節點上進行數據操作。!NameServer需要維護Block信息列表,以及Block與!DataServer之間的映射關系,其存儲的元數據結構如下:

技術分享

在!DataServer節點上,在掛載目錄上會有很多物理塊,物理塊以文件的形式存在磁盤上,並在!DataServer部署前預先分配,以保證後續的訪問速度和減少碎片產生。為了滿足這個特性,!DataServer現一般在EXT4文件系統上運行。物理塊分為主塊和擴展塊,一般主塊的大小會遠大於擴展塊,使用擴展塊是為了滿足文件更新操作時文件大小的變化。每個Block在文件系統上以“主塊+擴展塊”的方式存儲。每一個Block可能對應於多個物理塊,其中包括一個主塊,多個擴展塊。
在DataServer端,每個Block可能會有多個實際的物理文件組成:一個主Physical Block文件,N個擴展Physical Block文件和一個與該Block對應的索引文件。Block中的每個小文件會用一個block內唯一的fileid來標識。!DataServer會在啟動的時候把自身所擁有的Block和對應的Index加載進來。

3. 容錯機制

  1. 3.1 集群容錯

TFS可以配置主輔集群,一般主輔集群會存放在兩個不同的機房。主集群提供所有功能,輔集群只提供讀。主集群會把所有操作重放到輔集群。這樣既提供了負載均衡,又可以在主集群機房出現異常的情況不會中斷服務或者丟失數據。

  1. 3.2 !NameServer容錯

Namserver主要管理了!DataServer和Block之間的關系。如每個!DataServer擁有哪些Block,每個Block存放在哪些!DataServer上等。同時,!NameServer采用了HA結構,一主一備,主NameServer上的操作會重放至備NameServer。如果主NameServer出現問題,可以實時切換到備NameServer。
另外!NameServer和!DataServer之間也會有定時的heartbeat,!DataServer會把自己擁有的Block發送給!NameServer。!NameServer會根據這些信息重建!DataServer和Block的關系。

  1. 3.3 !DataServer容錯

TFS采用Block存儲多份的方式來實現!DataServer的容錯。每一個Block會在TFS中存在多份,一般為3份,並且分布在不同網段的不同!DataServer上。對於每一個寫入請求,必須在所有的Block寫入成功才算成功。當出現磁盤損壞!DataServer宕機的時候,TFS啟動復制流程,把備份數未達到最小備份數的Block盡快復制到其他DataServer上去。 TFS對每一個文件會記錄校驗crc,當客戶端發現crc和文件內容不匹配時,會自動切換到一個好的block上讀取。此後客戶端將會實現自動修復單個文件損壞的情況。

4. 並發機制

對於同一個文件來說,多個用戶可以並發讀。
現有TFS並不支持並發寫一個文件。一個文件只會有一個用戶在寫。這在TFS的設計裏面對應著是一個block同時只能有一個寫或者更新操作。

5. TFS文件名的結構

TFS的文件名由塊號和文件號通過某種對應關系組成,最大長度為18字節。文件名固定以T開始,第二字節為該集群的編號(可以在配置項中指定,取值範圍 1~9)。余下的字節由Block ID和File ID通過一定的編碼方式得到。文件名由客戶端程序進行編碼和解碼,它映射方式如下圖:

技術分享

TFS客戶程序在讀文件的時候通過將文件名轉換為BlockID和FileID信息,然後可以在!NameServer取得該塊所在!DataServer信息(如果客戶端有該Block與!DataServere的緩存,則直接從緩存中取),然後與!DataServer進行讀取操作。

6. TFS性能數據

  1. 軟件環境描述

【測試機軟件情況描述】
(1) Red Hat Enterprise Linux AS release 4 (Nahant Update 8)
(2) gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-11)
(3) 部署了TFS客戶端程序
【服務器軟件情況描述】
(1) Red Hat Enterprise Linux Server release 5.4 (Tikanga)
(2) gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-9)
(3) 部署了2臺!DataServer程序。
【服務器軟件情況描述】
(1) Red Hat Enterprise Linux Server release 5.4 (Tikanga)
(2) gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-46)
(3) 部署了2臺!NameServer(HA)程序。

  1. 硬件環境描述

【測試機硬件情況描述】
(1) 一枚八核Intel(R) Xeon(R) CPU E5520 @ 2.27GHz
(2) 內存總數8299424 kB
【服務器硬件情況描述】cpu/memory等
(1) 一枚八核Intel(R) Xeon(R) CPU E5520 @ 2.27GHz
(2) 內存總數8165616 kB

  1. 隨機讀取1K~50K大小的文件性能


技術分享

Read的TPS隨著線程數的增加而增加,增長逐漸趨緩,到90線程的時候達到第一個高峰,此時再增加讀線程,則TPS不再穩定增長。

  1. 隨機寫入1K~50K大小的文件


技術分享

Write的TPS在線程數60左右達到高峰,此時再增加寫入線程,TPS不再穩定增長。

  1. 在不同線程寫壓力下的讀文件性能


技術分享


技術分享

可以看出隨著寫壓力的增加,讀文件的TPS會大幅下滑。當寫壓力達到一定程度時讀文件TPS趨緩。

同時,對平均大小為20K的文件進行了測試,測試中讀:寫:更新:刪除操作的比率為100:18:1:1時,在!DataServer服務器磁盤util訪問達到80%以上時,響應時間如下:

TYPE SUCCCOUNT FAILCOUNT AVG(us) MIN(us) MAX(us)
read 100000 0 20886 925 1170418
write 18000 0 17192 2495 1660686
update 1000 0 48489 5755 1205119
delete 1000 0 14221 382 591651

TYPE:操作類型
SUCCCOUNT:成功個數
FAILCOUNT:失敗個數
AVG:平均響應時間
MIN:最短響應時間
MAX: 最大響應時間

TFS寫操作數據流

TFS系統中,nameserver會保證一個文件有多個副本存儲於不同的dataserver上以保證冗余。當由於dataserver服務器宕機或由於其他原因退出系統導致某些文件副本數量下降時,nameserver將會調度新的dataserver節點存儲文件備份。同樣為了保證數據一致性,當寫入一個文件時,只有所有參與的dataserver均寫入成功時,該操作才算成功。TFS的寫操作數據流圖如下所示:

技術分享

客戶端首先向nameserver發起寫請求,nameserver需要根據dataserver上的可寫塊,容量和負載加權平均來選擇一個可寫的block。並且在該block所在的多個dataserver中選擇一個作為寫入的master,這個選擇過程也需要根據dataserver的負載以及當前作為master的次數來計算,使得每個dataserver作為master的機會均等。master一段選定,除非master宕機,不會更換,一旦master宕機,需要在剩余的dataserver中選擇新的master。返回一個dataserver列表。
客戶端向master dataserver開始數據寫入操作。master server將數據傳輸為其他的dataserver節點,只有當所有dataserver節點寫入均成功時,master server才會向nameserver和客戶端返回操作成功的信息。

獲得Block ID和File ID

根據TFS文件名解析出Block ID和block中的File ID.

獲取dataserver地址

向nameserver發送查詢請求得到Block ID所在的dataserver地址。

由於nameserver中維護了block和dataserver的對應關系,所以nameserver能夠提供相應的信息。

Note: 由於TFS是把大量小文件放在一個block裏面,

所以TFS的文件復制是基於block的,而且復制出來的block的block id應該是一致的

請求文件

通過發送Block_ID、File_ID和offset為參數的讀請求到對應的dataserver,得到文件內容。

dataserver會根據本地記錄的信息來得到File ID所在block的偏移量,從而讀取到正確的文件內容.

技術分享

NS中BlockManager和ServerManager介紹

1. Ns中的BlockManager用來管理所有來自Ds的Block信息。因為Block的數量比較多,因此,BlockManager將Block組織成HashMap的數據結構,Hash的桶的個數由MAX_BLOCK_CHUNK_NUMS決定。另外,為了組織方便,定義了一個雙向隊列std::deque<std::pair<uint32, uint64> > delete_block_queue_,用來對刪除的Block(以及Block所在的Server)進行一個管理並且將最近(在規定時間內)寫過的Block也組織成一個HashMap便於管理(難道這個和延遲刪有關,就是最近時間內有寫入的不馬上刪除?)。通過這些數據結構,BlockManager可以實現insert一個Block,remove一個Block,將刪除的Block以及對應的Server加入和從刪除隊列中移除,dump所有的Block信息以及最近寫入比較頻繁的Block信息。 此外,BlockManager還可以判斷某個Block是否存在(通過BlockId)以及先通過BlockId獲得BlockCollect結構,進而獲取到該Block所對應的Ds的信息(這裏提供多個重載)。在與Ds的關系方便,BlockManager提供了建立、解除以及更新具體Block與Ds關系接口,以及判斷某個Block是否需要復制、壓縮、遷移的接口。最後,BlockManager還會根據時間在last_write_blocks_[i]中插入和刪除最近寫入的Block。

2. Ns中的ServerManager用來管理所有的Server信息。為了管理好活動的和不可服務的DS,ServerManager定義了兩個Server列表servers_和dead_servers_。針對具體的DS的操作大致包括加入Server到活動列表(分為是新加入的還是暫時不可服務又好了的),從活動列表中移除Server到不可服務列表(這種情況可能發生在Ds某種原因退出)。當Server在不可服務列表中超過一定時間後,就會將它從不可服務列表中移除(這種情況可能是磁盤壞掉了,所以等換好新盤啟動需要一定的時間)。另外,通過ServerManager可以得到活動Server列表以及不可服務Server列表以及某一個範圍內的Server列表。 與BlockManager類似,ServerManager也提供了建立和解除具體Ds與Block的關系接口,但這些過程是以各個Server為中心來完成的。此外,ServerManager還負責挑選可寫主塊,先由ServerManager挑一個Server,再由Server挑一個Block。當BlockManager中發現某些Block需要復制時,由於每個Block對應多個Server,ServerManager負責挑選出要復制的源Server和目標Server。當ServerManager發現某個Server不滿足均衡(目前是將活動列表中的前32個server根據容量百分比,特別小的作為目標Server,特別大的作為源Server)時,針對該Server(作為Source Server)裏面的具體Block,ServerManager負責挑選出可做為目標的Server。當某種原因導致Block的副本數大於最大副本數時,ServerManager會根據包含該Block的Server的容量進行排序並在滿足一定條件下選擇一個Server將多余的Block進行刪除。(在選擇復制、遷移目標Server時需要考慮Server是否不在任務隊列裏,是否有空間,以及是否和已經挑選的Server在不同機架)

Dataserver後臺線程介紹

一、 心跳線程

這裏的心跳是指Ds向Ns發的周期性統計信息。原先的做法是當Ds需要匯報block時會將blockInfo的信息通過心跳包的形式發給Ns。而現在的心跳只負責keepalive,匯報block的工作由專門的包進行發送。(所以之前的做法是Ns會在心跳的回復包中帶上一個狀態(status),Ds在收到這個狀態包後,會根據狀態進行一些相應的操作(比如淘汰過期的Block以及新增Block操作等))。

二、 復制線程(replicate_block.cpp)

人工或者Ns可以添加復制Block任務至復制隊列中,復制線程會從復制隊列中取出並執行。結合Ns,整個復制的大致過程是ns向復制的源Ds發起復制任務,源Ds將復制任務所需要的信息結構(ReplBlockExt)加入復制隊列中。復制線程取出一個復制任務後,會先通過ReadRawData接口將源Ds的Block數據讀出,然後向目標Ds發WriteRawData消息,目標ds在接到writeRawData消息後復制數據,然後通過batch_write_info進行index的復制。然後源Ds將復制是否成功的狀態向Ns進行回復,Ns在收到復制成功的消息後會進行Block與Ds關系的更新。當從ns中收到move的操作後,還會將源ds上的Block刪除掉。在管理復制的過程中,還用到兩個重要的數據結構ReplicateBlockMap_和ClonedBlockMap_,前者用來記錄源中將要進行復制的Block,後者用來記錄目標中正在復制Block的狀態。

三、 壓縮線程(compact_block.cpp)

真正的壓縮線程也從壓縮隊列中取出並進行執行(按文件進行,小文件合成一起發送)。壓縮的過程其實和復制有點像,只是說不需要將刪除的文件數據以及index數據復制到新創建的壓縮塊中。要判斷某個文件是否被刪除,還需要拿index文件的offset去fileinfo裏面取刪除標記,如果標記不是刪除的,那麽就可以進行write_raw_data的操作,否則則濾過。

四、 檢查線程

a 清理過期的Datafile; b 修復check_file_queue_中的邏輯塊(block_checker.cpp) c 清理過期的復制塊(由於復制過程中出錯導致的錯誤復制塊,復制目標的ds做) d 清理過期的壓縮塊(由於壓縮過程中出錯導致的錯誤壓縮塊,壓縮在同一個ds上做) e 每天rotate讀寫日誌,清理過期的錯誤邏輯塊 f 讀日誌累積後刷磁盤

b的詳細過程: 每次對文件進行讀寫刪操作失敗的時候,會try_add_repair_task(blockid, ret)來將ret錯誤的block加入check_file_queue_中,正常情況下加入的為-EIO(I/O錯誤)的錯誤Block,那什麽時候加入的是CRC的錯誤呢?人工進行修復的時候發該類型的CRC_ERROR_MESSAGE消息,然後也會加入check_file_queue_中.也就是說人工修復是認為CRC錯誤的。然後在check的時候會根據類型進行do_repair_crc還是do_repair_eio操作,對各自類型進行錯誤統計,其中check_block的過程就是通過crc_error和eio_error數量來判斷該Block是否過期(對於過期的邏輯塊,在錯誤位圖上進行相應物理塊的設置),如果是,則請求Ns進行update_block_info, 如果不是,對於eio請求,則無法修復,設置Block不正常(abnormal)的最新時間,對於Crc的則嘗試修復,修復過程中會從其他Ds上讀副本來進行修復,若出錯則會請求Ns進行update_block_info,否則設置Block不正常的最新時間。

rcserver介紹

TFS 在2.0版本增加了一個server, 叫做 rcserver. 這個 server 主要是為了淘寶內部管理使用 TFS 的各個應用. 我們給每個應用分配一個唯一的 AppKey. TFS 客戶端使用這個 AppKey 登錄到 rcserver, 取得自己應該訪問的 TFS 集群信息. 客戶端還會定期把自己的一些統計值發送給 rcserver. 具體信息可以參看源碼中 doc 目錄下的關於 rcserve 的文檔.

metaserver介紹

1. 簡介

metaserver是我們在2.0版本引進的一個服務. 用來存儲一些元數據信息, 這樣原本不支持自定義文件名的 TFS 就可以在 metaserver 的幫助下, 支持自定義文件名了.

2. 組成

metaserver 由一個主控節點(rootserver), 若幹服務節點(metaserver) 組成. rootserver 主要管理所有的 metaserver. 而metaserver 完成跟文件相關的操作. metaserver 緩存最近的被訪問到目錄和文件信息. 對於任何寫入, 除了更改自己的緩存外還要更改後端持久化存儲中的內容. 目前我們暫時使用 mysql 數據庫提供後端持久化存儲, 將來會替換成淘寶自己的分布式數據庫 oceanbase.

3. 訪問過程

客戶端在做自定義文件名的讀操作的時候, 會先從 rootserver 得到關於 metaserver 的信息, 並緩存在自己的內存中. 然後用自定義文件名去 metaserver 中查找 TFS 文件名信息, 再去 TFS 中訪問該文件. 客戶端在做自定義文件名的寫操作的時候, 會先寫入到 TFS 中, 再把 TFS 文件名和自定義文件的對應關系寫入metaserver中.

4. 自定義文件名的限制

我們目前要求使用自定義文件名的時候必須傳入一個app_id 一個 uid. 這兩個 id 成為所有自定義文件名的固定前綴. mv 操作只能在相同的app_id, uid 之下進行. 這樣做是我們為了簡化實現復雜度. 我們的應用都是要面向海量客戶, 每個客戶自身的數據量有限. 有了上面的限制, 我們可以總是把對同一個app_id uid的請求用相同的 metaserver 來響應. 這樣使得我們可以很容易的處理數據一致性問題.

5. 寫文件的特殊點

在使用自定義文件名寫文件的時候, 必須先調用 creat_file 接口建立文件. 文件建立之後, 可以多進程並發的寫這個文件. 這樣是為了便於大文件的分片上傳. 我們還是不支持對已有文件的修改, 所以分片上傳的時候各個分片是不能互相覆蓋的.

6. 後續計劃

目前自定義文件名提供的功能還比較簡單初級, 我們會根據應用的需求逐步完善功能, 提高性能. 我們將來計劃在 oceanbase 團隊的幫助下, 把後端存儲替換成 oceanbase 數據庫. 另:編譯時候我們設置了 mysql 的最低版本, 這個版本設置的比較高, 其實只要是5.0以上版本就可以支持這個應用.

TFS分布式文件系統