摘要:本文經過介紹GaussDB(for MySQL)讀寫路徑,分析其可用性。
數據持久性和服務可用性是數據庫服務的關鍵特徵。前端
在實踐中,一般認爲擁有 3 份數據副本,就足以保證持久性。算法
可是 3 份副本,對於可用性的要求是不夠的。維護 3 份一致的副本意味着,這些副本必須同時在線,系統才能保證可用。當數據庫跨多個節點分片時,某些節點不可用的機率會隨着節點數量的增長而呈指數增加。數據庫
在 GaussDB(for MySQL) 中,咱們針對日誌和數據採用不一樣副本策略,並採用一種新穎的恢復算法,來解決可用性的問題。segmentfault
下面首先介紹寫路徑,而後介紹讀路徑,最後分析理論上的可用性估計,並與其它副本策略進行比較。api
寫路徑如上圖所示,下面對每個步驟進行說明。性能
1)用戶事務致使對數據庫頁面的更改,從而生成描述更改的日誌記錄(redo log,下面簡稱 redo)。spa
2)將 redo 寫入到 Log Stores。寫入 3 份副本,而且採用強一致性,即 3 份均寫入成功纔算成功。日誌
3)將事務標記爲已提交(committed)。blog
只要集羣中有三個或以上的 Log Stores 可用,該數據庫就能夠進行寫操做(由於寫入只須要選擇可用的節點便可,並不規定必定要寫入某個節點)。對於成千上萬個節點的羣集,這實際上意味着 100% 的寫入可用性。接口
4)redo 寫入 Log Stores 以後,會將此 redo 放入到 SAL 的 write buffer 中,以後將此 buffer 寫入到管理對應 slice 的 Page Store 上。
5)當任何一個 Page Store 副本返回成功,此寫入成功,SAL 的 write buffer 被釋放。
6)不一樣的 Page Store 副本之間使用 gossip 協議檢測和修復缺失的日誌。
數據庫運行過程當中,會源源不斷地產生 redo 日誌。若是不將不須要的 redo 刪除,能夠預見,最終確定會耗盡磁盤空間。在成功將 redo 寫入全部 Slice 副本,而且全部數據庫的讀副本(read replica)均可以看到該記錄以後,就能夠將該日誌從 Log Store 中刪除。獨立地跟蹤每條 redo 的持久性很費資源,所以咱們選擇基於 LSN 來跟蹤持久性。
對於 Page Store 的每一個 slice,都有一個 persistent LSN,它的含義是 slice 接收到的全部日誌記錄中,保證連續(沒有空洞)的最大 LSN。(譬如某個 slice 接收到 LSN 爲 1 的 redo log 後,persistent LSN 變爲 1,此時若是接收到 LSN 爲 3 的 redo log,persistent LSN 依然爲 1。以後若是接收到 LSN 爲 2 的 redo log,即補齊了空洞以後, persistent LSN 變爲 3)。
7)SAL 能夠經過按期調用 api 或者在讀寫接口中獲取每一個 slice 的 persistent LSN(在恢復中也會使用)。
8)SAL 也會跟蹤每一個 PLog 的 LSN 範圍。若是 PLog 中的全部 redo 的 LSN 都小於數據庫 persistent LSN(3 副本中最小 persistent LSN),該 PLog 可被刪除。
經過上面的機制,咱們可以保證每條 redo 都至少會有三個節點上存在副本(一開始在 Plog Store 節點上有 3 副本,保證在 Page Store 節點上有 3 副本以後,將 Plog Store 節點上的副本刪除,以回收磁盤資源)。
數據庫前端以 page 粒度讀取數據。
讀取或者修改數據時,相應的 page 必須在 buffer pool 中。當 buffer pool 已滿,咱們又須要引入一個 page 時,必須將某些頁面從 buffer pool 中淘汰。
咱們對淘汰算法進行了修改,保證只有當全部相關 redo 日誌都寫入至少 1 個 Page Store 副本後,髒頁才能被淘汰。所以,在最新的 redo 記錄到達 Page Store 以前,保證相應的頁面可從 buffer pool 中得到。 以後,能夠從 Page Store 中讀取頁面。
對於每個 slice,SAL 保存最新 redo log 的 LSN。主節點讀取 page 時,讀請求首先到達 SAL,SAL 會使用上述 LSN 下發讀請求。讀請求會被路由到時延最低的 Page Store。若是被選擇的 Page Store 不可用或者尚未收到提供 LSN 以前的全部 redo,會返回錯誤。以後 SAL 會嘗試下一個 Page Store,遍歷全部副本,直到讀請求能夠被正確響應。
目前業界最普遍使用的強一致性複製技術基於 quorum replication。若是每份數據在 N 個節點上存在副本,每一個讀取操做必須從NR個節點接收響應,並寫入NW個節點。
爲了保證強一致性,必須知足 NR + NW > N 。業界許多系統使用 quorum replication 的不一樣組合方式。 例如,
1)RAID1 磁盤陣列中一般使用 N = 3,NR = 1,NW = 3;
2)PolarDB 中,N = 3,NR = 2,NW = 2;
3)Aurora 中,N = 6,NR = 3,NW = 4。
下面的分析中,僅考慮節點單獨出現不可用的場景(不考慮譬如由於斷點致使全部節點不可用的場景)。
假設 1 個節點不可用的機率爲 x,則當 N - NW + 1 到 N 個節點同時不可用時,寫請求會失敗。 即一個寫請求失敗的機率可用以下公式計算:
同理,一個讀請求失敗的機率計算公式以下:
在前面的寫路徑一節中已經提到,GaussDB(for MySQL) 的寫 redo,不須要寫到特定的 Log Store 上,因此公式 (1) 並不適用。對於寫請求,只有當全部 PLog Store 都不可用時,纔會失敗。若是集羣中 Log Store 足夠多,這個機率幾乎接近於 0。
對於讀,每一個 Page Store 節點均可以基於其 persistent LSN 決定是否能夠爲讀提供服務。若是不能,它將返回錯誤,告訴 SAL 嘗試另外一個節點。在極少數狀況下,因爲級聯故障,沒有節點能夠提供讀服務(並不是節點不可用),SAL 會識別這種狀況並使用 Log Store 來修復數據。在這種狀況下,性能可能降低,可是存儲層仍然可用。
SAL 沒法恢復的惟一狀況是,包含 Slice 副本的全部 Page Store 都不可用,這樣的機率是 x^3。
下表對比了 GaussDB(for MySQL) 和幾種典型 quorum replication 場景的可用性:
1)對於寫,GaussDB(for MySQL) 老是可用的,優於 quorum replication 方案;
2)對於讀,除了 x = 0.01 且 quorum 的節點個數爲 6 的狀況,GaussDB(for MySQL) 老是能提供比 quorum replication 相同或更好的的可用性。而且在上面的場景下,提供的可用性已經足夠高,與 quorum replication 相差並不遠。