PolarDB是阿里雲基於MySQL推出的雲原生資料庫(Cloud Native Database)產品,通過將資料庫中計算和儲存分離,多個計算節點訪問同一份儲存資料的方式來解決目前MySQL資料庫存在的運維和擴充套件性問題;通過引入RDMA和SPDK等新硬體來改造傳統的網路和IO協議棧來極大提升資料庫效能。代表了未來資料庫發展的一個方向。本系列共2篇文章,主要分析為什麼會出現PolarDB以及其技術實現。
由於PolarDB並不開源,因此只能基於阿里雲公開的技術資料進行解讀。這些資料包括從去年下半年開始陸續在阿里雲棲社群、雲棲大會等場合釋出的PolarDB相關資料,以及今年以來公開的PolarDB後端共享儲存PolarFS相關文章。
PolarDB出現背景
MySQL雲服務遇到的問題
首先來了解下為什麼會出現PolarDB。阿里雲資料庫團隊具備國內領先的技術能力,為MySQL等資料庫在國內的推廣起到了很大的作用。在阿里雲上也維護了非常龐大的MySQL雲服務(RDS)叢集,但也遇到了很多棘手的問題。舉例如下:
-
例項資料量太大,單例項幾個TB的資料,這樣即使使用xtrabackup物理備份,也需要很長的備份時間,且備份期間寫入量大的話可能導致redo日誌被覆蓋引起備份失敗;
-
大例項故障恢復需要重建時,耗時太長,影響服務可用性(此時存活節點也掛了,那麼完蛋了)。時間長有2個原因,一是備份需要很長時間,二是恢復的時候回放redo也需要較長時間;
-
大例項做只讀擴充套件麻煩,因為只讀例項的資料是單獨一份的,所以也需要通過備份來重建;
-
RDS例項叢集很大,包括成千上萬個例項,可能同時有很多例項同時在備份,會佔用雲服務巨大的網路和IO頻寬,導致雲服務不穩定;
-
雲服務一般使用雲硬碟,導致資料庫的效能沒有物理機例項好,比如IO延時過高;
-
主庫寫入量大的時候,會導致主從複製延遲過大,semi-sync/半同步複製也沒法徹底解決,這是由於mysql基於binlog複製,需要走完整的mysql事務處理流程。
-
對於需要讀寫分離,且要求部署多個只讀節點的使用者,最明顯的感覺就是每增加一個只讀例項,成本是線性增長的。
其實不僅僅是阿里雲RDS,網易雲上的RDS服務也有數千個例項,同樣遇到了類似的問題,我們是親身經歷而非感同身受。應該說就目前的MySQL技術實現方案,要解決上述任何一個問題都不是件容易的事情,甚至有幾個問題是無法避免的。
現有解決方案及不足
那麼,跳出MySQL,是否有解決方案呢,分析目前業界的資料庫和儲存領域技術,可以發現基於共享儲存是個可選的方案,所謂資料庫共享儲存方案指的是RDS例項(一般指一主一從的高可用例項)和只讀例項共享同一份資料,這樣在例項故障或只讀擴充套件時就無需拷貝資料了,只需簡單得把故障節點重新拉起來,或者新建個只讀計算節點即可,省時省力更省錢。共享儲存可通過快照技術(snapshot/checkpoint)和寫時拷貝(copy-on-write,COW)來解決資料備份和誤操作恢復問題,將所需備份的資料量分攤到較長的一段時間內,而不需要瞬時完成,這樣就不會導致多例項同時備份導致網路和IO資料風暴。下圖就是一個典型的資料庫共享儲存方案,Primary節點即資料庫主節點,對外提供讀寫服務,Read Only節點可以是Primary的災備節點,也可以是對外提供只讀服務的節點,他們共享一份底層資料。
理想很豐滿,但現實卻很骨感,目前可用的共享儲存方案寥寥無幾,比如在Hadoop生態圈佔統治地位的HDFS,以及在通用儲存領域風生水起的Ceph,只是如果將其作為線上資料處理(OLTP)服務的共享儲存,最終對使用者呈現的效能是不可接受的。除此之外,還存在大量與現有資料庫實現整合適配等問題。
PolarDB實現方案
雲原生資料庫
說道雲原生資料庫,就不得不提Aurora。其在2014年下半年釋出後,轟動了整個資料庫領域。Aurora對MySQL儲存層進行了大刀闊斧的改造,將其拆為獨立的儲存節點(主要做資料塊儲存,資料庫快照的伺服器)。上層的MySQL計算節點(主要做SQL解析以及儲存引擎計算的伺服器)共享同一個儲存節點,可在同一個共享儲存上快速部署新的計算節點,高效解決服務能力擴充套件和服務高可用問題。基於日誌即資料的思想,大大減少了計算節點和儲存節點間的網路IO,進一步提升了資料庫的效能。再利用儲存領域成熟的快照技術,解決資料庫資料備份問題。被公認為關係型資料庫的未來發展方向之一。截止2018年上半年,Aurora已經實現了多個計算節點同時提供寫服務的能力,繼續在雲原生資料庫上保持領先的地位。
不難推斷,在Aurora釋出3年後推出的PolarDB,肯定對Aurora進行了深入的研究,並借鑑了很多技術實現方法。關於Aurora的分析,國內外,包括公司內都已進行了深入分析,本文不再展開描述。下面著重介紹PolarDB實現。我們採用先儲存後計算的方式,先講清楚PolarFS共享儲存的實現,再分析PolarDB計算層如何適配PolarFS。
PolarDB架構
上圖為PolarFS視角看到的PolarDB實現架構。一套PolarDB至少包括3個部分,分別為最底層的共享儲存,與使用者互動的MySQL節點,還有使用者進行系統管理的PolarCtrl。而其中PolarFS又可進一步拆分為libpfs、PolarSwitch和ChunkServer。下面進行簡單說明:
-
MySQL節點,即圖中的POLARDB,負責使用者SQL解析、事務處理等資料庫相關操作,扮演計算節點角色;
-
libpfs是一個使用者空間檔案系統庫,提供POSIX相容的檔案操作API介面,嵌入到PolarDB負責資料庫IO(File IO)接入;
-
PolarSwitch執行在計算節點主機(Host)上,每個Host部署一個PolarSwitch的守護程式,其將資料庫檔案IO變換為塊裝置IO,併傳送到具體的後端節點(即ChunkServer);
-
ChunkServer部署在儲存節點上,用於處理塊裝置IO(Block IO)請求和節點內的儲存資源分佈;
-
PolarCtrl是系統的控制平面,PolarFS叢集的控制核心,所有的計算和儲存節點均部署有PolarCtrl的Agent。
PolarFS的儲存組織
與大多數儲存系統一樣,PolarFS對儲存資源也進行了多層封裝和管理,PolarFS的儲存層次包括:Volume、Chunk和Block,分別對應儲存領域中的資料卷,資料區和資料塊,在有些系統中Chunk又被成為Extent,均表示一段連續的塊組成的更大的區域,作為分配的基本單位。一張圖可以大致表現各層的關係:
Volume
當使用者申請建立PolarDB資料庫例項時,系統就會為該例項建立一個Volume(卷,本文後續將這兩種表達混用),每個卷都有多個Chunk組成,其大小就是使用者指定的資料庫例項大小,PolarDB支援使用者建立的例項大小範圍是10GB至100TB,滿足絕大部分雲資料庫例項的容量要求。
跟其他傳統的塊裝置一樣,捲上的讀寫IO以512B大小對齊,對捲上同個Chunk的修改操作是原子的。當然,卷還是塊裝置層面的概念,在提供給資料庫例項使用前,需在捲上格式化一個PolarFS檔案系統(PFS)例項,跟ext4、btrfs一樣,PFS上也會在捲上存放檔案系統後設資料。這些後設資料包括inode、directory entry和空閒塊等物件。同時,PFS也是一個日誌檔案系統,為了實現檔案系統的後設資料一致性,後設資料的更新會首先記錄在捲上的Journal(日誌)檔案中,然後才更新指定的後設資料。
跟傳統檔案系統不一樣的是PolarFS是個共享檔案系統即一個卷會被掛載到多個計算節點上,也就是說可能存在有多個客戶端(掛載點)對檔案系統進行讀寫和更新操作,所以PolarFS在捲上額外維護了一個Paxos檔案。每個客戶端在更新Journal檔案前,都需要使用Paxos檔案執行Disk Paxos演算法實現對Journal檔案的互斥訪問。更詳細的PolarFS後設資料更新實現,後續單獨作為一個小節。
Chunk
前面提到,每個卷內部會被劃分為多個Chunk(區),區是資料分佈的最小粒度,每個區都位於單塊SSD盤上,其目的是利於資料高可靠和高可用的管理,詳見後續章節。每個Chunk大小設定為10GB,遠大於其他類似的儲存系統,例如GFS為64MB,Linux LVM的物理區(PE)為4MB。這樣做的目的是減少捲到區對映的後設資料量大小(例如,100TB的卷只包含10K個對映項)。一方面,全域性後設資料的存放和管理會更容易;另一方面,後設資料可以全都快取在記憶體中,避免關鍵IO路徑上的額外後設資料訪問開銷。
當然,Chunk設定為10GB也有不足。當上層資料庫應用出現區域級熱點訪問時,Chunk內熱點無法進一步打散,但是由於每個儲存節點提供的Chunk數量往往遠大於節點數量(節點:Chunk在1:1000量級),PolarFS支援Chunk的線上遷移,其上服務著大量資料庫例項,因此可以將熱點Chunk分佈到不同節點上以獲得整體的負載均衡。
在PolarFS上,捲上的每個Chunk都有3個副本,分佈在不同的ChunkServer上,3個副本基於ParallelRaft分散式一致性協議來保證資料高可靠和高可用。
Block
在ChunkServer內,Chunk會被進一步劃分為163,840個Block(塊),每個塊大小為64KB。Chunk至Block的對映資訊由ChunkServer自行管理和儲存。每個Chunk除了用於存放資料庫資料的Block外,還包含一些額外Block用來實現預寫日誌(Write Ahead Log,WAL)。
需要注意的是,雖然Chunk被進一步劃分為塊,但Chunk內的各個Block在SSD盤是物理連續的。PolarFS的VLDB文章裡提到“Blocks are allocated and mapped to a chunk on demand to achieve thin provisioning”。thin provisioning就是精簡配置,是儲存上常用的技術,就是使用者建立一個100GB大小的卷,但其實在卷建立時並沒有實際分配100GB儲存空間給它,僅僅是邏輯上為其建立10個Chunk,隨著使用者資料不斷寫入,PolarFS不斷分配物理儲存空間供其使用,這樣能夠實現儲存系統按需擴容,大大節省儲存成本。
那麼為何PolarFS要引入Block這個概念呢,其中一個是跟捲上的具體檔案相關,我們知道一個檔案系統會有多個檔案,比如InnoDB資料檔案*.ibd。每個檔案大小會動態增長,檔案系統採用預分配(fallocate())為檔案提前分配更多的空間,這樣在真正寫資料的時無需進行檔案系統後設資料操作,進而優化了效能。顯然,每次給檔案分配一個Chunk,即10GB空間是不合理的,64KB或其倍數才是合適的值。上面提到了精簡配置和預分配,看起來是衝突的方法,但其實是統一的,精簡配置的粒度比預分配的粒度大,比如精簡配置了10GB,預分配了64KB。這樣對使用者使用沒有任何影響,同時還節省了儲存成本。
PolarFS元件解析
首先展示一張能夠更加清晰描述與資料流相關的各個元件作用的示意圖,並逐一對其進行解釋。
libpfs
libpfs是一個使用者空間檔案系統(即上圖User Space File System)庫,負責資料庫IO(File IO)接入。更直觀點,libpfs提供了供計算節點/PolarDB訪問底層儲存的API介面,進行檔案讀寫和後設資料更新等操作,如下圖所示:
pfs_mount()用於將指定捲上檔案系統掛載到對應的資料庫計算節點上,該操作會獲取捲上的檔案系統後設資料資訊,將其快取在計算節點上,這些後設資料資訊包括目錄樹(the directory tree),檔案對映表(the file mapping table)和塊對映表(the block mapping table)等,其中目錄樹描述了檔案目錄層級結構資訊,每個檔名對應的inode節點資訊(目錄項)。inode節點資訊就是檔案系統中唯一標識一個檔案的FileID。檔案對映表描述了該檔案都有哪些Block組成。通過上圖我們還發現了pfs_mount_growfs(),該API可以讓使用者方便得進行資料庫擴容,在對捲進行擴容後,通過呼叫該API將增加的空間對映到檔案系統層。
上圖右側的表描述了目錄樹中的某個檔案的前3個塊分別對應的是卷的第348,1500和201這幾個塊。假如資料庫操作需要回刷一個髒頁,該頁在該表所屬檔案的偏移位置128KB處,也就是說要寫該檔案偏移128KB開始的16KB資料,通過檔案對映表知道該寫操作其實寫的是卷的第201個塊。這就是lipfs傳送給PolarSwitch的請求包含的內容:volumeid,offset和len。其中offset就是201*64KB,len就是16KB。
PolarSwitch
PolarSwitch是部署在計算節點的Daemon,即上圖的Data Router&Cache模組,它負責接收libpfs傳送而來的檔案IO請求,PolarSwitch將其劃分為對應的一到多個Chunk,並將請求發往Chunk所屬的ChunkServer完成訪問。具體來說PolarSwitch根據自己快取的volumeid到Chunk的對映表,知道該檔案請求屬於那個Chunk。請求如果跨Chunk的話,會將其進一步拆分為多個塊IO請求。PolarSwitch還快取了該Chunk的三個副本分別屬於那幾個ChunkServer以及哪個ChunkServer是當前的Leader節點。PolarSwitch只將請求傳送給Leader節點。
ChunkServer
ChunkServer部署在儲存節點上,即上圖的Data Chunk Server,用於處理塊IO(Block IO)請求和節點內的儲存資源分佈。一個儲存節點可以有多個ChunkServer,每個ChunkServer繫結到一個CPU核,並管理一塊獨立的NVMe SSD盤,因此ChunkServer之間沒有資源競爭。
ChunkServer負責儲存Chunk和提供Chunk上的IO隨機訪問。每個Chunk都包括一個WAL,對Chunk的修改會先寫Log再執行修改操作,保證資料的原子性和永續性。ChunkServer使用了3D XPoint SSD和普通NVMe SSD混合型WAL buffer,Log會優先存放到更快的3DXPoint SSD中。
前面提到Chunk有3副本,這三個副本基於ParallelRaft協議,作為該Chunk Leader的ChunkServer會將塊IO請求傳送給Follow節點其他ChunkServer)上,通過ParallelRaft一致性協議來保證已提交的Chunk資料不丟失。
PolarCtrl
PolarCtrl是系統的控制平面,相應地Agent代理被部署到所有的計算和儲存節點上,PolarCtrl與各個節點的互動通過Agent進行。PolarCtrl是PolarFS叢集的控制核心,後端使用一個關聯式資料庫雲服務來管理PolarDB的後設資料。其主要職責包括:
-
監控ChunkServer的健康狀況,包括剔除出現故障的ChunkServer,維護Chunk多個副本的關係,遷移負載過高的ChunkServer上的部分Chunk等;
-
Volume建立及Chunk的佈局管理,比如Volume上的Chunk應該分配到哪些ChunkServer上;
-
Volume至Chunk的後設資料資訊維護;
-
向PolarSwitch推送元資訊快取更新,比如因為計算節點執行DDL導致捲上檔案系統後設資料更新,這些更新可通過PolarCtrl推送給PolarSwitch;
-
監控Volume和Chunk的IO效能,根據一定的規則進行遷移操作;
-
週期性地發起副本內和副本間的CRC資料校驗。
本篇主要是介紹了PolarDB資料庫及其後端共享儲存PolarFS系統的基本架構和組成模組,是最基礎的部分。下一篇重點分析PolarFS的資料IO流程,後設資料更新流程,以及PolarDB資料庫節點如何適配PolarFS這樣的共享儲存系統。
本文來自網易雲社群 ,經作者溫正湖授權釋出。
網易雲免費體驗館,0成本體驗20+款雲產品!
更多網易研發、產品、運營經驗分享請訪問網易雲社群。
相關文章:
【推薦】 3分鐘掌握一個有數小技能:收入貢獻分析
【推薦】 手把手帶你打造一個 Android 熱修復框架
【推薦】 kudu 儲存引擎簡析