1. 程式人生 > >分散式資料庫選型——資料水平拆分方案

分散式資料庫選型——資料水平拆分方案

概述

 

水平拆分的概念隨著分散式資料庫的推廣已為大部分人熟知。分庫分表、異構索引、小表廣播、這些功能幾乎是產品功能需求標配。然而有些客戶使用分散式資料庫後的體驗不盡如意。本文嘗試從資料的角度總結分散式資料的複製(replication)和分割槽(partition)技術原理和方案,其中分割槽也有稱為分片(sharding),希望能引起讀者一些思考,在分散式資料庫選型中能注意這些細節的區別,選擇適合業務的資料水平拆分方案。

 

分散式資料庫架構

 

分散式資料庫以叢集形式存在,有多個節點。叢集架構有共享磁碟架構(shared-disk)和無共享架構(shared-nothing)。後者有時也稱為水平擴充套件(horizontal scale)或向外擴充套件(scale out),本文主要總結無共享架構方案。

無共享架構的各個節點之間的通訊都是軟體層面使用網路實現,不同產品在架構不同導致這個細節也不同。有些架構是計算與儲存分離。計算節點特點是無狀態(即資料不要求持久化),通過叢集方式管理,可以水平擴充套件;儲存節點有資料,使用複製和分割槽技術,節點間任務集中排程或者獨立互動。瞭解這個架構細節都可用性分析會更加具體。具體分散式資料庫架構有哪些請參考《一些關係資料庫的架構總結》。

這裡節點的實際體現形式可以是一個機器,也可以是機器上的一個例項。比如說有些資料庫支援單機安裝多個例項,如MySQL。每個節點具備一定的資源和能力。資源指的是CPU、記憶體和磁碟,能力是提供資料讀寫和儲存能力。分散式資料庫需要把多個節點的能力聚集到一起集中管理,只是不同分散式資料庫產品對資源的管理能力各有特點。

在分散式資料庫裡,資料隨處可見,這是最容易讓人混淆的地方。因為資料經過複製和分割槽後會有兩種存在形式:副本(replica)和分割槽(partition)。

 

資料的複製(replication)

 

複製(replication)指在幾個不同的節點上儲存資料的相同副本(replica)。複製提供了冗餘的能力。其作用一是提供高可用能力:如果一個節點不可用,剩餘的節點可以快速提供資料服務。作用二是提供讀寫分離能力。常見的有兩副本和三副本架構。

多個副本內容相同,角色會有區分。常見的是一個副本是Leader角色(有的也稱主副本),預設提供讀寫服務;其他副本是Follower角色(有的也稱備副本),預設不提供服務。這種架構也稱為基於單Leader的(Single Leader-based)。還有其他架構是多Leader的,每個Leader都有資料要複製到其他Leader或Follower,這種架構會有個明顯的問題就是資料衝突處理。如果產品層面不處理,使用者直接使用風險會很高。

後面討論的是前者:基於單Leader副本架構。

 

多副本之間資料同步不是依賴業務多寫,而是採用副本間複製事務日誌(Redo)技術。複製的方式有同步複製和非同步複製。使用同步複製方式,備副本要收到Redo並落盤主副本才能提交,也叫強同步;使用非同步複製方式,Follower副本相對Leader副本內容會有延時,具體延時多少取決於Leader副本上事務量、網路傳輸速度、Follower副本所在節點的負載和能力。強同步的缺點時主副本寫效能會下降,同時如果備副本不可用主副本也不能提供服務(變相的解決方案是複製方式降級為非同步複製)。

傳統關係型資料庫還有一種用法一主兩備架構,使用同步複製,只要任何一個備副本收到Redo,主副本的事務就可以提交。這個方案優點是保障了資料在多個副本中存在,高可用時有候選副本,也不用擔心掛掉一個備副本會影響主副本。它的缺點是不能自動知道哪個候選副本擁有主副本最新最全的資料,也不強制要求兩個備副本都要擁有全部資料。

還有一類三副本架構在複製時使用的是Paxos協議,三副本會就Redo落盤事件進行投票,有兩個副本成功了Leader副本的事務即可提交。這個表面上跟上面傳統一主兩備的三副本效果一樣,實際上還是有區別的。區別一是使用Paxos協議時,如果Leader副本自身投票慢了,兩個Follower副本投票成功,Leader副本的事務也是能提交的;區別二是第三個副本最終也必須寫Redo成功,否則其狀態就是異常,產品自身可以發現並自動修復(如重新建立一個副本);區別三是使用Paxos協議時,在Leader副本不可用時還可以自動選出新的Leader副本並且擁有老Leader副本的最新資料。這裡其實說的是高可用機制。同樣,這裡對使用者而言也不知道哪個Follower副本擁有最新最全的資料,如果訪問Follower副本(讀寫分離),也可能發現數據有延時。

 

大部分資料庫做副本複製使用的是Redo,也稱為物理同步。在應用Redo的時候直接是資料塊變更。使用物理同步機制的備副本是不提供寫服務,不能修改。還有一類複製使用的是Binlog,也稱為邏輯同步。Binlog裡只包含已提交的事務,並且在應用的時候是通過執行SQL。使用邏輯同步的備副本通常也可能是主副本,可以修改(如MySQL的雙向複製架構Master-Master)。如果目標端資料不對,應用SQL會失敗,這個複製就會中斷需要人介入處理。這也進一步加深了主備副本不一致的概率。

 

關於副本角色的粒度,有多種實現方案。

傳統關係資料庫主備架構,主副本或備副本的粒度就是例項。對於主例項(Primary)而言,裡面所有資料庫(或SCHEMA)的所有表的角色都是主;備例項(Standby)裡資料則都是備副本。如果發生高可用切換,業務會中斷幾十秒或幾分鐘然後恢復(需要人工處理或自動化指令碼處理)。

還有一種粒度是到表。即一個節點內有些表是Leader副本,有些表是Follower副本,這樣這個節點就不能簡單的說是主節點(例項)或備節點(例項)。這個副本角色細節業務也是可以獲取的,如果發生高可用切換,業務會中斷十幾秒然後恢復。

還有一種粒度是儲存級別的定長塊。即一個節點的儲存裡,部分資料塊是Leader副本,部分資料塊是Follower副本。這種對業務就完全透明,業務基本不感知高可用切換。

 

資料的分割槽(partition)

 

上面總結的是資料的複製(冗餘,多副本),對於非常大的資料集(表)或者非常高的訪問量(QPS),僅僅靠複製是不夠的,還需要對資料進行分割槽(partition),也稱為分片(sharding)。

分割槽粒度

首先這裡的分割槽(partition)是一種抽象概念,在不同資料庫產品裡這個體現是不一樣的。如在MongoDB, Elasticsearch中體現為分片(shard),在HBase中體現為區域塊(Region),Bigtable中體現為表塊(tablet),ORACLE中體現為分割槽(partition),Couchbase中體現為虛擬桶(vBucket)。可見不同的資料庫產品資料分割槽的粒度不同。在分散式關係資料庫中介軟體中,分片的粒度是分表(物理表);在真正的分散式關係資料庫裡,分片的粒度有分割槽(partition,同ORACLE)或者區域塊(Region)。

 

分割槽粒度對業務研發的使用體驗影響很大。

比如說中介軟體常用分庫分表方案,使用時對開發和運維會有一些要求。如建很多同構的表並後期維護、要求SQL帶上拆分鍵,還有一些功能限制(如跨庫JOIN問題)、底層儲存節點用的資料庫自身高可用和多副本的資料一致問題等等。不同的中介軟體產品能力上也有區別,網際網路大廠的產品由於內部場景培育很久,做的相對成熟一些。

體驗最好的分割槽粒度就是儲存級別的Region,業務研發完全不用關心分片細節,也無法干預分片細節。當有些場景追求效能需要干預資料分佈特點時就不好處理。

界入這兩種策略之間的就是分割槽。物理上業務只要建立一個分割槽表,根據業務特點指定分割槽策略(包含分割槽列、拆分演算法、分割槽數目等)。

 

資料複製是為了冗餘和高可用,資料分割槽主要是為了可擴充套件性。不管使用哪種分割槽方案,業務的每條資料(記錄)屬於且僅屬於一個分割槽(或分片sharding),同一個分割槽(分片)只會存在於一個節點。前面說了每個節點代表了一定的資源和能力。當複製和分割槽(分片)一起使用的時候,注意區分你看到的資料。

分割槽策略

分割槽的目標是將大量資料和訪問請求均勻分佈在多個節點上。如果每個節點均勻承擔資料和請求,那麼理論上10個節點就應該能承擔10倍於單節點的資料量和訪問量。這個理論是忽略了複製產生的Follower副本的存在。Follower副本的空間和記憶體是不可能跟其他Leader副本共享的,但是計算能力(CPU)是可以的。當所有節點都提供服務的時候(多活),是計算資源最大利用。

然而如果分割槽是不均勻的,一些分割槽的資料量或者請求量會相對比較高,出現數據偏斜(skew),這個可能導致節點資源利用率和負載也不均衡。偏斜集中的資料我們又稱為熱點資料。避免熱點資料的直接方法就是資料儲存時隨機分配(沒有規則)給節點,缺點是讀取的時候不知道去哪個分割槽找該記錄,只有掃描所有分割槽了,所以這個方法意義不大。實際常用的分割槽策略都是有一定的規則。

 

這個規則可以是業務規則,也可以不是。

 

業務規則的分割槽首先是選取一個或一組列作為分割槽鍵,然後選取拆分方法。比如說根據鍵的範圍(Range)分割槽,分割槽數量和邊界時確定的(後期還可以新增分割槽)。好處時針對分割槽鍵的範圍掃描效能會比較好。分散式資料庫中介軟體的分庫分表、分割槽表的分割槽都支援RANGE 拆分函式。各個產品拆分細節上面會有一些創新。Range分割槽的缺點是某些特定的訪問模式會導致熱點。比如說根據時間列做RANGE分割槽,業務寫入和讀寫資料集中在最近的時間,就可能導致各個分割槽負載不均衡。這只是一個缺點,業務層面還要考慮這樣做的好處。比如說刪除歷史分割槽比較快。

 

還有種拆分方法是雜湊(HASH)分割槽,分割槽數量和邊界是確定的(後期可以做分割槽分裂)。這時各個資料的分佈是否均衡就取決於各個產品實現機制。大部分做法是使用一個雜湊(HASH)函式對Key計算一個值,然後針分段儲存。

有的產品會使用這個HASH值對分割槽數取模,這個方法可能引起分割槽資料分佈不均勻(若MySQL的Key分割槽)。此外如果要調整分割槽數,則需要移動所有資料。ORACLE的HASH分割槽時會先選取最接近分割槽數的一個2的冪值,對於分割槽數大於這個值的分割槽,會從前面分割槽裡調過來。所以ORACLE 建議HASH分割槽數為2的冪。M有SQL建議Key分割槽數為奇數時資料分佈最均勻。

此外在現有分割槽下還可以再做一次分割槽,分割槽鍵和分割槽方法都可以不一樣。通常稱為兩級分割槽。比如說分庫分表時,分庫和分表策略不一樣就是兩級分割槽;分割槽表也支援兩級分割槽。

 

有業務規則的分割槽方案的特點就是使用上。SQL如果要效能好建議帶上分割槽鍵,這樣分散式資料庫才可以直接定位到所訪問資料所在的分片;否則,資料庫就要掃描所有分割槽去查詢資料。通常分割槽鍵只能選取一個或一組業務欄位,代表的是一個業務維度,那麼另外一種業務維度的SQL請求效能就會不好。個別分散式資料庫產品在HASH 方法上支援兩種維度的分割槽列,其前提是在業務構造資料時讓這兩個列有著內部一致的分割槽邏輯。

詳情可以參考《說說分庫分表的一個最佳實踐》。

 

另外一種分割槽策略就是無業務規則的,在儲存級別按塊的大小切分為多個定長塊(Region)。這個分割槽對業務而言就是透明的,所以使用體驗上會相對好一些。

 

不過,分散式資料庫裡的資料分割槽除了儲存資料還要提供讀寫服務。業務讀寫資料的SQL本身是帶業務邏輯的,如果一次SQL請求訪問的資料分散到多個分割槽,而這些分割槽又散落在不同的節點上,不可避免的會發生跨節點的請求。如果是多表連線,這種情形更容易出現。如果這個業務請求有事務,那這就產生了分散式事務。分散式事務解決方案有兩種,強一致的兩階段提交(XA)方案和最終一致的TCC方案。詳情請參考《說說資料庫事務和開發(下)—— 分散式事務》。

這裡主要提示跨節點的請求帶來的效能衰減。當然,硬體方面萬兆網絡卡加RDMA技術下網路延時已經縮小很多,但是當分散式資料庫的請求量(QPS)非常高時,或者分散式資料庫是多機房部署(比如說兩地三中心)時,跨機房的網路延時還是不可忽視,跨節點的請求帶來的效能衰減也會很明顯。所以有業務規則的分割槽策略可以提供策略給業務控制自己的資料分割槽分佈特點,非常適合做異地多活和單元化類業務。此外還有個常用的規避跨節點請求讀的方法就是小表廣播,即將個別沒有分割槽的表的資料複製到其他分割槽所在的節點,這樣相關業務資料分割槽的JOIN就是在本地節點內部完成。這裡就看複製使用的是物理同步還是邏輯同步,以及同步的延時是否滿足業務需求。

分割槽數量

關於分割槽數量也需要評估。如果是無規則的分割槽策略,由於每個分割槽(分片)是定長塊,那麼分割槽數量就由總資料大小除以定長塊大小,對業務也是透明的。這裡總結的是有業務規則的分割槽的數量。

使用分割槽的目的是為了擴充套件性,具體就是能將不同分割槽分散多多個節點上,發揮多個節點的資源和能力。所以分割槽數一定要大於可用的資源節點數,為了考慮到將來分散式資料庫可能會擴容,分割槽數應該是數倍於當前規劃的節點數。這是一個總的指導思想。由於不同的分散式資料庫其節點的表示方法不一樣,實施的時候會略有不同。

比如說在分散式資料庫中介軟體架構裡,資料儲存的節點是例項,資料分割槽的粒度是分表(物理表),中間還有一層分庫的維度。分散式資料庫例項:總物理例項數:總物理分庫數:總物理分表數=1:M:N:X  。X是分割槽的數量,N 是總分庫數。X 是固定的,如果要調整分割槽數,成本非常高,所以一般都是提前規劃好。N 是總分庫數,是2的冪。 M 是例項的數量,也建議是2的冪,決定了最大能用多少節點的資源。 N/M 的結果決定了未來能擴容的倍數。分散式資料庫中介軟體由於資料分割槽落在具體的節點後就不能自由移動,其擴容方式多是對每個例項一分為二,最好的途徑就是利用資料庫(MySQL)自身的主從複製搭建新的備例項擴容節點數。

此外分割槽數還要考慮到單個分割槽的容量和請求量是否滿足需求。即分割槽是否到位。這個也是需要業務評估的。在使用分割槽表的分割槽方案的分散式資料庫裡,分割槽數也是結合上面兩點考慮的。

當然分割槽數太大了,可能會增加分佈資料庫內部管理成本。分割槽數量跟分割槽粒度恰好是相反關係,二者都需要取一個合適的值。

分割槽數量一旦確定後,調整的成本非常高,通常會引起資料重分佈。有些產品可以針對特定型別的分割槽做分割槽分裂。如RANGE分割槽可以分裂為兩個RANGE, HASH分割槽也可以一分為二。只要這個分割槽分裂的邏輯是資料庫內部邏輯實現,保證資料不丟,且對業務透明的,那麼風險就很低值得考慮。

分割槽負載均衡

隨著時間的推移,資料庫一直在發生各種變化。如QPS增加,資料集更大,或者新增/替換機器等。無論哪種都需要將部分資料分割槽和相應的請求從一個節點移動到另外一個節點,這個過程稱為分割槽的再平衡(rebalance)。業務對再平衡的要求就是平衡過程中對業務當前讀寫影響要可控,資料讀寫服務不能中斷。還有一點就是為了再平衡應儘可能少的遷移資料。

前面兩個要求都不難滿足,最後一個要求就考驗各個分割槽方案的靈活度了。當分割槽粒度是儲存級別的Region時,分割槽遷移的粒度就是Region,這個對業務也是透明的;分割槽粒度是分割槽時,這個取決於各個產品對節點資源管理的設計。比如說有的設計可以做到只需要遷移分割槽就可以調整各個節點的資源利用率和負載;如果分割槽方案是分庫分表,此時分割槽粒度是分表。但是資料遷移的單位通常還是例項,利用資料庫原生複製能力搭建新的級聯備例項,然後新老例項分別刪除一半分庫資料。這裡就遷移了不必要的很多資料分割槽。

分割槽訪問路由

現在資料分割槽方案已經確定,業務資料分佈在多個節點上。業務應用訪問資料庫如何連線呢?再分割槽負載均衡發生後部分分割槽節點發生變化,業務應用是否要修改連線?這個就是分割槽訪問路由問題,是分散式資料庫的基本能力。理論上分割槽訪問路由有三種方案。一是每個節點都可以進行路由轉發(如果請求的資料不在該節點上,該節點可以轉發應用請求到正確的節點上);二是設定一箇中心模組負責接受請求並轉發到正確的節點上;三是應用自己獲取分散式資料庫所有分割槽的節點資訊,直接連線對應的節點,不需要其他元件提供路由功能。

大部分分散式資料庫架構,選擇了第二種方案,有一個負責分割槽路由訪問的模組。有些產品同時支援這三種方案。 針對分割槽路由問題情況還可能更復雜。如一個事務有多條SQL時該路由到哪個節點。此外就是如果負責路由的節點故障,或者分割槽所在節點故障,這個路由不可用或者失效時會如何恢復路由服務。

 

SQL線性擴充套件能力

 

當資料分割槽方案確定、分割槽路由問題也解決了後,運維和業務架構為業務的搭建了一個好的分散式資料庫環境。很多業務誤以為用上分散式資料庫後,就一定會很好,或者擴容後業務的效能也能相應的提升。實際使用經驗並不一定如此。還是前面那句話使用分割槽方案主要是獲得擴充套件性,其關鍵就是分割槽分佈在更多的節點上,能利用上更多節點的能力。

 

但這個並不是指讓單個SQL利用更多節點的能力。舉個例子在OLAP業務裡,一條SQL 如果能讓很多節點同時提供服務,其效能當然是最好的。不過這樣的SQL的併發不能太多,否則很容易讓所有節點都很忙。即使分散式資料庫擴容了節點將分割槽進一步打散,由於業務的訪問壓力和資料量也會增加很多,很可能依然是每個SQL同時讓所有節點為其服務,這個SQL的吞吐量並不會隨著這個節點數量的擴容而得到相應的提升。

分散式資料庫的優勢在於對於空間問題和請求訪問問題分而治之。針對每個分割槽的訪問,由該分割槽所在的節點響應即可。即使該SQL 併發很高,由於訪問的是不同的分割槽,分別由不同的節點提供服務。每個節點自身也有一定能力滿足一定的QPS,所有節點集中在一起就能提供更大的QPS。這個時候如果擴容節點數量,該SQL總的QPS也能獲得相應的提升。這是分散式資料庫裡最好的情形。

第二個例子根據PK 訪問表,並且PK還是主鍵等。通常我們都建議分庫分表或者分割槽時,業務SQL儘量帶上拆分鍵就是這個道理。但是如果業務場景確實無法帶上拆分鍵,除了強制掃描所有分割槽外,還有個解決方案就是全域性索引表。全域性索引是獨立於資料分割槽儲存的,全域性索引可以避免掃描不必要的分割槽,負面作用就是業務分割槽的寫操作很可能帶來分散式事務。

 

以上兩個例子就是分散式資料庫裡SQL的先行擴充套件能力的兩個極端。前一個場景SQL沒有擴充套件能力,後一個SQL的擴充套件能力幾乎是百分百。大部分SQL的先行擴充套件能力就界於兩者之間。比如說SQL裡是分割槽列的IN條件。這個SQL的先行擴充套件能力取決於這個INLIST的資料特點。如果恰好每次都是命中同一個分割槽,那跟分割槽列等值訪問效果一樣好;如果INLIST的資料命中絕大部分分割槽,那就接近OLAP 場景的那個SQL。有些業務增長後,這個INLIST的長度基本不變。比如說人口業務,雖然總人口的激增,但每個家庭的子女數量大部分在1-2。這是一類特點,訪問這個子女資料的SQL的先行擴充套件能力會很好。另外一個例子就是買家訂單查詢業務。10年前每個買家一段時間的訂單數量可能就幾個,如今每個買家一段時間的平均訂單數量可能在幾十或幾百。

比INLIST 更復雜的邏輯就是表連線。 表連線時的條件是否是分割槽列,每個具體的連線值會相應命中多少個分割槽,是否有分散式執行計劃等等。都會影響這個SQL的線性擴充套件能力。

 

對於無業務規則的分割槽方案,雖然分割槽對業務是透明的,但不可否認的是資料分割槽是分佈在不同的節點上,只要業務讀寫這些資料,資料分佈特點就會影響到SQL的效能。對於業務而言,該如何選擇?如果業務通過分割槽策略控制資料分割槽分佈特點,能夠獲得更高的效能,業務是否願意選擇會影響分散式資料庫的選型。而不同分割槽方案在運維方面的特點也不一樣,是影響選型的另外一個因素,這裡就不細說。

 

螞蟻的分散式資料庫最佳實踐

 

螞蟻金服的業務規模非常大,業務模組劃分非常細。以網商銀行非常核心的交易、賬務和支付模組舉例,每個業務模組的資料經分散式資料庫中介軟體(SOFA的DBP)拆分為多個OceanBase租戶(例項)下百庫百表,每個表同時變更為OceanBase自身的分割槽表,分為100個分割槽。總共有多個OceanBase叢集,每個叢集橫跨杭州上海和深圳五機房,並同時提供服務。這裡的資料總共分為10000個分割槽,不同分庫下的資料分割槽的Leader副本分別位於不同的機房。不同分表之間可以分別進行結構變更(灰度釋出能力),不同OceanBase租戶甚至叢集之間是物理隔離的,這是金融核心業務拆分有使用分庫分表的第一個原因。

業務層面資料是按使用者維度拆分的,不同的使用者訪問不同的機房的應用和資料。業務層面的流量分配規則和資料分割槽Leader副本分配規則保持一致並聯動,實現了任意時刻的線上業務流量機房間比例調整。這是拆分使用分庫分表的第二個原因。

OceanBase叢集在螞蟻金服業務裡的核心作用是在資料庫層面解決資料副本三地分佈的強一致和高可用切換問題,並且提供了線上分割槽遷移和租戶彈性伸縮能力

 

 

後記

 

本文首先針對分散式資料庫種的資料存在的兩種形式副本(複製產生的)和分割槽(分割槽產生的)進行區分。然後總結了分割槽方案需要考慮的幾個點:分割槽粒度、分割槽策略、分割槽遷移和負載均衡、分割槽數量和分割槽路由問題等。即使這些都考慮好了,也只是分佈資料庫這個初局做好了。後面業務能否發揮分散式資料庫的優勢就取決於業務SQL的寫法是否有很好的線性擴充套件能力。最後簡單總結了螞蟻金服支付寶和網上銀行在分散式資料庫架構方面的最佳實踐。

 

推薦閱讀

 

  • 分散式資料庫的拆分設計實踐

  • 說說資料庫事務和開發(下)—— 分散式事務

  • 如何基於OceanBase構建應用和資料庫的異地多活

  • 揭祕OceanBase的彈性伸縮和負載均衡原理

  • 網商銀行 × OceanBase:首家雲上銀行的分散式資料庫應用實踐

  • Designing Data-Intensive Applications(文中配圖大部分來自本書)

 

更多後續分享敬請關注公眾號:obpilot

&n