分布式數據庫:數據分片,如何存儲超大規模的數據?
隨著互聯網時代,特別是移動互聯網的到來,形形色色的企業都在將自己的系統平臺快速升級迭代,以此作為向互聯網轉型的一部分。
在此背景下,這類應用平臺所依賴的數據庫系統就需要支持突然增加的巨量交易數據,但是在這種情況下單體的數據庫往往會很快過載,而用于擴展數據庫最常見的技術手段就是“數據分片”。
因此今天我將為你介紹什么是分片,以及如何將其用于擴展數據庫。同時,我還會回顧常見分片架構的優缺點,以使用 TiDB 為例,和你探討如何在分布式數據庫中實現分片。
數據分片概論
分片是將大數據表分解為較小的表(稱為分片)的過程,這些分片分布在多個數據庫集群節點上。分片本質上可以被看作傳統數據庫中的分區表,是一種水平擴展手段。每個分片上包含原有總數據集的一個子集,從而可以將總負載分散在各個分區之上。
數據分片的方式一般有兩種。
- 水平分片:在不同的數據庫節點中存儲同一表的不同行。
- 垂直分片:在不同的數據庫節點中存儲表不同的表列。
如下圖所示,水平和垂直這兩個概念來自原關系型數據庫表模式的可視化直觀視圖。
圖 1 可視化直觀視圖
分片理念其實來源于經濟學的邊際收益理論:如果投資持續增加,但收益的增幅開始下降時,被稱為邊際收益遞減狀態。而剛好要開始下降的那個點被稱為邊際平衡點。
該理論應用在數據庫計算能力上往往被表述為:如果數據庫處理能力遇到瓶頸,最簡單的方式是持續提高系統性能,如更換更強勁的 CPU、更大內存等,這種模式被稱為垂直擴展。當持續增加資源以提升數據庫能力時,垂直擴展有其自身的限制,最終達到邊際平衡,收益開始遞減。
而此時,對表進行水平分片意味著可以引入更多的計算能力處理數據與交易。從而,將邊際遞減扭轉為邊際遞增狀態。同時,通過持續地平衡所有節點上的處理負載和數據量,分片模式還可以獲得 1+1>2 的效果,即集群平均處理能力大于單節點處理能力。
這樣就使得規模較小、價格便宜的服務器組成的水平擴展集群,可能比維護一臺大型商用數據庫服務器更具成本效益。這也是第一講中“去 IOE 運動”的核心技術背景。
除了解決擴展難題,分片還可以緩解計劃外停機,大大降低系統 RTO(目標恢復時間)。即使在計劃內的停機期,如果沒有分片的加持,數據庫整體上還是處于不可訪問狀態的,這就無法滿足業務上對 SLO(目標服務級別)的要求。
如果分片可以如我們所希望的那樣正常工作,它就可以確保系統的高可用。即使數據庫集群部分節點發生故障,只要其他節點在其中運行,數據庫整體仍可對外提供服務。當然,這還需要復制與一致性服務的保證,我們會在之后課時中進一步探討。
總而言之,分片可以增加數據庫集群的總容量并加快處理速度,同時可以使用比垂直擴展更低的成本提供更高的可用性。
分片算法
分片算法一般指代水平分片所需要的算法。經過多年的演化,其已經在大型系統中得到了廣泛的實踐。下面我將介紹兩種最常見的水平分片算法,并簡要介紹一些其他的分片算法優化思路。
哈希分片
哈希分片,首先需要獲取分片鍵,然后根據特定的哈希算法計算它的哈希值,最后使用哈希值確定數據應被放置在哪個分片中。數據庫一般對所有數據使用統一的哈希算法(例如 ketama),以促成哈希函數在服務器之間均勻地分配數據,從而降低了數據不均衡所帶來的熱點風險。通過這種方法,數據不太可能放在同一分片上,從而使數據被隨機分散開。
這種算法非常適合隨機讀寫的場景,能夠很好地分散系統負載,但弊端是不利于范圍掃描查詢操作。下圖是這一算法的工作原理。
范圍分片
范圍分片根據數據值或鍵空間的范圍對數據進行劃分,相鄰的分片鍵更有可能落入相同的分片上。每行數據不像哈希分片那樣需要進行轉換,實際上它們只是簡單地被分類到不同的分片上。下圖是范圍分片的工作原理。
范圍分片需要選擇合適的分片鍵,這些分片鍵需要盡量不包含重復數值,也就是其候選數值盡可能地離散。同時數據不要單調遞增或遞減,否則,數據不能很好地在集群中離散,從而造成熱點。
范圍分片非常適合進行范圍查找,但是其隨機讀寫性能偏弱。
融合算法
這時我們應該意識到,以上介紹的哈希和范圍的分片算法并不是非此即彼,二選一的。相反,我們可以靈活地組合它們。
例如,我們可以建立一個多級分片策略,該策略在最上層使用哈希算法,而在每個基于哈希的分片單元中,數據將按順序存儲。
這個算法相對比較簡單且靈活,下面我們再說一個地理位置算法。
地理位置算法
該算法一般用于 NewSQL 數據庫,提供全球范圍內分布數據的能力。
在基于地理位置的分片算法中,數據被映射到特定的分片,而這些分片又被映射到特定區域以及這些區域中的節點。
然后在給定區域內,使用哈希或范圍分片對數據進行分片。例如,在美國、中國和日本的 3 個區域中運行的集群可以依靠 User 表的 Country_Code 列,將特定用戶(User)所在的數據行映射到符合位置就近規則的區域中。
那么以上就是幾種典型的分片算法,下面我們接著討論如何將分片算法應用到實際的場景中。
手動分片 vs 自動分片
手動分片,顧名思義,就是設置靜態規則來將數據根據分片算法分散到數據庫節點。這一般是由于用戶使用的數據庫不支持自動的分片,如 MySQL、Oracle 等。這個問題可以在應用層面上做數據分片來解決,也可以使用簡單的數據庫中間件或 Proxy 來設置靜態的分片規則來解決。
手動分片的缺點是數據分布不均勻。數據分布不均可能導致數據庫負載極其不平衡,從而使其中一些節點過載,而另一些節點訪問量較少。
因此,最好避免在部分節點上存儲過多數據,否則會造成這些節點成為訪問熱點,進而導致其運行速度降低,甚至使服務器崩潰。此外,當整體數據集過小時,也會導致這個問題,因為集群中只有部分節點才有數據。
這在開發和測試環境中是可以接受的,但在生產環境中是不可以接受的。因為數據分布不均,熱點以及將數據存儲在太少的分片上,都會導致數據庫集群內的節點計算資源耗盡,造成系統不穩定。
但如果精心設計,且數據分布變化不大,采用手動分片也是一個較為簡單、維護成本低廉的方案。
而使用自動分片意味著計算節點與分片算法可以相互配合,從而使數據庫進行彈性伸縮。
使用基于范圍的分片很容易實現自動分片:只需拆分或合并每個分片。
假設現在有一個范圍為 [1,100)的分片,我們想要將它分裂為兩個范圍,先選擇 50 作為切分點;然后將該區域分為 [1,50)和 [50,100)之后,將兩個區域移動到兩臺不同的數據庫節點中,從而使系統負載達到平衡。
基于范圍的分片可能會帶來讀取和寫入熱點,我們可以通過拆分和移動分片消除這些熱點。
而使用基于哈希的分片的系統實現自動分片代價很高昂。我們現在使用上面圖 1 中的例子來說明。
當前系統有 4 個節點,然后添加一個新的數據庫節點。在哈希函數中,“ n”從 4 更改為 5,這會導致較大的系統抖動。盡管你可以使用像 Ketama 這樣的一致性哈希算法來盡可能減少系統抖動,但數據遷移與再平衡操作還是必須要有的。
這是因為在應用哈希函數后,數據是隨機分布的,并且調整散列算法肯定會更改大多數數據的分布情況。
自動分片是分布式數據庫的主流功能,所有主要的分布式數據庫,甚至數據庫中間件都在嘗試自動分片。下面我將結合幾個案例來說明。
分片算法案例
數據分片是數據庫中間件的核心功能,且該領域開源項目較多。我這里以 Apache ShardingShpere 的分片內容為例,向你介紹分片算法的相關實踐案例。
分片鍵生成
ShardingShpere 首先提供了分布式的主鍵生成,這是生成分片鍵的關鍵。由于分布式數據庫內一般由多個數據庫節點參與,因此基于數據庫實例的主鍵生成并不適合分布式場景。
常用的算法有 UUID 和 Snowfalke 兩種無狀態生成算法。
UUID 是最簡單的方式,但是生成效率不高,且數據離散度一般。因此目前生產環境中會采用后一種算法。下圖就是用該算法生成的分片鍵的結構。
圖 4 分片鍵結構
其中有效部分有三個。
- 時間戳:算法類似 UNIX 時間的表示形式,它是從一個特定時間開始到當前時間點之間的毫秒數,本案例中該算法可以使用近 70 年。
- 工作節點 ID:保證每個獨立工作的數據庫節點不會產生重復的數據。
- 訪問序列:在同一個進程、同一個毫秒內,保證產生的 ID 不重復。
靈活的分片算法
為了保證分片計算的靈活性,ShardingShpere 提供了標準分片算法和一些工具,幫助用戶實現個性化算法。
- PreciseShardingAlgorithm 配合哈希函數使用,可以實現哈希分片。RangeShardingAlogrithm 可以實現范圍分片。
- 使用 ComplexShardingStrategy 可以使用多個分片鍵來實現融合分片算法。
- 有的時候,數據表的分片模式不是完全一致。對于一些特別的分片模式,可以使用 HintShardingStrategy 在運行態制定特殊的路由規則,而不必使用統一的分片配置。
- 如果用戶希望實現諸如地理位置算法等特殊的分片算法,可以自定義分片策略。使用 inline 表達式或 Java 代碼進行編寫,前者基于配置不需要編譯,適合簡單的個性化分片計算;后者可以實現更加復雜的計算,但需要編譯打包的過程。
用戶通過以上多種分片工具,可以靈活和統一地制定數據庫分片策略。
自動分片
ShardingShpere 提供了 Sharding-Scale 來支持數據庫節點彈性伸縮,該功能就是其對自動分片的支持。下圖是自動分片功能展示圖,可以看到經過 Sharding-Scale 的特性伸縮,原有的兩個數據庫擴充為三個。
圖 5 自動分片功能展示
自動分片包含下圖所示的四個過程。
圖 6 自動分片過程
從圖 6 中可以看到,通過該工作量,ShardingShpere 可以支持復雜的基于哈希的自動分片。同時我們也應該看到,沒有專業和自動化的彈性擴縮容工具,想要實現自動化分片是非常困難的。
以上就是分片算法的實際案例,使用的是經典的水平分片模式。而目前水平和垂直分片有進一步合并的趨勢,下面要介紹的 TiDB 正代表著這種融合趨勢。
垂直與水平分片融合案例
TiDB 就是一個垂直與水平分片融合的典型案例,同時該方案也是 HATP 融合方案。
其中水平擴展依賴于底層的 TiKV,如下圖所示。
圖 7 TiKV
TiKV 使用范圍分片的模式,數據被分配到 Region 組里面。一個分組保持三個副本,這保證了高可用性(相關內容會在“05 | 一致性與 CAP 模型:為什么需要分布式一致性?”中詳細介紹)。當 Region 變大后,會被拆分,新分裂的 Region 也會產生多個副本。
TiDB 的水平擴展依賴于 TiFlash,如下圖所示。
圖 8 TiFlash
從圖 8 中可以看到 TiFlash 是 TiKV 的列擴展插件,數據異步從 TiKV 里面復制到 TiFlash,而后進行列轉換,其中要使用 MVCC 技術來保證數據的一致性。
上文所述的 Region 會增加一個新的異步副本,而后該副本進行了數據切分,并以列模式組合到 TiFlash 中,從而達到了水平和垂直擴展在同一個數據庫的融合。這是兩種數據庫引擎的融合。
以上的融合為 TiDB 帶來的益處主要體現在查詢層面,特別對特定列做聚合查詢的效率很高。TiDB 可以很智能地切換以上兩種分片引擎,從而達到最優的查詢效率。
總結
今天先是詳細介紹了分片的原理,以及多種常用的分片技術;而后分析了手動分片與自動分片的區別,要知道數據分片的未來是屬于自動分片的。
最后,我通過兩個著名的開源項目介紹了分片技術是如何應用到分布式數據庫中的。其中 TiDB 所展示的 HATP 融合兩個分片模式的技術路線,可以被看作是未來分片模式發展的趨勢。