Java互聯網架構-高併發數據庫分片技術詳解

Java NoSQL 編程語言 Spark Java小馬哥 Java小馬哥 2017-10-04

序言

分片技術的由來

關係型數據庫本身比較容易成為系統性能瓶頸,單機存儲容量、連接數、處理能力等都很有限,數據庫本身的“有狀態性”導致了它並不像Web和應用服務器那麼容易擴展。在互聯網行業海量數據和高併發訪問的考驗下,聰明的技術人員提出了分庫分表技術(有些地方也稱為Sharding、分片)。同時,流行的分佈式系統中間件(例如MongoDB、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小異的。

分佈式全局唯一ID

在很多中小項目中,我們往往直接使用數據庫自增特性來生成主鍵ID,這樣確實比較簡單。而在分庫分表的環境中,數據分佈在不同的分片上,不能再借助數據庫自增長特性直接生成,否則會造成不同分片上的數據表主鍵會重複。簡單介紹下使用和了解過的幾種ID生成算法。

Twitter的Snowflake(又名“雪花算法”)

UUID/GUID(一般應用程序和數據庫均支持)

MongoDB ObjectID(類似UUID的方式)

Ticket Server(數據庫生存方式,Flickr採用的就是這種方式)

分片字段該如何選擇

在開始分片之前,我們首先要確定分片字段(也可稱為“片鍵”)。很多常見的例子和場景中是採用ID或者時間字段進行拆分。這也並不絕對的,我的建議是結合實際業務,通過對系統中執行的sql語句進行統計分析,選擇出需要分片的那個表中最頻繁被使用,或者最重要的字段來作為分片字段。

常見分片規則

常見的分片策略有隨機分片和連續分片這兩種,如下圖所示:

當需要使用分片字段進行範圍查找時,連續分片可以快速定位分片進行高效查詢,大多數情況下可以有效避免跨分片查詢的問題。後期如果想對整個分片集群擴容時,只需要添加節點即可,無需對其他分片的數據進行遷移。但是,連續分片也有可能存在數據熱點的問題,就像圖中按時間字段分片的例子,有些節點可能會被頻繁查詢壓力較大,熱數據節點就成為了整個集群的瓶頸。而有些節點可能存的是歷史數據,很少需要被查詢到。

隨機分片其實並不是隨機的,也遵循一定規則。通常,我們會採用Hash取模的方式進行分片拆分,所以有些時候也被稱為離散分片。隨機分片的數據相對比較均勻,不容易出現熱點和併發訪問的瓶頸。但是,後期分片集群擴容起來需要遷移舊的數據。使用一致性Hash算法能夠很大程度的避免這個問題,所以很多中間件的分片集群都會採用一致性Hash算法。離散分片也很容易面臨跨分片查詢的複雜問題。

數據遷移,容量規劃,擴容等問題

很少有項目會在初期就開始考慮分片設計的,一般都是在業務高速發展面臨性能和存儲的瓶頸時才會提前準備。因此,不可避免的就需要考慮歷史數據遷移的問題。一般做法就是通過程序先讀出歷史數據,然後按照指定的分片規則再將數據寫入到各個分片節點中。

此外,我們需要根據當前的數據量和QPS等進行容量規劃,綜合成本因素,推算出大概需要多少分片(一般建議單個分片上的單表數據量不要超過1000W)。

如果是採用隨機分片,則需要考慮後期的擴容問題,相對會比較麻煩。如果是採用的範圍分片,只需要添加節點就可以自動擴容。

跨分片技術問題

跨分片的排序分頁

一般來講,分頁時需要按照指定字段進行排序。當排序字段就是分片字段的時候,我們通過分片規則可以比較容易定位到指定的分片,而當排序字段非分片字段的時候,情況就會變得比較複雜了。為了最終結果的準確性,我們需要在不同的分片節點中將數據進行排序並返回,並將不同分片返回的結果集進行彙總和再次排序,最後再返回給用戶。如下圖所示:

Java互聯網架構-高併發數據庫分片技術詳解

上面圖中所描述的只是最簡單的一種情況(取第一頁數據),看起來對性能的影響並不大。但是,如果想取出第10頁數據,情況又將變得複雜很多,如下圖所示:

Java互聯網架構-高併發數據庫分片技術詳解

有些讀者可能並不太理解,為什麼不能像獲取第一頁數據那樣簡單處理(排序取出前10條再合併、排序)。其實並不難理解,因為各分片節點中的數據可能是隨機的,為了排序的準確性,必須把所有分片節點的前N頁數據都排序好後做合併,最後再進行整體的排序。很顯然,這樣的操作是比較消耗資源的,用戶越往後翻頁,系統性能將會越差。

跨分片的函數處理

在使用Max、Min、Sum、Count之類的函數進行統計和計算的時候,需要先在每個分片數據源上執行相應的函數處理,然後再將各個結果集進行二次處理,最終再將處理結果返回。如下圖所示:

Java互聯網架構-高併發數據庫分片技術詳解

跨分片join

Join是關係型數據庫中最常用的特性,但是在分片集群中,join也變得非常複雜。應該儘量避免跨分片的join查詢(這種場景,比上面的跨分片分頁更加複雜,而且對性能的影響很大)。通常有以下幾種方式來避免:

全局表

全局表的概念之前在“垂直分庫”時提過。基本思想一致,就是把一些類似數據字典又可能會產生join查詢的表信息放到各分片中,從而避免跨分片的join。

ER分片

在關係型數據庫中,表之間往往存在一些關聯的關係。如果我們可以先確定好關聯關係,並將那些存在關聯關係的表記錄存放在同一個分片上,那麼就能很好的避免跨分片join問題。在一對多關係的情況下,我們通常會選擇按照數據較多的那一方進行拆分。如下圖所示:

Java互聯網架構-高併發數據庫分片技術詳解

這樣一來,Data Node1上面的訂單表與訂單詳細表就可以直接關聯,進行局部的join查詢了,Data Node2上也一樣。基於ER分片的這種方式,能夠有效避免大多數業務場景中的跨分片join問題。

內存計算

隨著spark內存計算的興起,理論上來講,很多跨數據源的操作問題看起來似乎都能夠得到解決。可以將數據丟給spark集群進行內存計算,最後將計算結果返回。

跨分片事務問題

跨分片事務也分佈式事務,想要了解分佈式事務,就需要了解“XA接口”和“兩階段提交”。值得提到的是,MySQL5.5x和5.6x中的xa支持是存在問題的,會導致主從數據不一致。直到5.7x版本中才得到修復。Java應用程序可以採用Atomikos框架來實現XA事務(J2EE中JTA)。

總結

以上是對高併發數據庫分片技術總結,分享給大家,希望大家可以瞭解什麼是高併發數據庫分片技術。覺得收穫的話可以點個關注收藏轉發一波喔,謝謝大佬們支持。(吹一波,233~~)

Java小馬哥,頭條出品,每天一篇乾貨,喜歡就收藏+關注

Java互聯網架構-高併發數據庫分片技術詳解

相關推薦

推薦中...