面向雲資料庫,超低延遲檔案系統PolarFS誕生了
阿里妹導讀:隨著國內首款Cloud Native自研資料庫POLARDB精彩亮相ICDE 2018的同時,作為其核心支撐和使能平臺的PolarFS檔案系統的相關論文"PolarFS: An Ultra-low Latency and Failure Resilient Distributed File System for Shared Storage Cloud Database"也被資料庫頂級會議VLDB 2018錄用,VLDB 2018將於8月份在巴西里約召開。本文著重介紹PolarFS的系統設計與實現。
背景
如同Oracle存在與之匹配的OCFS2,POLARDB作為儲存與計算分離結構的一款資料庫,PolarFS承擔著發揮POLARDB特性至關重要的角色。PolarFS是一款具有超低延遲和高可用能力的分散式檔案系統,其採用了輕量的使用者空間網路和I/O棧構建,而棄用了對應的核心棧,目的是充分發揮RDMA和NVMe SSD等新興硬體的潛力,極大地降低分散式非易失資料訪問的端到端延遲。目前,PolarFS的3副本跨節點寫入的訪問總延遲已經非常接近單機本地PCIe SSD的延遲水平,成功地使得POLARDB在分散式多副本架構下仍然能夠發揮出極致的效能。
設計初衷
針對資料庫設計分散式檔案系統會帶來以下幾點好處:
計算節點和儲存節點可以使用不同的伺服器硬體,並能獨立地進行定製。例如,計算節點不需要考慮儲存容量和記憶體容量的比例,其嚴重依賴於應用場景並且難以預測。
多個節點上的儲存資源能夠形成單一的儲存池,這能降低儲存空間碎化、節點間負載不均衡和空間浪費的風險,儲存容量和系統吞吐量也能容易地進行水平擴充套件。
資料庫應用的持久狀態可下移至分散式檔案系統,由分散式儲存提供較高的資料可用性和可靠性。因此資料庫的高可用處理可被簡化,也利於資料庫例項在計算節點上靈活快速地遷移。
此外,雲資料庫服務也會因此帶來額外的收益:
雲資料庫可以採用虛擬計算環境如KVM等部署形態,其更安全、更易擴充套件和更易升級管理。
一些關鍵的資料庫特性,如一寫多讀例項、資料庫快照等可以通過分散式檔案系統的資料共享、檢查點等技術而得以增強。
系統結構
系統元件
PolarFS系統內部主要分為兩層管理:
儲存資源的虛擬化管理,其負責為每個資料庫例項提供一個邏輯儲存空間。
檔案系統元資料的管理,其負責在該邏輯儲存空間上實現檔案管理,並負責檔案併發訪問的同步和互斥。
PolarFS的系統結構如圖所示:
libpfs是一個使用者空間檔案系統庫,負責資料庫的I/O接入。
PolarSwitch執行在計算節點上,用於轉發資料庫的I/O請求。
ChunkServer部署在儲存節點上,用於處理I/O請求和節點內的儲存資源分佈。
PolarCtrl是系統的控制平面,它包含了一組實現為微服務的管理者,相應地Agent代理被部署到所有的計算和儲存節點上。
在進一步介紹各部分之前,我們先來了解下PolarFS儲存資源的組織方法:
PolarFS的儲存資源管理單元分為3層:Volume、Chunk、Block。
★Volume
Volume是為每個資料庫提供的獨立邏輯儲存空間,其上建立了具體檔案系統供此資料庫使用,其大小為10GB至100TB,可充分適用於典型雲資料庫例項的容量要求。
在Volume上存放了具體檔案系統例項的元資料。檔案系統元資料包括inode、directory entry和空閒資源塊等物件。由於POLARDB採用的是共享檔案儲存架構,我們在檔案層面實現了檔案系統元資料一致性,在每個檔案系統中除DB建立的資料檔案之外,我們還有用於元資料更新的Journal檔案和一個Paxos檔案。我們將檔案系統元資料的更新首先記錄在Journal檔案中,並基於Paxos檔案以disk paxos演算法實現多個例項對Journal檔案的互斥寫訪問。
★Chunk
每個Volume內部被劃分為多個Chunk,Chunk是資料分佈的最小粒度,每個Chunk只存放於儲存節點的單個NVMe SSD盤上,其目的是利於資料高可靠和高可用的管理。典型的Chunk大小為10GB,這遠大於其他類似的系統,例如GFS的64MB。
這樣做的優勢是能夠有效地減少Volume的第一級對映元資料量的大小(例如,100TB的Volume只包含10K個對映項)。一方面,全域性元資料的存放和管理會更容易;另一方面,這使得元資料可以方便地快取在記憶體中,從而有效避免關鍵I/O路徑上的額外元資料訪問開銷。
但這樣做的潛在問題是,當上層資料庫應用出現區域級熱點訪問時,Chunk內熱點無法進一步打散,但是由於我們的每個儲存節點提供的Chunk數量往往遠大於節點數量(節點:Chunk在1:1000量級),PolarFS可支援Chunk的線上遷移,並且服務於大量資料庫例項,因此可以將不同例項的熱點以及同一例項跨Chunk的熱點分佈到不同節點以獲得整體的負載均衡。
★Block
在ChunkServer內,Chunk會被進一步劃分為多個Block,其典型大小為64KB。Blocks動態對映到Chunk 中來實現按需分配。Chunk至Block的對映資訊由ChunkServer自行管理和儲存,除資料Block之外,每個Chunk還包含一些額外Block用來實現Write Ahead Log。我們也將本地對映元資料全部快取在ChunkServer的記憶體中,使得使用者資料的I/O訪問能夠全速推進。
下面我們詳細介紹PolarFS的各個系統元件。
★libpfs
libpfs是一個輕量級的使用者空間庫,PolarFS採用了編譯到資料庫的形態,替換標準的檔案系統介面,這使得全部的I/O路徑都在使用者空間中,資料處理在使用者空間完成,儘可能減少資料的拷貝。這樣做的目的是避免傳統檔案系統從核心空間至使用者空間的訊息傳遞開銷,尤其資料拷貝的開銷。這對於低延遲硬體的效能發揮尤為重要。
其提供了類Posix的檔案系統介面(見下表),因而付出很小的修改代價即可完成資料庫的使用者空間化。
★PolarSwitch
PolarSwitch是部署在計算節點的Daemon,它負責I/O請求對映到具體的後端節點。資料庫通過libpfs將I/O請求傳送給PolarSwitch,每個請求包含了資料庫例項所在的Volume ID、起始偏移和長度。PolarSwitch將其劃分為對應的一到多個Chunk,並將請求發往Chunk所屬的ChunkServer完成訪問。
★ChunkServer
ChunkServer部署在後端儲存節點上。一個儲存節點可以有多個ChunkServer。每個ChunkServer繫結到一個CPU核,並管理一個獨立的NVMe SSD盤,因此ChunkServer之間沒有資源爭搶。
ChunkServer負責Chunk內的資源對映和讀寫。每個Chunk都包括一個WAL,對Chunk的修改會先進Log再修改,保證資料的原子性和永續性。ChunkServer使用了3DXPoint SSD和普通NVMe SSD混合型WAL buffer,Log會優先存放到更快的3DXPoint SSD中。
ChunkServer會複製寫請求到對應的Chunk副本(其他ChunkServer)上,我們通過自己定義的Parallel Raft一致性協議來保證Chunk副本之間在各類故障狀況下資料正確同步和保障已Commit資料不丟失。
★PolarCtrl
PolarCtrl是PolarFS叢集的控制核心。其主要職責包括:
監控ChunkServer的健康狀況,確定哪些ChunkServer有權屬於PolarFS叢集;
Volume建立及Chunk的佈局管理(即Chunk分配到哪些ChunkServer);
Volume至Chunk的元資料資訊維護;
向PolarSwitch推送元資訊快取更新;
監控Volume和Chunk的I/O效能;
週期性地發起副本內和副本間的CRC資料校驗。
PolarCtrl使用了一個關係資料庫雲服務用於管理上述metadata。
中心統控,區域性自治的分散式管理
分散式系統的設計有兩種正規化:中心化和去中心化。中心化的系統包括GFS和HDFS,其包含單中心點,負責維護元資料和叢集成員管理。這樣的系統實現相對簡單,但從可用性和擴充套件性的角度而言,單中心可能會成為全系統的瓶頸。去中心化的系統如Dynamo完全相反,節點間是對等關係,元資料被切分並冗餘放置在所有的節點上。去中心化的系統被認為更可靠,但設計和實現會更復雜。
PolarFS在這兩種設計方式上做了一定權衡,採用了中心統控,區域性自治的方式:PolarCtrl是一箇中心化的master,其負責管理任務,如資源管理和處理控制平面的請求如建立Volume。ChunkServer負責Chunk內部對映的管理,以及Chunk間的資料複製。當ChunkServer彼此互動時,通過ParallelRaft一致性協議來處理故障並自動發起Leader選舉,這個過程無需PolarCtrl參與。
PolarCtrl服務由於不直接處理高併發的I/O流,其狀態更新頻率相對較低,因而可採用典型的多節點高可用架構來提供PolarCtrl服務的持續性,當PolarCtrl因崩潰恢復出現的短暫故障間隙,由於PolarSwitch的快取以及ChunkServer資料平面的區域性元資料管理和自主leader選舉的緣故,PolarFS能夠儘量保證絕大部分資料I/O仍能正常服務。
I/O 流程
下面我們通過一個I/O的處理來說明各元件的互動過程。
PolarFS執行寫I/O請求的過程如上圖所示:
POLARDB通過libpfs傳送一個寫請求,經由ring buffer傳送到PolarSwitch。
PolarSwitch根據本地快取的元資料,將該請求傳送至對應Chunk的主節點。
新寫請求到達後,主節點上的RDMA NIC將寫請求放到一個提前分好的buffer中,並將該請求項加到請求佇列。一個I/O輪詢執行緒不斷輪詢這個請求佇列,一旦發現新請求到來,它就立即開始處理。
請求通過SPDK寫到硬碟的日誌block,並通過RDMA發向副本節點。這些操作都是非同步呼叫,資料傳輸是併發進行的。
當副本請求到達副本節點,副本節點的RDMA NIC同樣會將其放到預分buffer中並加入到複製佇列。
副本節點上的I/O輪詢執行緒被觸發,請求通過SPDK非同步地寫入Chunk的日誌。
當副本節點的寫請求成功回撥後,會通過RDMA向主節點發送一個應答響應。
主節點收到一個複製組中大多數節點的成功返回後,主節點通過SPDK將寫請求應用到資料塊上。
隨後,主節點通過RDMA向PolarSwitch返回。
PolarSwitch標記請求成功並通知上層的POLARDB。
資料副本一致性模型
ParallelRaft協議設計動機
一個產品級別的分散式儲存系統需要確保所有提交的修改在各種邊界情況下均不丟失。PolarFS在Chunk層面引入一致性協議來保證檔案系統資料的可靠性和一致性。設計之初,從工程實現的成熟度考慮,我們選擇了Raft演算法,但對於我們構建的超低延遲的高併發儲存系統而言,很快就遇到了一些坑。
Raft為了簡單性和協議的可理解性,採用了高度序列化的設計。日誌在leader和follower上都不允許有空洞,其意味著所有log項會按照順序被follower確認、被leader提交併apply到所有副本上。因此當有大量併發寫請求執行時,會按順序依次提交。處於佇列尾部的請求,必需等待所有之前的請求已被持久化到硬碟並返回後才會被提交和返回,這增加了平均延遲也降低了吞吐量。我們發現當併發I/O深度從8升到32時,I/O吞吐量會降低一半。
Raft並不十分適用於多連線的在高併發環境。實際中leader和follower使用多條連線來傳送日誌很常見。當一個連結阻塞或者變慢,log項到達follower的順序就會變亂,也即是說,一些次序靠後的log項會比次序靠前的log項先到。但是,Raft的follower必需按次序接收log項,這就意味著這些log項即使被記錄到硬碟也只能等到前面所有缺失的log項到達後才能返回。並且假如大多數follower都因一些缺失的項被阻塞時,leader也會出現卡頓。我們希望有一個更好的協議可以適應這樣的情形。
由於PolarFS之上執行的是Database事務處理系統,它們在資料庫邏輯層面的並行控制演算法使得事務可以交錯或亂序執行的同時還能生成可序列化的結果。這些應用天然就需要容忍標準儲存語義可能出現的I/O亂序完成情況,並由應用自身進一步保證資料一致性。因此我們可以利用這一特點,在PolarFS中依照儲存語義放開Raft一致性協議的某些約束,從而獲得一種更適合高I/O併發能力發揮的一致性協議。
我們在Raft的基礎上,提供了一種改進型的一致性協議ParallelRaft。ParallelRaft的結構與Raft一致,只是放開了其嚴格有序化的約束。
亂序日誌複製
Raft通過兩個方面保障序列化:
當leader傳送一個log項給follower,follower需要返回ack來確認該log項已經被收到且記錄,同時也隱式地表明所有之前的log項均已收到且儲存完畢。
當leader提交一個log項並廣播至所有follower,它也同時確認了所有之前的log項都已被提交了。ParallelRaft打破了這兩個限制,並讓這些步驟可亂序執行。
因此,ParallelRaft與Raft最根本的不同在於,當某個entry提交成功時,並不意味著之前的所有entry都已成功提交。因此我們需要保證:
在這種情況下,單個儲存的狀態不會違反儲存語義的正確性;
所有已提交的entry在各種邊界情況下均不會丟失;
有了這兩點,結合資料庫或其他應用普遍存在的對儲存I/O亂序完成的預設容忍能力,就可以保證它們在PolarFS上的正常運轉,並獲得PolarFS提供的資料可靠性。
ParallelRaft的亂序執行遵循如下原則:
當寫入的Log項彼此的儲存範圍沒有交疊,那麼就認為Log項無衝突可以亂序執行;
否則,衝突的Log項將按照寫入次序依次完成。
容易知道,依照此原則完成的I/O不會違反傳統儲存語義的正確性。
接下來我們來看log的ack-commit-apply環節是如何因此得到優化並且保持一致性的。
亂序確認(ack):當收到來自leader的一個log項後,Raft follower會在它及其所有之前的log項都持久化後,才傳送ack。ParallelRaft則不同,任何log entry成功持久化後均能立即返回,這樣就優化了系統的平均延遲。
亂序提交(commit):Raft leader序列提交log項,一個log項只有之前的所有項提交之後才能提交。而ParallelRaft的leader在一個log項的多數副本已經確認之後即可提交。這符合儲存系統的語義,例如,NVMe SSD驅動並不檢查讀寫命令的LBA來保證並行命令的次序,對命令的完成次序也沒有任何保證。
亂序應用(apply):對於Raft,所有log項都按嚴格的次序apply,因此所有副本的資料檔案都是一致的。但是,ParallelRaft由於亂序的確認和提交,各副本的log都可能在不同位置出現空洞,這裡的挑戰是,如何保證前面log項有缺失時,安全地apply一個log項?
ParallelRaft引入了一種新型的資料結構look behind buffer來解決apply中的問題。
ParallelRaft的每個log項都附帶有一個look behind buffer。look behind buffer存放了前N個log項修改的LBA摘要資訊。
look behind buffer的作用就像log空洞上架設的橋樑,N表示橋樑的寬度,也就是允許單個空洞的最大長度,N的具體取值可根據網路連續缺失log項的概率大小,靜態地調整為合適的值,以保證log橋樑的連續性。
通過look behind buffer,follower能夠知道一個log項是否衝突,也就是說是否有缺失的前序log項修改了範圍重疊的LBAs。沒有衝突的log項能被安全apply。如有衝突,它們會被加到一個pending list,待之前缺失的衝突log項apply之後,才會接著apply。
通過上述的非同步ack、非同步commit和非同步apply,PolarFS的chunk log entry的寫入和提交避免了次序造成的額外等待時間,從而有效縮減了高併發3副本寫的平均時延。
ParallelRaft協議正確性
我們在ParallelRaft的設計中,確保了Raft協議關鍵特性不丟失,從而保障了新協議的正確性。
ParallelRaft協議的設計繼承了原有Raft協議的Election Safety、Leader Append-Only及Log Matching特性。
衝突log會以嚴格的次序提交,因此協議的State Machine Safety特效能夠最終得以保證。
我們在Leader選舉階段額外引入了一個Merge階段,填補Leader中log的空洞,能夠有效保障協議的Leader Completeness特性。
PolarFS中與POLARDB緊密相關的設計
檔案系統多副本高速寫入——資料庫單例項的超高TPS,資料高可靠
PolarFS設計中採用瞭如下技術以充分發揮I/O效能:
PolarFS採用了繫結CPU的單執行緒有限狀態機的方式處理I/O,避免了多執行緒I/O pipeline方式的上下文切換開銷。
PolarFS優化了記憶體的分配,採用MemoryPool減少記憶體物件構造和析構的開銷,採用巨頁來降低分頁和TLB更新的開銷。
PolarFS通過中心加區域性自治的結構,所有元資料均快取在系統各部件的記憶體中,基本完全避免了額外的元資料I/O。
PolarFS採用了全使用者空間I/O棧,包括RDMA和SPDK,避免了核心網路棧和儲存棧的開銷。
在相同硬體環境下的對比測試,PolarFS中資料塊3副本寫入效能接近於單副本本地SSD的延遲效能。從而在保障資料可靠性的同時,極大地提升POLARDB的單例項TPS效能。
下圖是我們採用Sysbench對不同負載進行的初步測試比較。
POLARDB on PolarFS
Alibaba MySQL Cloud Service RDS
用例負載:OLTP,只讀、只寫(update : delete : insert = 2:1:1)、讀寫混合(read : write = 7:2)。資料庫測試集資料量為500GB。
可以發現POLARDB在PolarFS下取得了較好的效能,PolarFS同時支援了POLARDB的高TPS和資料的高可靠性。
檔案系統共享訪問——寫多讀的資料庫QPS強擴充套件,資料庫例項的Failover
PolarFS是共享訪問的分散式檔案系統,每個檔案系統例項都有相應的Journal檔案和與之對應的Paxos檔案。Journal檔案記錄了metadata的修改歷史,是共享例項之間元資料同步的中心。Journal檔案邏輯上是一個固定大小的迴圈buffer。PolarFS會根據水位來回收journal。Paxos檔案基於Disk Paxos實現了分散式互斥鎖。
由於journal對於PolarFS非常關鍵,它們的修改必需被Paxos互斥鎖保護。如果一個節點希望在journal中追加項,其必需使用DiskPaxos演算法來獲取Paxos檔案中的鎖。通常,鎖的使用者會在記錄持久化後馬上釋放鎖。但是一些故障情況下使用者不釋放鎖。為此在Paxos互斥鎖上分配有一個租約lease。其他競爭者可以重啟競爭過程。當PolarFS當節點開始同步其他節點修改的元資料時,它從上次掃描的位置掃描到journal末尾,將新entry更新到memory cache中。
下圖展示了檔案系統元資料更新和同步的過程。
節點1分配塊201至檔案316後,請求互斥鎖,並獲得。
Node 1開始記錄事務至journal中。最後寫入項標記為pending tail。當所有的項記錄之後,pending tail變成journal的有效tail。
Node1更新superblock,記錄修改的元資料。與此同時,node2嘗試獲取node1擁有的互斥鎖,Node2會失敗重試。
Node2在Node1釋放lock後拿到鎖,但journal中node1追加的新項決定了node2的本地元資料是過時的。
Node2掃描新項後釋放lock。然後node2回滾未記錄的事務並更新本地metadata。最後Node2進行事務重試。
Node3開始自動同步元資料,它只需要load增量項並在它本地重放即可。
PolarFS的上述共享機制非常適合POLARDB一寫多讀的典型應用擴充套件模式。一寫多讀模式下沒有鎖爭用開銷,只讀例項可以通過原子I/O無鎖獲取Journal資訊,從而使得POLARDB可以提供近線性的QPS效能擴充套件。
由於PolarFS支援了基本的多寫一致性保障,當可寫例項出現故障時,POLARDB能夠方便地將只讀例項升級為可寫例項,而不必擔心底層儲存產生不一致問題,因而方便地提供了資料庫例項Failover的功能。
檔案系統級快照——POLARDB的瞬時邏輯備份
對於百TB級超大資料庫例項的備份而言,資料庫快照是必須支援的功能。
PolarFS採用了自有的專利快照技術,能夠基於位於底層的多個ChunkServer的區域性快照,構建Volume上的統一的檔案系統即時映像。POLARDB利用自身資料庫的日誌,能夠基於此檔案系統映像快速構建出此具體時點的資料庫快照,從而有效支援資料庫備份和資料分析的需求。
可以發現,POLARDB的高效能、強擴充套件、輕運維等具備競爭優勢的優異特性,與PolarFS的緊密協作息息相關,PolarFS發揮了強大的使能作用。
結論
PolarFS是一個專為雲資料庫而設計的分散式檔案系統,其能夠支援跨節點高可靠性同時提供極致的效能。PolarFS採用了新興硬體和先進的優化技術,例如OS-bypass和zero-copy,使得PolarFS中資料塊3副本寫入效能接近於單副本本地SSD的延遲效能。PolarFS在使用者空間實現了POSIX相容介面,使得POLARDB等資料庫服務能夠儘量少地修改即可獲得PolarFS帶來的高效能的優勢。
可以看到,面向資料庫的專有檔案系統,是保障未來資料庫技術領先的一個不可或缺的關鍵一環。資料庫核心技術的進展及其專有檔案系統的使能,是一個相輔相成的演進過程,二者的結合也會隨著當今系統技術的進步而愈加緊密。
未來我們將探索NVM和FPGA等新硬體,以期通過檔案系統與資料庫的深度結合來進一步優化POLARDB資料庫的效能。
作者:鳴嵩,弘然,明書,旭危,寧進,文義,韓逸,翊雲
你可能還喜歡
點選下方圖片即可閱讀
關注「阿里技術」
把握前沿技術脈搏