上篇介紹了PolarDB數據庫及其後端共享存儲PolarFS系統的基本架構和組成模塊,是最基礎的部分。本篇重點分析PolarFS的數據IO流程,元數據更新流程,以及PolarDB數據庫節點如何適配PolarFS這樣的共享存儲系統。html
PolarFS的數據IO操做node
寫操做mysql
通常狀況下,寫操做不會涉及到捲上文件系統的元數據更新,由於在寫以前就已經經過libpfs的pfs_posix_fallocate()這個API將Block預分配給文件,這就避免在讀寫IO路徑上出現代價較高的文件系統元數據同步過程。上圖是PolarFS的寫操做流程圖,每步操做解釋以下:算法
POLARDB經過libpfs發送一個寫請求Request1,經由ring buffer發送到PolarSwitch;sql
PolarSwitch根據本地緩存的元數據,將Request1發送至對應Chunk的Leader節點(ChunkServer1);數據庫
Request1到達ChunkServer1後,節點上的RDMA NIC將Request1放到一個預分配好的內存buffer中,基於Request1構造一個請求對象,並將該對象加到請求隊列中。一個IO輪詢線程不斷輪詢這個請求隊列,一旦發現有新請求則當即開始處理;後端
IO處理線程經過異步調用將Request1經過SPDK寫到Chunk對應的WAL日誌塊上,同時將請求經過RDMA異步發向給Chunk的Follower節點(ChunkServer二、ChunkServer3)。因爲都是異步調用,因此數據傳輸是併發進行的;緩存
當Request1請求到達ChunkServer二、ChunkServer3後,一樣經過RDMA NIC將其放到預分配好的內存buffer並加入到複製隊列中;服務器
Follower節點上的IO輪詢線程被觸發,Request1經過SPDK異步地寫入該節點的Chunk副本對應的WAL日誌塊上;網絡
當Follower節點的寫請求成功後,會在回調函數中經過RDMA向Leader節點發送一個應答響應;
Leader節點收到ChunkServer二、ChunkServer3任一節點成功的應答後,即造成Raft組的majority。主節點經過SPDK將Request1寫到請求中指定的數據塊上;
隨後,Leader節點經過RDMA NIC向PolarSwitch返回請求處理結果;
PolarSwitch標記請求成功並通知上層的POLARDB。
讀請求無需這麼複雜的步驟,lipfs發起的讀請求直接經過PolarSwitch路由到數據對應Chunk的Leader節點(ChunkServer1),從其中讀取對應的數據返回便可。須要說明的是,在ChunkServer上有個子模塊叫IoScheduler,用於保證發生併發讀寫訪問時,讀操做可以讀到最新的已提交數據。
基於用戶態的網絡和IO路徑
在本地IO處理上,PolarFS基於預分配的內存buffer來處理請求,將buffer中的內容直接使用SPDK寫入WAL日誌和數據塊中。PolarFS讀寫數據基於SPDK套件直接經過DMA操做硬件設備(SSD卡)而不是操做系統內核IO協議棧,解決了內核IO協議棧慢的問題;經過輪詢的方式監聽硬件設備IO完成事件,消除了上下文切換和中斷的開銷。還能夠將IO處理線程和CPU進行一一映射,每一個IO處理線程獨佔CPU,相互之間處理不一樣的IO請求,綁定不一樣的IO設備硬件隊列,一個IO請求生命週期從頭至尾都在一個線程一顆CPU上處理,不須要鎖進行互斥。這種技術實現最大化的和高速設備進行性能交互,實現一顆CPU達每秒約20萬次IO處理的能力,而且保持線性的擴展能力,也就意味着4顆CPU能夠達到每秒80萬次IO處理的能力,在性能和經濟型上遠高於內核。
網絡也是相似的狀況。過去傳統的以太網,網卡發一個報文到另外一臺機器,中間經過一跳交換機,大概須要一百到兩百微秒。POLARDB支持ROCE以太網,經過RDMA網絡,直接將本機的內存寫入另外一臺機器的內存地址,或者從另外一臺機器的內存讀一塊數據到本機,中間的通信協議編解碼、重傳機制都由RDMA網卡來完成,不須要CPU參與,使性能得到極大提高,傳輸一個4K大小報文只須要六、7微秒的時間。
如同內核的IO協議棧跟不上高速存儲設備能力,內核的TCP/IP協議棧跟不上高速網絡設備能力,也被POLARDB的用戶態網絡協議棧代替。這樣就解決了HDFS和Ceph等目前的分佈式文件系統存在的性能差、延遲大的問題。
基於ParallelRaft的數據可靠性保證
在PolarFS中,位於不一樣ChunkServer上的3個Chunk數據副本使用改進型Raft協議ParallelRaft來保障可靠性,經過快速主從切換和majority機制確保可以容忍少部分Chunk副本離線時仍可以持續提供在線讀寫服務,即數據的高可用。
在標準的Raft協議中,raft日誌是按序被Follower節點確認,按序被Leader節點提交的。這是由於Raft協議不容許出現空洞,一條raft日誌被提交,意味着它以前的全部raft日誌都已經被提交。在數據庫系統中,對不一樣數據的併發更新是常態,也正由於這點,纔有了事務的組提交技術,但若是引入Raft協議,意味着組提交技術在PolarFS數據多副本可靠性保障這一層退化爲串行提交,對於性能會產生很大影響。經過將多個事務batch成一個raft日誌,經過在一個Raft Group的Leader和Follower間創建多個鏈接來同時處理多個raft日誌這兩種方式(batching&pipelining)可以緩解性能退化。但batch會致使額外延遲,batch也不能過大。pipelining因爲Raft協議的束縛,仍然須要保證按序確認和提交,若是出現因爲網絡等緣由致使先後pipeline上的raft日誌發送往follow或回覆leader時亂序,那麼就不可避省得出現等待。
爲了進一步優化性能,PolarFS對Raft協議進行了改進。核心思想就是解除按序確認,按序提交的束縛。將其變爲亂序確認,亂序提交和亂序應用。首先看看這樣作的可行性,假設每一個raft日誌表明一個事務,多個事務可以並行提交說明其不存在衝突,對應到存儲層每每意味着沒有修改相同的數據,好比事務T1修改File1的Block1,事務T2修改File1的Block2。顯然,先修改Block1仍是Block2對於存儲層仍是數據庫層都沒有影響。這真是可以亂序的基礎。下圖爲優化先後的性能表現:
但T1和T2都修改了同一個表的數據,致使表的統計信息發生了變化,好比T1執行後表中有10條記錄,T2執行後變爲15條(舉例而已,不必定正確)。因此,他們都須要更新存儲層的相同BlockX,該更新操做就不能亂序了。
爲了解決上述所說的問題,ParallelRaft協議引入look behind buffer(LBB)。每一個raft日誌都有個LBB,緩存了它以前的N個raft日誌所修改的LBA信息。LBA即Logical Block Address,表示該Block在Chunk中的偏移位置,從0到10GB。經過判斷不一樣的raft日誌所包含的LBA是否有重合來決定可否進行亂序/並行應用,好比上面的例子,前後修改了BlockX的raft日誌就能夠經過LBB發現,若是T2對BlockX的更新先完成了確認和提交,在應用前經過LBB發現所依賴的T1對BlockX的修改尚未應用。那麼就會進入pending隊列,直到T1對BlockX完成應用。
另外,亂序意味着日誌會有空洞。所以,Leader選舉階段額外引入了一個Merge階段,填補Leader中raft日誌的空洞,可以有效保障協議的Leader日誌的完整性。
PolarFS元數據管理與更新
PolarFS各節點元數據維護
libpfs僅維護文件塊(塊在文件中的偏移位置)到卷塊(塊在卷中的偏移位置)的映射關係,並未涉及到卷中Chunk跟ChunkServer間的關係(Chunk的物理位置信息),這樣libpfs就跟存儲層解耦,爲Chunk分配實際物理空間時無需更新libpfs層的元數據。而Chunk到ChunkServer的映射關係,也就是物理存儲空間到卷的分配行爲由PolarCtrl組件負責,PolarCtrl完成分配後會更新PolarSwitch上的緩存,確保libpfs到ChunkServer的IO路徑是正確的。
Chunk中Block的LBA到Block真實物理地址的映射表,以及每塊SSD盤的空閒塊位圖均所有緩存在ChunkServer的內存中,使得用戶數據IO訪問可以全速推動。
PolarFS元數據更新流程
前面咱們介紹過,PolarDB爲每一個數據庫實例建立了一個volume/卷,它是一個文件系統,建立時生成了對應的元數據信息。因爲PolarFS是個可多點掛載的共享訪問分佈式文件系統,須要確保一個掛載點更新的元數據可以及時同步到其餘掛載點上。好比一個節點增長/刪除了文件,或者文件的大小發生了變化,這些都須要持久化到PolarFS的元數據上並讓其餘節點感知到。下面咱們來討論PolarFS如何更新元數據並進行同步。
PolarFS的每一個卷/文件系統實例都有相應的Journal文件和與之對應的Paxos文件。Journal文件記錄了文件系統元數據的修改歷史,是該卷各個掛載點之間元數據同步的中心。Journal文件邏輯上是一個固定大小的循環buffer,PolarFS會根據水位來回收Journal。若是一個節點但願在Journal文件中追加項,其必需使用DiskPaxos算法來獲取Journal文件控制權。
正常狀況下,爲了確保文件系統元數據和數據的一致性,PolarFS上的一個卷僅設置一個計算節點進行讀寫模式掛載,其餘計算節點以只讀形式掛載文件系統,讀寫節點鎖會在元數據記錄持久化後立刻釋放鎖。可是若是該讀寫節點crash了,該鎖就不會被釋放,爲此加在Journal文件上的鎖會有過時時間,在過時後,其餘節點能夠經過執行DiskPaxos來從新競爭對Journal文件的控制權。當PolarFS的一個掛載節點開始同步其餘節點修改的元數據時,它從上次掃描的位置掃描到Journal末尾,將新entry更新到節點的本地緩存中。PolarFS同時使用push和pull方式來進行節點間的元數據同步。
下圖展現了文件系統元數據更新和同步的過程:
Node 1是讀寫掛載點,其在pfs_fallocate()調用中將卷的第201個block分配給FileID爲316的文件後,經過Paxos文件請求互斥鎖,並順利得到鎖。
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一寫多讀的典型應用擴展模式。正常狀況下一寫多讀模式沒有鎖爭用開銷,只讀實例能夠經過原子IO無鎖獲取Journal信息,從而使得PolarDB能夠提供近線性的QPS性能擴展。
數據庫如何適配PolarFS
你們可能認爲,若是讀寫實例和只讀實例共享了底層的數據和日誌,只要把只讀數據庫配置文件中的數據目錄換成讀寫實例的目錄,貌似就能夠直接工做了。可是這樣會遇到不少問題,MySQL適配PolarFS有不少細節問題須要處理,有些問題只有在真正作適配的時候還能想到,下面介紹已知存在的問題並分析數據庫層是如何解決的。
數據緩存和數據一致性
從數據庫到硬件,存在不少層緩存,對基於共享存儲的數據庫方案有影響的緩存層包括數據庫緩存,文件系統緩存。
數據庫緩存主要是InnoDB的Buffer Pool(BP),存在2個問題:
讀寫節點的數據更改會緩存在bp上,只有完成刷髒頁操做後polarfs才能感知,因此若是在刷髒以前只讀節點發起讀數據操做,讀到的數據是舊的;
就算PolarFS感知到了,只讀節點的已經在BP中的數據仍是舊的。因此須要解決不一樣節點間的緩存一致性問題。
PolarDB採用的方法是基於redolog複製的節點間數據同步。可能咱們會想到Primary節點經過網絡將redo日誌發送給ReadOnly/Replica節點,但其實並非,如今採用的方案是redo採用非ring buffer模式,每一個文件固定大小,大小達到後Rotate到新的文件,在寫模式上走Direct IO模式,確保磁盤上的redo數據是最新的,在此基礎上,Primary節點經過網絡通知其餘節點能夠讀取的redo文件及偏移位置,讓這些節點自主到共享存儲上讀取所需的redo信息,並進行回放。流程以下圖所示:
因爲StandBy節點與讀寫節點不共享底層存儲,因此須要走網絡發送redo的內容。節點在回放redo時需區分是ReadOnly節點仍是StandBy節點,對於ReadOnly節點,其僅回放對應的Page頁已在BP中的redo,未在BP中的page不會主動從共享存儲上讀取,且BP中Apply過的Page也不會回刷到共享存儲。但對於StandBy節點,須要全量回放並回刷到底層存儲上。
文件系統緩存主要是元數據緩存問題。文件系統緩存包括Page Cache,Inode/Dentry Cache等,對於Page Cache,能夠經過Direct IO繞過。但對於VFS(Virtual File System)層的Inode Cache,沒法經過Direct IO模式而需採用o_sync的訪問模式,但這樣致使性能嚴重降低,沒有實際意義。vfs層cache沒法經過direct io模式繞過是個很嚴重的問題,這就意味着讀寫節點建立的文件,只讀節點沒法感知,那麼針對這個新文件的後續IO操做,只讀節點就會報錯,若是採用內核文件系統,很差進行改造。
PolarDB經過元數據同步來解決該問題,它是個用戶態文件系統,數據的IO流程不走內核態的Page Cache,也不走VFS的Inode/Dentry Cache,徹底本身掌控。共享存儲上的文件系統元數據經過前述的更新流程實現便可。經過這種方式,解決了最基本的節點間數據同步問題。
事務的數據可見性問題
1、MySQL/InnoDB經過Undo日誌來實現事務的MVCC,因爲只讀節點跟讀寫節點屬於不一樣的mysqld進程,讀寫節點在進行Undo日誌Purge的時候並不會考慮此時在只讀節點上是否還有事務要訪問即將被刪除的Undo Page,這就會致使記錄舊版本被刪除後,只讀節點上事務讀取到的數據是錯誤的。
針對該問題,PolarDB提供兩種解決方式:
全部ReadOnly按期向Primary彙報本身的最大能刪除的Undo數據頁,Primary節點統籌安排;
當Primary節點刪除Undo數據頁時候,ReadOnly接收到日誌後,判斷即將被刪除的Page是否還在被使用,若是在使用則等待,超過一個時間後還未有結束則直接給客戶端報錯。
2、還有個問題,因爲InnoDB BP刷髒頁有多種方式,其並非嚴格按照oldest modification來的,這就會致使有些事務未提交的頁已經寫入共享存儲,只讀節點讀到該頁後須要經過Undo Page來重建可見的版本,但可能此時Undo Page還未刷盤,這就會出現只讀上事務讀取數據的另外一種錯誤。
針對該問題,PolarDB解決方法是:
限制讀寫節點刷髒頁機制,若是髒頁的redo尚未被只讀節點回放,那麼該頁不能被刷回到存儲上。這就確保只讀節點讀取到的數據,它以前的數據鏈是完整的,或者說只讀節點已經知道其以前的全部redo日誌。這樣即便該數據的記錄版本當前的事務不可見,也能夠經過undo構造出來。即便undo對應的page是舊的,能夠經過redo構造出所需的undo page。
replica須要緩存全部未刷盤的數據變動(即RedoLog),只有primary節點把髒頁刷入盤後,replica緩存的日誌才能被釋放。這是由於,若是數據未刷盤,那麼只讀讀到的數據就多是舊的,須要經過redo來重建出來,參考第一點。另外,雖然buffer pool中可能已經緩存了未刷盤的page的數據,但該page可能會被LRU替換出去,當其再次載入因此只讀節點必須緩存這些redo。
DDL問題
若是讀寫節點把一個表刪了,反映到存儲上就是把文件刪了。對於mysqld進程來講,它會確保刪除期間和刪除後再也不有事務訪問該表。可是在只讀節點上,可能此時還有事務在訪問,PolarFS在完成文件系統元數據同步後,就會致使只讀節點的事務訪問存儲出錯。
PolarDB目前的解決辦法是:若是主庫對一個表進行了表結構變動操做(須要拷表),在操做返回成功前,必須通知到全部的ReadOnly節點(有一個最大的超時時間),告訴他們,這個表已經被刪除了,後續的請求都失敗。固然這種強同步操做會給性能帶來極大的影響,有進一步的優化的空間。
Change Buffer問題
Change Buffer本質上是爲了減小二級索引帶來的IO開銷而產生的一種特殊緩存機制。當對應的二級索引頁沒有被讀入內存時,暫時緩存起來,當數據頁後續被讀進內存時,再進行應用,這個特性也帶來的一些問題,該問題僅存在於StandBy中。例如Primary節點可能由於數據頁還未讀入內存,相應的操做還緩存在Change Buffer中,可是StandBy節點則由於不一樣的查詢請求致使這個數據頁已經讀入內存,能夠直接將二級索引修改合併到數據頁上,無需通過Change Buffer了。但因爲複製的是Primary節點的redo,且須要保證StandBy和Primary在存儲層的一致性,因此StandBy節點仍是會有Change Buffer的數據頁和其對應的redo日誌,若是該髒頁回刷到存儲上,就會致使數據不一致。
爲了解決這個問題,PolarDB引入shadow page的概念,把未修改的數據頁保存到其中,將cChange Buffer記錄合併到原來的數據頁上,同時關閉該Mtr的redo,這樣修改後的Page就不會放到Flush List上。也就是StandBy實例的存儲層數據跟Primary節點保持一致。
性能測試
性能評估不是本文重點,官方的性能結果也不必定是靠譜的,只有真實測試過了纔算數。在此僅簡單列舉阿里雲本身的性能測試結果,權當一個參考。
PolarFS性能
不一樣塊大小的IO延遲
4KB大小的不一樣請求類型
PolarDB總體性能
使用不一樣底層存儲時性能表現
對外展現的性能表現
與Aurora簡單對比
阿里雲的PolarDB和AWS Aurora雖然同爲基於MySQL和共享存儲的Cloud-Native Database(雲原生數據庫)方案,不少原理是相同的,包括基於redo的物理複製和計算節點間狀態同步。但在實現上也存在很大的不一樣,Aurora在存儲層採用日誌即數據的機制,計算節點無需再將髒頁寫入到存儲節點,大大減小了網絡IO量,但這樣的機制須要對InnoDB存儲引擎層作很大的修改,難度極大。而PolarDB基本上聽從了原有的MySQL IO路徑,經過優化網絡和IO路徑來提升網絡和IO能力,相對來講在數據庫層面並未有框架性的改動,相對容易些。我的認爲Aurora在數據庫技術創新上更勝一籌,但PolarDB在數據庫系統級架構優化上作得更好,以儘量小的代價得到了足夠好的收益。
另附PolarFS的架構師曹偉在知乎上對PolarDB和Aurora所作的對比:
在設計方法上,阿里雲的PolarDB和Aurora走了不同的路,歸根結底是咱們的出發點不一樣。AWS的RDS一開始就是架設在它的虛擬機產品EC2之上的,使用的存儲是雲盤EBS。EC2和EBS之間經過網絡通信,所以AWS的團隊認爲「網絡成爲數據庫的瓶頸」,在Aurora的論文中,他們開篇就提出「Instead, the bottleneck moves to the network between the database tier requesting I/Os and the storage tier that performs these I/Os.」 Aurora設計於12到13年之際,當時網絡主流是萬兆網絡,確實容易成爲瓶頸。而PolarDB是從15年開始研發的,咱們見證了IDC從萬兆到25Gb RDMA網絡的飛躍。所以咱們很是大膽的判斷,將來幾年主機經過高速網絡互聯,其傳輸速率會和本地PCIe總線存儲設備帶寬打平,網絡不管在延遲仍是帶寬上都會接近總線,所以再也不成爲高性能服務器的瓶頸。而偏偏是軟件,過去基於內核提供的syscall開發的軟件代碼,纔是拖慢系統的一環。Bottleneck resides in the software.
在架構上Aurora和PolarDB各有特點。我認爲PolarDB的架構和技術更勝一籌。
1)現代雲計算機型的演進和分化,計算機型向高主頻,多CPU,大內存的方向演進;存儲機型向高密度,低功耗方向發展。機型的分化能夠大大提升機器資源的使用率,下降TCO。
所以PolarStore中大量採用OS-bypass和zero-copy的技術來節約CPU,下降處理單位I/O吞吐須要消耗的CPU資源,確保存儲節點處理I/O請求的效率。而Aurora的存儲節點須要大量CPU作redolog到innodb page的轉換,存儲節點的效率遠不如PolarStore。
2)Aurora架構的最大亮點是,存儲節點具備將redolog轉換爲innodb page的能力,這個改進看着很吸引眼球,事實上這個優化對關係數據庫的性能提高頗有限,性能瓶頸真的不在這裏:),反而會拖慢關鍵路徑redolog落地的性能。btw,在PolarDB架構下,redolog離線轉換爲innodb page的能力不難實現,但咱們目前不認爲這是高優先級要作的。
3)Aurora的存儲多副本是經過quorum機制來實現的,Aurora是六副本,也就是說,須要計算節點向六個存儲節點分別寫六次,這裏其實計算節點的網絡開銷又上去了,並且是發生在寫redolog這種關鍵路徑上。而PolarDB是採用基於RDMA實現的ParallelRaft技術來複制數據,計算節點只要寫一次I/O請求到PolarStore的Leader節點,由Leader節點保證quorum寫入其餘節點,至關於多副本replication被offload到存儲節點上。
此外,在最終一致性上Aurora是用gossip協議來兜底的,在完備程度上沒有PolarDB使用的ParallelRaft算法有保證。
4)Aurora的改動手術切口太大,使得它很難後面持續跟進社區的新版本。這也是AWS幾個數據庫產品線的通病,例如Redshift,如何吸取PostgrelSQL 10的變動是他們的開發團隊很頭疼的問題。對新版本作到與時俱進是雲數據庫的一個樸素需求。怎麼設計這個刀口,達到effect和cost之間的平衡,是對架構師的考驗。
總得來講,PolarDB將數據庫拆分爲計算節點與存儲節點2個獨立的部分,計算節點在已有的MySQL數據庫基礎上進行修改,而存儲節點基於全新的PolarFS共享存儲。PolarDB經過計算和存儲分離的方式實現提供了即時生效的可擴展能力和運維能力,同時採用RDMA和SPDK等最新的硬件來優化傳統的過期的網絡和IO協議棧,極大提高了數據庫性能,基本上解決了使用MySQL是會遇到的各類問題,除此以外本文並未展開介紹PolarDB的ParallelRaft,其依託上層數據庫邏輯實現IO亂序提交,大大提升多個Chunk數據副本達成一致性的性能。以上這些創新和優化,都成爲了將來數據庫的發展方向。
參數資料:
本文來自網易雲社區 ,經做者溫正湖受權發佈。
網易雲免費體驗館,0成本體驗20+款雲產品!
更多網易研發、產品、運營經驗分享請訪問網易雲社區。
相關文章:
【推薦】 從加班論客戶端開發中的建模
【推薦】 GDB抓蟲之旅(上篇)
【推薦】 談談驗證碼的工做原理