概述
水平拆分的概念隨着分布式數據庫的推廣已為大部分人熟知。分庫分表、異構索引、小表廣播、這些功能幾乎是產品功能需求標配。然而有些客戶使用分布式數據庫后的體驗不盡如意。本文嘗試從數據的角度總結分布式數據的復制(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的寫法是否有很好的線性擴展能力。最后簡單總結了螞蟻金服支付寶和網上銀行在分布式數據庫架構方面的最佳實踐。
推薦閱讀
-
Designing Data-Intensive Applications(文中配圖大部分來自本書)
更多后續分享敬請關注公眾號:obpilot