簡單明瞭!OLTP場景下的數據分佈式設計原則

前言


最近幾年做分佈式項目,很多工作是關於OLTP(聯機交易系統)場景下數據分佈式架構的,疫情期間正好整理下這方面的一些設計與實踐。為避免篇幅太長,本文分為設計篇和技術篇,設計篇主要偏向數據拆分的理論與方法,還有一些原則與經驗。技術篇則主要會介紹分庫分表中間件的設計與使用實踐,以及如何構建一個完整的分佈式數據服務平臺。


一般來說做分佈式架構,應用層是好做分佈式的,因為往往都是無狀態的(或者通過將數據轉移到DB、緩存、MQ等方式來實現無狀態),只需在流量入口、即在應用前面加一個負載均衡即可(例如Nginx、HAProxy、F5),這在大單體架構也多已具備。所以一般我們說分佈式架構,一個重要的部分就是要做數據的分佈式化。


簡單明瞭!OLTP場景下的數據分佈式設計原則

傳統單體集中式架構


數據的分佈式不像應用那麼簡單,因為各節點的數據可能是不一樣的,需要進行路由、解決多副本一致性,甚至多寫衝突等問題。雖然實現方案複雜,不過數據的分佈式本質上就兩種樸素思想:複製和分片。複製技術在傳統關係數據庫中也很常見,主要用來做主備、雙活,例如 MySQL Replication、Oracle DataGuard等。分片在數據庫裡也有對應產品。例如 MySQL Fabric、Oracle Sharding,但與複製相比,這些數據庫廠商對應的分片方案卻一直沒有被大眾廣泛接受。


在NewSQL數據庫中往往都內置了sharding機制,而且都基於paxos、raft算法來保證複製一致性,關於分庫分表與NewSQL方案對比選型,可參見我之前一篇文章 。


在OLTP場景下,複製和分片思想應用在傳統關係數據庫上,有兩個更為人熟知的名字,分庫分表與讀寫分離。


分庫分表,就是對原來單一數據庫表進行拆分,是基於傳統關係數據庫實現分佈式架構轉型的一個主要方式,因此首先第一個問題:


為什麼拆分?什麼時候需要拆分?


容量、性能、橫向擴展、微服務


單機數據庫的存儲、CPU、內存等資源都存在上限瓶頸,當數據量、訪問量到達一定量級後,性能則會急劇下降,也就是說通過scale up這種垂直擴展的方式是一個上限的,而且成本是較高的。


如果要實現scale out橫向擴展,就需要把原來一張表的數據拆分到多張物理庫表中存儲(水平拆分)。


另外如果是微服務架構,拆分後的服務歸屬不同的系統,對應不同的數據庫,其實就已經進行了垂直拆分。


拆分方式有哪些?


1、垂直拆分


垂直拆分一般更加貼近業務的拆分方式,在做微服務時使用最多的就是這種方式,具體會根據DDD(領域驅動設計)技術或者業務能力進行拆分,一般有界上下文確定了,拆分規則也就比較明確了。


這種方式對應用侵入性較小,往往只需要配置各自獨立數據庫(可能是物理機,也可能只是不同的實列)即可,最多做一個多數據源選擇的數據訪問層。


另外還有一種垂直拆分的場景是由於冷熱數據,同一行數據的不同列訪問頻率差別很大,或者是有些Text、Blob等大字段影響讀寫效率,這時也會將這些列拆分到不同表中。這種方式一般不常見,很多時候是在做性能優化時會考慮。


簡單明瞭!OLTP場景下的數據分佈式設計原則

垂直拆分


垂直拆分的優點:


  • 拆分後業務清晰,拆分規則明確。往往是按照系統或者交易的
  • 系統之間整合或擴展容易
  • 數據維護簡單、架構複雜度低


垂直拆分的缺點:


  • 部分業務表無法join,只能在應用層通過接口方式解決
  • 受每種業務不同的限制存在單庫性能瓶頸
  • 往往會產生分佈式事務場景


由於垂直切分是按照業務的分類將表分散到不同的庫,所以有些業務表會過於龐大,存在單庫讀寫與存儲瓶頸,這時就需要水平拆分來做解決。


2、水平拆分


水平拆分更加技術化,將一張表的數據分佈到多張庫與表中,具體方式可分為:只分庫、只分表、分庫又分表。例如order表,只分庫(ds1.order、ds2.order…dsk.order),只分表(ds.order_0、ds.order_1…ds.order_n),分庫又分表(ds1.order_0、ds2.order_1…dsk.order_n)。


簡單明瞭!OLTP場景下的數據分佈式設計原則

水平拆分


水平拆分的優點:


  • 如果操作數據分佈在同一庫中, 可以支持join、子查詢等複雜SQL
  • 解決了單庫性能瓶頸,支持橫向擴展
  • 由於應用未拆分,如果有分佈式數據訪問層,則應用改造較少


水平拆分的缺點:


  • 拆分規則、分庫分表數量需要精心設計
  • 如果涉及多個庫,會產生分佈式事務場景
  • 數據擴容時數據遷移工作量較大
  • 跨庫join往往需要應用實現,性能較差
  • 數據合併、聚合、分頁等無法由數據庫直接支持


數據庫有分區表還要分庫分表嗎?


傳統關係數據庫的分區表本質上還是共享cpu、內存,所以仍然面臨著scale up的問題,而且分區表支持的分區鍵往往也不夠靈活。但新的一些NewSQL分佈式數據庫,如OceanBase的分區表分散在不同的存儲節點上,從而避免單機性能瓶頸問題。


拆分具體步驟


1、確定拆分方式


根據業務特性選擇合適的拆分方式,一般結合使用。


1)垂直拆分


  • 場景:字段長度、訪問頻率差別較大字段表、微服務化
  • 注意:需要在同事務中操作的表儘量不要做拆分


2)水平拆分


  • 場景:數據量較大,超過單表、單庫性能
  • 注意:是否有跨庫事務,是否有非分片鍵操作表的場景,會涉及到庫表掃描交易


2、確定拆分字段


1)垂直拆分表、字段


按照功能模塊進行拆分直接按表即可,如果是拆分部分列,則需添加關聯列甚至冗餘列。


2)水平拆分字段


確保 拆分表都有分片鍵,多為主鍵或唯一索引,這些列中需包含分片信息。如果請求中未包含分片信息,則需要一個全局的路由表。


3、確定拆分規則


1)範圍Range


適合按照一定規律有序遞增的業務字段,例如日期、流水ID等,這種方式,例如0-9999->庫1,10000~19999->庫2 …;20150101-20161231->庫1,20170101-20171231->庫2…。


這種方式天然支持水平擴展,方便進行冷熱分離、歸檔,按需擴容方便,但負載容易不均衡,如果單庫壓力大,則也需數據遷移。


2)哈希Hash


數據分佈比較均衡,一般通過mod庫/表數量計算路由,本質上一種預分配,因此擴容時需要進行數據遷移,通常有一致性哈希、成倍擴容法。


3)應用自定義


由應用自定義路由規則,配置有分片ID對應的庫表序號,可以通過路由表、配置文件或其它自定義算法。這種方式靈活度最高,容易實現動態改變。


在我們項目中是1、2、3方式都有使用。


4、確定拆分數量


1)假設目標數據量為T(根據業務發展需求預估)


2)單表數據量建議P(例如MySQL 為500w),分表數量=T/P


3)目前配置典型業務場景下,單庫性能穩定前提下對應的數據容量上限L


單庫性能可以根據cpu(80% 以上)、磁盤IO(磁盤使用率100% iowait出現並逐步增大)、交易tps穩定性(出現tps大幅度波動)等系統指標確定其瓶頸狀態從而得到容量上限的評估。


4)分庫數量=T/L


庫表的數量關係到未來擴容、以及運維需求,不宜太多也不宜太少,以上主要是從容量角度去計算,實際場景下還需要結合硬件成本預算、數據清理歸檔策略等因素綜合考慮。


拆分後怎麼擴容?


1、垂直擴容


垂直拆分後,如果某個應用的數據庫壓力太大,可通過增加其資源配置(CPU、內存、PCIE)進行垂直擴容。


2、水平擴容


水平拆分下可以通過增加數據庫服務器進行擴容。這種方式需要進行數據遷移,如果一致性哈希則遷移就近節點數據,如果是成倍擴容時則需遷移所有節點一半數據。


一致性哈希模式雖然遷移的數據量較小,但容易造成數據的冷熱不均,因此我們項目中採用的成倍擴容方式,具體方式是提前將表分出來,例如分成128張表,項目初期將這些表均勻分佈在4臺數據庫服務器,隨著業務增加數據量增長,擴容到8臺數據庫,只需要將原4臺數據庫各自一半數量的表遷出到新增的4臺服務器,然後修改SQL路由即可。


簡單明瞭!OLTP場景下的數據分佈式設計原則

成倍擴容:應對整體數據量增長,擴容後物理機是原有2倍


如果是單臺數據庫有熱點數據壓力,也可以只將該庫一部分數據遷移出新擴容的庫。


簡單明瞭!OLTP場景下的數據分佈式設計原則

單庫擴容:應對某個切片數據增長過快,擴容到獨立的物理機


拆分後面臨的問題


  • 引入分佈式事務的問題
  • 跨庫Join的問題
  • 多庫合併排序分頁問題
  • SQL路由、重寫問題
  • 多數據源管理問題
  • 多維度拆分後帶來的數據彙總查詢等操作問題


解決方式:


  • 儘可能避免分佈式事務、跨節點join、排序場景
  • 避免使用數據庫分佈式事務,提供柔性事務支持(冪等、衝正、可靠性消息、TCC)
  • 由應用層解決join問題
  • 提供分佈式數據訪問層
  • 彙總庫、二級索引庫、小表廣播


關於分佈式數據訪問層在技術篇進行詳細介紹。


讀寫分離


在實際業務場景中,對數據庫的讀寫頻率是不一樣的。有的是寫多讀少,例如交易流水錶;有的是讀寫均衡,例如訂單表;有的則是讀多寫少,如客戶、信息以及配置等信息表。


數據分片解決的是單點性能瓶頸和橫向擴展能力,適合寫壓力比較大的場景。而讀多寫少的這類場景,如果單庫容量可以滿足,則可通過讀寫分離來解決讀壓力大的問題。具體可以把寫操作路由到主庫,讀操作按照權重、機房等分散在主庫和各個從庫。


簡單明瞭!OLTP場景下的數據分佈式設計原則

讀寫分離


讀寫分離模式下需要注意幾點:


1)主從延遲。在從庫上讀比主庫數據有一定時延(一般在毫秒級別,寫壓力大時可能在秒級別),所以選擇這種方式時業務上要允許一定的數據時延,例如一般對外查詢類交易都使用這種方式。


2)同一事務中,不能在從庫讀取數據,因為可能由於數據延時讀取到髒數據,違背事務的一致性,所以必須在主庫讀取。在實際開發時,數據訪問層可根據是否關閉事務自動提交來自動判斷是否必須在主庫讀。


3)對於數據延遲容忍度很低的查詢交易,可以在開發時單獨再封裝一個從主庫查詢的接口,或者在入參增加“是否需要強一致”標誌,交易實現時根據該標誌選擇從主庫還是從庫讀。


在實際項目中分庫分表和讀寫分離方式都有場景在用,但注意一般情況下避免使用分庫分表+讀寫分離這種複雜方案,因為分庫分表後讀寫壓力也不會太大了。


原則與經驗


數據分佈式是個系統工程,需要從領域建模、場景劃分、數據訪問、數據遷移擴容等多方面綜合考慮,在落地實現前要從全局做好設計,這裡簡單列下我們的一些設計原則與經驗:


1)用簡單的方案解決問題。能不切分儘量不要切分,切莫為了分佈式而拆分。讀寫分離能解決問題,就不分庫分表。


2)切分一定要選擇合適切分規則(能保證90%交易不會跨分片), 梳理好所有場景,提前規劃好再實施。


3)數據訪問層設計上功能要強大,但一定明確使用場景,切忌無腦濫用。比如我們項目中數據訪問中間件雖然支持分佈式事務XA,但一般並不推薦使用;支持DDL,但聯機交易時禁止使用;支持多庫鏈式事務提交,但默認只支持嚴格單庫事務。


4)制定應用開發規範,明確SQL使用限制與要求,SQL要儘量簡單。例如我們項目使用MySQL,部署在PC Server上,單機性能相比小型機上DB2、Oracle差很多,因此禁止使用觸發器、外鍵、join,SQL操作必須攜帶索引與拆分列(數據訪問層也會校驗),主鍵必須是自增等等。


5)儘量使用柔性事務解決跨庫與跨系統事務問題。能用MQ最終一致性就別用Saga、TCC。


2020年9月11日,北京,Gdevops全球敏捷運維峰會將開啟年度首站!重點圍繞數據庫、智慧運維、Fintech金融科技領域,攜手阿里、騰訊、螞蟻金服、中國銀行、平安銀行、中郵消費金融、建設銀行、工商銀行、農業銀行、民生銀行、58到家、中國聯通大數據、浙江移動、新炬網絡等技術代表,展望雲時代下數據庫發展趨勢、破解運維轉型困局。


簡單明瞭!OLTP場景下的數據分佈式設計原則


分享到:


相關文章: