精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

分庫分表,我再講最后一次!

運維 數(shù)據(jù)庫運維
提起分庫分表,對于大部分服務(wù)器開發(fā)來說,其實并不是一個新鮮的名詞。隨著業(yè)務(wù)的發(fā)展,我們表中的數(shù)據(jù)量會變的越來越大,字段也可能隨著業(yè)務(wù)復(fù)雜度的升高而逐漸增多,我們?yōu)榱私鉀Q單表的查詢性能問題,一般會進行分表操作。

[[430710]]

圖片來自 包圖網(wǎng)

提起分庫分表,對于大部分服務(wù)器開發(fā)來說,其實并不是一個新鮮的名詞。隨著業(yè)務(wù)的發(fā)展,我們表中的數(shù)據(jù)量會變的越來越大,字段也可能隨著業(yè)務(wù)復(fù)雜度的升高而逐漸增多,我們?yōu)榱私鉀Q單表的查詢性能問題,一般會進行分表操作。

同時我們業(yè)務(wù)的用戶活躍度也會越來越高,并發(fā)量級不斷加大,那么可能會達到單個數(shù)據(jù)庫的處理能力上限。此時我們?yōu)榱私鉀Q數(shù)據(jù)庫的處理性能瓶頸,一般會進行分庫操作。

不管是分庫操作還是分表操作,我們一般都有兩種方式應(yīng)對,一種是垂直拆分,一種是水平拆分。

關(guān)于兩種拆分方式的區(qū)別和特點,互聯(lián)網(wǎng)上參考資料眾多,很多人都寫過相關(guān)內(nèi)容,這里就不再進行詳細贅述,有興趣的讀者可以自行檢索。

此文主要詳細聊一聊,我們最實用最常見的水平分庫分表方式中的一些特殊細節(jié),希望能幫助大家避免走彎路,找到最合適自身業(yè)務(wù)的分庫分表設(shè)計。

【注 1】本文中的案例均基于 MySQL 數(shù)據(jù)庫,下文中的分庫分表統(tǒng)指水平分庫分表。

【注 2】后文中提到到 M 庫 N 表,均指共 M 個數(shù)據(jù)庫,每個數(shù)據(jù)庫共 N 個分表,即總表個數(shù)其實為 M*N。

什么是一個好的分庫分表方案?

①方案可持續(xù)性

前期業(yè)務(wù)數(shù)據(jù)量級不大,流量較低的時候,我們無需分庫分表,也不建議分庫分表。

但是一旦我們要對業(yè)務(wù)進行分庫分表設(shè)計時,就一定要考慮到分庫分表方案的可持續(xù)性。

那何為可持續(xù)性?其實就是:業(yè)務(wù)數(shù)據(jù)量級和業(yè)務(wù)流量未來進一步升高達到新的量級的時候,我們的分庫分表方案可以持續(xù)使用。

一個通俗的案例,假定當前我們分庫分表的方案為 10 庫 100 表,那么未來某個時間點,若 10 個庫仍然無法應(yīng)對用戶的流量壓力,或者 10 個庫的磁盤使用即將達到物理上限時,我們的方案能夠進行平滑擴容。

在后文中我們將介紹下目前業(yè)界常用的翻倍擴容法和一致性 Hash 擴容法。

②數(shù)據(jù)偏斜問題

一個良好的分庫分表方案,它的數(shù)據(jù)應(yīng)該是需要比較均勻的分散在各個庫表中的。

如果我們進行一個拍腦袋式的分庫分表設(shè)計,很容易會遇到以下類似問題:

  • 某個數(shù)據(jù)庫實例中,部分表的數(shù)據(jù)很多,而其他表中的數(shù)據(jù)卻寥寥無幾,業(yè)務(wù)上的表現(xiàn)經(jīng)常是延遲忽高忽低,飄忽不定。
  • 數(shù)據(jù)庫集群中,部分集群的磁盤使用增長特別塊,而部分集群的磁盤增長卻很緩慢。每個庫的增長步調(diào)不一致,這種情況會給后續(xù)的擴容帶來步調(diào)不一致,無法統(tǒng)一操作的問題。

這邊我們定義分庫分表最大數(shù)據(jù)偏斜率為:(數(shù)據(jù)量最大樣本-數(shù)據(jù)量最小樣本)/數(shù)據(jù)量最小樣本。

一般來說,如果我們的最大數(shù)據(jù)偏斜率在 5% 以內(nèi)是可以接受的。

常見的分庫分表方案

①Range 分庫分表

顧名思義,該方案根據(jù)數(shù)據(jù)范圍劃分數(shù)據(jù)的存放位置。

舉個最簡單例子,我們可以把訂單表按照年份為單位,每年的數(shù)據(jù)存放在單獨的庫(或者表)中。

如下圖所示:

  1. /** 
  2.  * 通過年份分表 
  3.  * 
  4.  * @param orderId 
  5.  * @return 
  6.  */ 
  7. public static String rangeShardByYear(String orderId) { 
  8.     int year = Integer.parseInt(orderId.substring(0, 4)); 
  9.     return "t_order_" + year

通過數(shù)據(jù)的范圍進行分庫分表,該方案是最樸實的一種分庫方案,它也可以和其他分庫分表方案靈活結(jié)合使用。

時下非常流行的分布式數(shù)據(jù)庫:TiDB 數(shù)據(jù)庫,針對 TiKV 中數(shù)據(jù)的打散,也是基于 Range 的方式進行,將不同范圍內(nèi)的[StartKey,EndKey)分配到不同的 Region 上。

下面我們看看該方案的缺點:

  • 最明顯的就是數(shù)據(jù)熱點問題,例如上面案例中的訂單表,很明顯當前年度所在的庫表屬于熱點數(shù)據(jù),需要承載大部分的 IO 和計算資源。
  • 新庫和新表的追加問題。一般我們線上運行的應(yīng)用程序是沒有數(shù)據(jù)庫的建庫建表權(quán)限的,故我們需要提前將新的庫表提前建立,防止線上故障。
  • 這點非常容易被遺忘,尤其是穩(wěn)定跑了幾年沒有迭代任務(wù),或者人員又交替頻繁的模塊。

業(yè)務(wù)上的交叉范圍內(nèi)數(shù)據(jù)的處理。舉個例子,訂單模塊無法避免一些中間狀態(tài)的數(shù)據(jù)補償邏輯,即需要通過定時任務(wù)到訂單表中掃描那些長時間處于待支付確認等狀態(tài)的訂單。

這里就需要注意了,因為是通過年份進行分庫分表,那么元旦的那一天,你的定時任務(wù)很有可能會漏掉上一年的最后一天的數(shù)據(jù)掃描。

②Hash 分庫分表

雖然分庫分表的方案眾多,但是 Hash 分庫分表是最大眾最普遍的方案,也是本文花最大篇幅描述的部分。

針對 Hash 分庫分表的細節(jié)部分,相關(guān)的資料并不多。大部分都是闡述一下概念舉幾個示例,而細節(jié)部分并沒有特別多的深入,如果未結(jié)合自身業(yè)務(wù)貿(mào)然參考引用,后期非常容易出現(xiàn)各種問題。

在正式介紹這種分庫分表方式之前,我們先看幾個常見的錯誤案例。

常見錯誤案例一:非互質(zhì)關(guān)系導(dǎo)致的數(shù)據(jù)偏斜問題

  1. public static ShardCfg shard(String userId) { 
  2.     int hash = userId.hashCode(); 
  3.     // 對庫數(shù)量取余結(jié)果為庫序號 
  4.     int dbIdx = Math.abs(hash % DB_CNT); 
  5.     // 對表數(shù)量取余結(jié)果為表序號 
  6.     int tblIdx = Math.abs(hash % TBL_CNT); 
  7.  
  8.     return new ShardCfg(dbIdx, tblIdx); 

上述方案是初次使用者特別容易進入的誤區(qū),用 Hash 值分別對分庫數(shù)和分表數(shù)取余,得到庫序號和表序號。

其實稍微思索一下,我們就會發(fā)現(xiàn),以 10 庫 100 表為例,如果一個 Hash 值對 100 取余為 0,那么它對 10 取余也必然為 0。

這就意味著只有 0 庫里面的 0 表才可能有數(shù)據(jù),而其他庫中的 0 表永遠為空!

類似的我們還能推導(dǎo)到,0 庫里面的共 100 張表,只有 10 張表中(個位數(shù)為 0 的表序號)才可能有數(shù)據(jù)。

這就帶來了非常嚴重的數(shù)據(jù)偏斜問題,因為某些表中永遠不可能有數(shù)據(jù),最大數(shù)據(jù)偏斜率達到了無窮大。

那么很明顯,該方案是一個未達到預(yù)期效果的錯誤方案。數(shù)據(jù)的散落情況大致示意圖如下:

事實上,只要庫數(shù)量和表數(shù)量非互質(zhì)關(guān)系,都會出現(xiàn)某些表中無數(shù)據(jù)的問題。

證明如下:

那么是不是只要庫數(shù)量和表數(shù)量互質(zhì)就可用用這種分庫分表方案呢?比如我用 11 庫 100 表的方案,是不是就合理了呢?

答案是否定的,我們除了要考慮數(shù)據(jù)偏斜的問題,還需要考慮可持續(xù)性擴容的問題,一般這種 Hash 分庫分表的方案后期的擴容方式都是通過翻倍擴容法,那 11 庫翻倍后,和 100 又不再互質(zhì)。

當然,如果分庫數(shù)和分表數(shù)不僅互質(zhì),而且分表數(shù)為奇數(shù)(例如 10 庫 101 表),則理論上可以使用該方案,但是我想大部分人可能都會覺得使用奇數(shù)的分表數(shù)比較奇怪吧。

常見錯誤案例二:擴容難以持續(xù)

如果避開了上述案例一的陷阱,那么我們又很容易一頭扎進另一個陷阱,大概思路如下。

我們把 10 庫 100 表看成總共 1000 個邏輯表,將求得的 Hash 值對 1000 取余,得到一個介于[0,999)中的數(shù),然后再將這個數(shù)二次均分到每個庫和每個表中。

大概邏輯代碼如下:

  1. public static ShardCfg shard(String userId) { 
  2.         // ① 算Hash 
  3.         int hash = userId.hashCode(); 
  4.         // ② 總分片數(shù) 
  5.         int sumSlot = DB_CNT * TBL_CNT; 
  6.         // ③ 分片序號 
  7.         int slot = Math.abs(hash % sumSlot); 
  8.         // ④ 計算庫序號和表序號的錯誤案例 
  9.         int dbIdx = slot % DB_CNT ; 
  10.         int tblIdx = slot / DB_CNT ; 
  11.  
  12.         return new ShardCfg(dbIdx, tblIdx); 
  13.     } 

該方案確實很巧妙的解決了數(shù)據(jù)偏斜的問題,只要 Hash 值足夠均勻,那么理論上分配序號也會足夠平均,于是每個庫和表中的數(shù)據(jù)量也能保持較均衡的狀態(tài)。

但是該方案有個比較大的問題,那就是在計算表序號的時候,依賴了總庫的數(shù)量,那么后續(xù)翻倍擴容法進行擴容時,會出現(xiàn)擴容前后數(shù)據(jù)不在同一個表中,從而無法實施。

如上圖中,例如擴容前 Hash 為 1986 的數(shù)據(jù)應(yīng)該存放在 6 庫 98 表,但是翻倍擴容成 20 庫 100 表后,它分配到了 6 庫 99 表,表序號發(fā)生了偏移。

這樣的話,我們在后續(xù)在擴容的時候,不僅要基于庫遷移數(shù)據(jù),還要基于表遷移數(shù)據(jù),非常麻煩且易錯。

看完了上面的幾種典型的錯誤案例,那么我們有哪些比較正確的方案呢?下面將結(jié)合一些實際場景案例介紹幾種 Hash 分庫分表的方案。

常用姿勢一:標準的二次分片法

上述錯誤案例二中,整體思路完全正確,只是最后計算庫序號和表序號的時候,使用了庫數(shù)量作為影響表序號的因子,導(dǎo)致擴容時表序號偏移而無法進行。

事實上,我們只需要換種寫法,就能得出一個比較大眾化的分庫分表方案。

  1. public static ShardCfg shard2(String userId) { 
  2.         // ① 算Hash 
  3.         int hash = userId.hashCode(); 
  4.         // ② 總分片數(shù) 
  5.         int sumSlot = DB_CNT * TBL_CNT; 
  6.         // ③ 分片序號 
  7.         int slot = Math.abs(hash % sumSlot); 
  8.         // ④ 重新修改二次求值方案 
  9.         int dbIdx = slot / TBL_CNT ; 
  10.         int tblIdx = slot % TBL_CNT ; 
  11.  
  12.         return new ShardCfg(dbIdx, tblIdx); 
  13.     } 

大家可以注意到,和錯誤案例二中的區(qū)別就是通過分配序號重新計算庫序號和表序號的邏輯發(fā)生了變化。

它的分配情況如下:

那為何使用這種方案就能夠有很好的擴展持久性呢?我們進行一個簡短的證明:

通過上面結(jié)論我們知道,通過翻倍擴容后,我們的表序號一定維持不變,庫序號可能還是在原來庫,也可能平移到了新庫中(原庫序號加上原分庫數(shù)),完全符合我們需要的擴容持久性方案。

方案缺點:

  • 翻倍擴容法前期操作性高,但是后續(xù)如果分庫數(shù)已經(jīng)是大幾十的時候,每次擴容都非常耗費資源。
  • 連續(xù)的分片鍵 Hash 值大概率會散落在相同的庫中,某些業(yè)務(wù)可能容易存在庫熱點(例如新生成的用戶 Hash 相鄰且遞增,且新增用戶又是高概率的活躍用戶,那么一段時間內(nèi)生成的新用戶都會集中在相鄰的幾個庫中)。

常用姿勢二:關(guān)系表冗余

我們可以將分片鍵對應(yīng)庫的關(guān)系通過關(guān)系表記錄下來,我們把這張關(guān)系表稱為"路由關(guān)系表"。

  1. public static ShardCfg shard(String userId) { 
  2.         int tblIdx = Math.abs(userId.hashCode() % TBL_CNT); 
  3.         // 從緩存獲取 
  4.         Integer dbIdx = loadFromCache(userId); 
  5.         if (null == dbIdx) { 
  6.             // 從路由表獲取 
  7.             dbIdx = loadFromRouteTable(userId); 
  8.             if (null != dbIdx) { 
  9.                 // 保存到緩存 
  10.                 saveRouteCache(userId, dbIdx); 
  11.             } 
  12.         } 
  13.         if (null == dbIdx) { 
  14.             // 此處可以自由實現(xiàn)計算庫的邏輯 
  15.             dbIdx = selectRandomDbIdx(); 
  16.             saveToRouteTable(userId, dbIdx); 
  17.             saveRouteCache(userId, dbIdx); 
  18.         } 
  19.  
  20.         return new ShardCfg(dbIdx, tblIdx); 
  21.     } 

該方案還是通過常規(guī)的 Hash 算法計算表序號,而計算庫序號時,則從路由表讀取數(shù)據(jù)。

因為在每次數(shù)據(jù)查詢時,都需要讀取路由表,故我們需要將分片鍵和庫序號的對應(yīng)關(guān)系記錄同時維護在緩存中以提升性能。

上述實例中 selectRandomDbIdx 方法作用為生成該分片鍵對應(yīng)的存儲庫序號,這邊可以非常靈活的動態(tài)配置。

例如可以為每個庫指定一個權(quán)重,權(quán)重大的被選中的概率更高,權(quán)重配置成0則可以將關(guān)閉某些庫的分配。當發(fā)現(xiàn)數(shù)據(jù)存在偏斜時,也可以調(diào)整權(quán)重使得各個庫的使用量調(diào)整趨向接近。

該方案還有個優(yōu)點,就是理論上后續(xù)進行擴容的時候,僅需要掛載上新的數(shù)據(jù)庫節(jié)點,將權(quán)重配置成較大值即可,無需進行任何的數(shù)據(jù)遷移即可完成。

如下圖所示:最開始我們?yōu)?4 個數(shù)據(jù)庫分配了相同的權(quán)重,理論上落在每個庫的數(shù)據(jù)概率均等。

但是由于用戶也有高頻低頻之分,可能某些庫的數(shù)據(jù)增長會比較快。當掛載新的數(shù)據(jù)庫節(jié)點后,我們靈活的調(diào)整了每個庫的新權(quán)重。

該方案似乎解決了很多問題,那么它有沒有什么不適合的場景呢?當然有,該方案在很多場景下其實并不太適合。

以下舉例說明:

  • 每次讀取數(shù)據(jù)需要訪問路由表,雖然使用了緩存,但是還是有一定的性能損耗。
  • 路由關(guān)系表的存儲方面,有些場景并不合適。例如上述案例中用戶 id 的規(guī)模大概是在 10 億以內(nèi),我們用單庫百表存儲該關(guān)系表即可。

但如果例如要用文件 MD5 摘要值作為分片鍵,因為樣本集過大,無法為每個 md5 值都去指定關(guān)系(當然我們也可以使用 md5 前 N 位來存儲關(guān)系)。

  • 饑餓占位問題,如下詳敘:我們知道,該方案的特點是后續(xù)無需擴容,可以隨時修改權(quán)重調(diào)整每個庫的存儲增長速度。

但是這個愿景是比較縹緲,并且很難實施的,我們選取一個簡單的業(yè)務(wù)場景考慮以下幾個問題。

【業(yè)務(wù)場景】:以用戶存放文件到云端的云盤業(yè)務(wù)為例,需要對用戶的文件信息進行分庫分表設(shè)計。

有以下假定場景:

  • 假定有 2 億理論用戶,假設(shè)當前有 3000W 有效用戶。
  • 平均每個用戶文件量級在 2000 個以內(nèi)
  • 用戶 id 為隨機 16 位字符串
  • 初期為 10 庫,每個庫 100 張表。

我們使用路由表記錄每個用戶所在的庫序號信息。那么該方案會有以下問題:

第一:我們總共有 2 億個用戶,只有 3000W 個產(chǎn)生過事務(wù)的用戶。若程序不加處理,用戶發(fā)起任何請求則創(chuàng)建路由表數(shù)據(jù),會導(dǎo)致為大量實際沒有事務(wù)數(shù)據(jù)的用戶提前創(chuàng)建路由表。

筆者最初存儲云盤用戶數(shù)據(jù)的時候便遇到了這個問題,客戶端 app 會在首頁查詢用戶空間使用情況,這樣導(dǎo)致幾乎一開始就為每個使用者分配好了路由。

隨著時間的推移,這部分沒有數(shù)據(jù)的"靜默"的用戶,隨時可能開始他的云盤使用之旅而“復(fù)蘇”,從而導(dǎo)致它所在的庫迅速增長并超過單個庫的空間容量極限,從而被迫拆分擴容。

解決這個問題的方案,其實就是只針對事務(wù)操作(例如購買空間,上傳數(shù)據(jù),創(chuàng)建文件夾等等)才進行路由的分配,這樣對代碼層面便有了一些傾入。

第二、按照前面描述的業(yè)務(wù)場景,一個用戶最終平均有 2000 條數(shù)據(jù),假定每行大小為 1K。

為了保證 B+樹的層級在 3 層,我們限制每張表的數(shù)據(jù)量在 2000W,分表數(shù)為 100 的話,可以得到理論上每個庫的用戶數(shù)不能超過 100W 個用戶。

也就是如果是 3000W 個產(chǎn)生過事務(wù)的用戶,我們需要為其分配 30 個庫,這樣會在業(yè)務(wù)前期,用戶平均數(shù)據(jù)量相對較少的時候,存在非常大的數(shù)據(jù)庫資源的浪費。

解決第二個問題,我們一般可以將很多數(shù)據(jù)庫放在一個實例上,后續(xù)隨著增長情況進行拆分。也可以后續(xù)針對將滿的庫,使用常規(guī)手段進行拆分和遷移。

常用姿勢三:基因法

還是由錯誤案例一啟發(fā),我們發(fā)現(xiàn)案例一不合理的主要原因,就是因為庫序號和表序號的計算邏輯中,有公約數(shù)這個因子在影響庫表的獨立性。

那么我們是否可以換一種思路呢?我們使用相對獨立的 Hash 值來計算庫序號和表序號。

  1. public static ShardCfg shard(String userId) { 
  2.     int dbIdx = Math.abs(userId.substring(0, 4).hashCode() % DB_CNT ); 
  3.     int tblIdx = Math.abs(userId.hashCode() % TBL_CNT); 
  4.     return new ShardCfg(dbIdx, tblIdx); 

如上所示,我們計算庫序號的時候做了部分改動,我們使用分片鍵的前四位作為 Hash 值來計算庫序號。

這也是一種常用的方案,我們稱為基因法,即使用原分片鍵中的某些基因(例如前四位)作為庫的計算因子,而使用另外一些基因作為表的計算因子。

該方案也是網(wǎng)上不少的實踐方案或者是其變種,看起來非常巧妙的解決了問題,然而在實際生成過程中還是需要慎重。

筆者曾在云盤的空間模塊的分庫分表實踐中采用了該方案,使用 16 庫 100 表拆分數(shù)據(jù),上線初期數(shù)據(jù)正常。

然而當數(shù)據(jù)量級增長起來后,發(fā)現(xiàn)每個庫的用戶數(shù)量嚴重不均等,故猜測該方案存在一定的數(shù)據(jù)偏斜。

為了驗證觀點,進行如下測試,隨機 2 億個用戶 id(16 位的隨機字符串),針對不同的 M 庫 N 表方案,重復(fù)若干次后求平均值得到結(jié)論如下:

  1. 8庫100表 
  2. min=248305(dbIdx=2, tblIdx=64), max=251419(dbIdx=7, tblIdx=8), rate= 1.25%            √ 
  3. 16庫100表 
  4. min=95560(dbIdx=8, tblIdx=42), max=154476(dbIdx=0, tblIdx=87), rate= 61.65%           × 
  5. 20庫100表 
  6. min=98351(dbIdx=14, tblIdx=78), max=101228(dbIdx=6, tblIdx=71), rate= 2.93% 

我們發(fā)現(xiàn)該方案中,分庫數(shù)為 16,分表數(shù)為 100,數(shù)量最小行數(shù)僅為 10W 不到,但是最多的已經(jīng)達到了 15W+,最大數(shù)據(jù)偏斜率高達 61%。

按這個趨勢發(fā)展下去,后期很可能出現(xiàn)一臺數(shù)據(jù)庫容量已經(jīng)使用滿,而另一臺還剩下 30%+ 的容量。

該方案并不是一定不行,而是我們在采用的時候,要綜合分片鍵的樣本規(guī)則,選取的分片鍵前綴位數(shù),庫數(shù)量,表數(shù)量,四個變量對最終的偏斜率都有影響。

例如上述例子中,如果不是 16 庫 100 表,而是 8 庫 100 表,或者 20 庫 100 表,數(shù)據(jù)偏斜率都能降低到了 5% 以下的可接受范圍。

所以該方案的隱藏的"坑"較多,我們不僅要估算上線初期的偏斜率,還需要測算若干次翻倍擴容后的數(shù)據(jù)偏斜率。

例如你用著初期比較完美的 8 庫 100 表的方案,后期擴容成 16 庫 100 表的時候,麻煩就接踵而至。

常用姿勢四:剔除公因數(shù)法

還是基于錯誤案例一啟發(fā),在很多場景下我們還是希望相鄰的 Hash 能分到不同的庫中。就像 N 庫單表的時候,我們計算庫序號一般直接用 Hash 值對庫數(shù)量取余。

那么我們是不是可以有辦法去除掉公因數(shù)的影響呢?下面為一個可以考慮的實現(xiàn)案例:

  1. public static ShardCfg shard(String userId) { 
  2.         int dbIdx = Math.abs(userId.hashCode() % DB_CNT); 
  3.         // 計算表序號時先剔除掉公約數(shù)的影響 
  4.         int tblIdx = Math.abs((userId.hashCode() / TBL_CNT) % TBL_CNT); 
  5.         return new ShardCfg(dbIdx, tblIdx); 

經(jīng)過測算,該方案的最大數(shù)據(jù)偏斜度也比較小,針對不少業(yè)務(wù)從 N 庫 1 表升級到 N 庫 M 表下,需要維護庫序號不變的場景下可以考慮。

常用姿勢五:一致性 Hash 法

一致性 Hash 算法也是一種比較流行的集群數(shù)據(jù)分區(qū)算法,比如 RedisCluster 即是通過一致性 Hash 算法,使用 16384 個虛擬槽節(jié)點進行每個分片數(shù)據(jù)的管理。

關(guān)于一致性 Hash 的具體原理這邊不再重復(fù)描述,讀者可以自行翻閱資料。這邊詳細介紹如何使用一致性 Hash 進行分庫分表的設(shè)計。

我們通常會將每個實際節(jié)點的配置持久化在一個配置項或者是數(shù)據(jù)庫中,應(yīng)用啟動時或者是進行切換操作的時候會去加載配置。

配置一般包括一個[StartKey,Endkey)的左閉右開區(qū)間和一個數(shù)據(jù)庫節(jié)點信息,例如:

示例代碼:

  1. private TreeMap<Long, Integer> nodeTreeMap = new TreeMap<>(); 
  2.  
  3. @Override 
  4. public void afterPropertiesSet() { 
  5.     // 啟動時加載分區(qū)配置 
  6.     List<HashCfg> cfgList = fetchCfgFromDb(); 
  7.     for (HashCfg cfg : cfgList) { 
  8.         nodeTreeMap.put(cfg.endKey, cfg.nodeIdx); 
  9.     } 
  10.  
  11. public ShardCfg shard(String userId) { 
  12.     int hash = userId.hashCode(); 
  13.     int dbIdx = nodeTreeMap.tailMap((long) hash, false).firstEntry().getValue(); 
  14.     int tblIdx = Math.abs(hash % 100); 
  15.     return new ShardCfg(dbIdx, tblIdx); 

我們可以看到,這種形式和上文描述的 Range 分表非常相似,Range 分庫分表方式針對分片鍵本身劃分范圍,而一致性 Hash 是針對分片鍵的 Hash 值進行范圍配置。

正規(guī)的一致性 Hash 算法會引入虛擬節(jié)點,每個虛擬節(jié)點會指向一個真實的物理節(jié)點。

這樣設(shè)計方案主要是能夠在加入新節(jié)點后的時候,可以有方案保證每個節(jié)點遷移的數(shù)據(jù)量級和遷移后每個節(jié)點的壓力保持幾乎均等。

但是用在分庫分表上,一般大部分都只用實際節(jié)點,引入虛擬節(jié)點的案例不多。

主要有以下原因:

  • 應(yīng)用程序需要花費額外的耗時和內(nèi)存來加載虛擬節(jié)點的配置信息。如果虛擬節(jié)點較多,內(nèi)存的占用也會有些不太樂觀。
  • 由于 MySQL 有非常完善的主從復(fù)制方案,與其通過從各個虛擬節(jié)點中篩選需要遷移的范圍數(shù)據(jù)進行遷移,不如通過從庫升級方式處理后再刪除冗余數(shù)據(jù)簡單可控。
  • 虛擬節(jié)點主要解決的痛點是節(jié)點數(shù)據(jù)搬遷過程中各個節(jié)點的負載不均衡問題,通過虛擬節(jié)點打散到各個節(jié)點中均攤壓力進行處理。

而作為 OLTP 數(shù)據(jù)庫,我們很少需要突然將某個數(shù)據(jù)庫下線,新增節(jié)點后一般也不會從 0 開始從其他節(jié)點搬遷數(shù)據(jù),而是前置準備好大部分數(shù)據(jù)的方式,故一般來說沒有必要引入虛擬節(jié)點來增加復(fù)雜度。

常見擴容方案

①翻倍擴容法

翻倍擴容法的主要思維是每次擴容,庫的數(shù)量均翻倍處理,而翻倍的數(shù)據(jù)源通常是由原數(shù)據(jù)源通過主從復(fù)制方式得到的從庫升級成主庫提供服務(wù)的方式。故有些文檔將其稱作"從庫升級法"。

理論上,經(jīng)過翻倍擴容法后,我們會多一倍的數(shù)據(jù)庫用來存儲數(shù)據(jù)和應(yīng)對流量,原先數(shù)據(jù)庫的磁盤使用量也將得到一半空間的釋放。

如下圖所示:

具體的流程大致如下:

時間點 t1:為每個節(jié)點都新增從庫,開啟主從同步進行數(shù)據(jù)同步。

時間點 t2:主從同步完成后,對主庫進行禁寫。

此處禁寫主要是為了保證數(shù)據(jù)的正確性。若不進行禁寫操作,在以下兩個時間窗口期內(nèi)將出現(xiàn)數(shù)據(jù)不一致的問題:

  • 斷開主從后,若主庫不禁寫,主庫若還有數(shù)據(jù)寫入,這部分數(shù)據(jù)將無法同步到從庫中。
  • 應(yīng)用集群識別到分庫數(shù)翻倍的時間點無法嚴格一致,在某個時間點可能兩臺應(yīng)用使用不同的分庫數(shù),運算到不同的庫序號,導(dǎo)致錯誤寫入。

時間點 t3:同步完全完成后,斷開主從關(guān)系,理論上此時從庫和主庫有著完全一樣的數(shù)據(jù)集。

時間點t4:從庫升級為集群節(jié)點,業(yè)務(wù)應(yīng)用識別到新的分庫數(shù)后,將應(yīng)用新的路由算法。

一般情況下,我們將分庫數(shù)的配置放到配置中心中,當上述三個步驟完成后,我們修改分庫數(shù)進行翻倍,應(yīng)用生效后,應(yīng)用服務(wù)將使用新的配置。

這里需要注意的是,業(yè)務(wù)應(yīng)用接收到新的配置的時間點不一定一致,所以必定存在一個時間窗口期,該期間部分機器使用原分庫數(shù),部分節(jié)點使用新分庫數(shù)。這也正是我們的禁寫操作一定要在此步完成后才能放開的原因。

時間點 t5:確定所有的應(yīng)用均接受到庫總數(shù)的配置后,放開原主庫的禁寫操作,此時應(yīng)用完全恢復(fù)服務(wù)。

啟動離線的定時任務(wù),清除各庫中的約一半冗余數(shù)據(jù)。

為了節(jié)省磁盤的使用率,我們可以選擇離線定時任務(wù)清除冗余的數(shù)據(jù)。也可以在業(yè)務(wù)初期表結(jié)構(gòu)設(shè)計的時候,將索引鍵的 Hash 值存為一個字段。

那么以上述常用姿勢四為例,我們離線的清除任務(wù)可以簡單的通過 sql 即可實現(xiàn)(需要防止鎖住全表,可以拆分成若干個 id 范圍的子 sql 執(zhí)行):

  1. delete from db0.tbl0 where hash_val mod 4 <> 0;  
  2. delete from db1.tbl0 where hash_val mod 4 <> 1; 
  3. delete from db2.tbl0 where hash_val mod 4 <> 2; 
  4. delete from db3.tbl0 where hash_val mod 4 <> 3; 

具體的擴容步驟可參考下圖:

總結(jié):通過上述遷移方案可以看出,從時間點 t2 到 t5 時間窗口呢內(nèi),需要對數(shù)據(jù)庫禁寫,相當于是該時間范圍內(nèi)服務(wù)器是部分有損的,該階段整體耗時差不多是在分鐘級范圍內(nèi)。若業(yè)務(wù)可以接受,可以在業(yè)務(wù)低峰期進行該操作。

當然也會有不少應(yīng)用無法容忍分鐘級寫入不可用,例如寫操作遠遠大于讀操作的應(yīng)用,此時可以結(jié)合 canel 開源框架進行窗口期內(nèi)數(shù)據(jù)雙寫操作以保證數(shù)據(jù)的一致性。

該方案主要借助于 MySQL 強大完善的主從同步機制,能在事前提前準備好新的節(jié)點中大部分需要的數(shù)據(jù),節(jié)省大量的人為數(shù)據(jù)遷移操作。

但是缺點也很明顯,一是過程中整個服務(wù)可能需要以有損為代價,二是每次擴容均需要對庫數(shù)量進行翻倍,會提前浪費不少的數(shù)據(jù)庫資源。

②一致性 Hash 擴容

我們主要還是看下不帶虛擬槽的一致性 Hash 擴容方法,假如當前數(shù)據(jù)庫節(jié)點 DB0 負載或磁盤使用過大需要擴容,我們通過擴容可以達到例如下圖的效果。

下圖中,擴容前配置了三個 Hash 分段,發(fā)現(xiàn)[-Inf,-10000)范圍內(nèi)的的數(shù)據(jù)量過大或者壓力過高時,需要對其進行擴容。

主要步驟如下:

  • 時間點 t1:針對需要擴容的數(shù)據(jù)庫節(jié)點增加從節(jié)點,開啟主從同步進行數(shù)據(jù)同步。
  • 時間點 t2:完成主從同步后,對原主庫進行禁寫。此處原因和翻倍擴容法類似,需要保證新的從庫和原來主庫中數(shù)據(jù)的一致性。
  • 時間點 t3:同步完全完成后,斷開主從關(guān)系,理論上此時從庫和主庫有著完全一樣的數(shù)據(jù)集。
  • 時間點 t4:修改一致性 Hash 范圍的配置,并使應(yīng)用服務(wù)重新讀取并生效。
  • 時間點 t5:確定所有的應(yīng)用均接受到新的一致性 Hash 范圍配置后,放開原主庫的禁寫操作,此時應(yīng)用完全恢復(fù)服務(wù)。

啟動離線的定時任務(wù),清除冗余數(shù)據(jù)。

可以看到,該方案和翻倍擴容法的方案比較類似,但是它更加靈活,可以根據(jù)當前集群每個節(jié)點的壓力情況選擇性擴容,而無需整個集群同時翻倍進行擴容。

小結(jié)

本文主要描述了我們進行水平分庫分表設(shè)計時的一些常見方案。

我們在進行分庫分表設(shè)計時,可以選擇例如范圍分表,Hash 分表,路由表,或者一致性 Hash 分表等各種方案。進行選擇時需要充分考慮到后續(xù)的擴容可持續(xù)性,最大數(shù)據(jù)偏斜率等因素。

文中也列舉了一些常見的錯誤示例,例如庫表計算邏輯中公約數(shù)的影響,使用前若干位計算庫序號常見的數(shù)據(jù)傾斜因素等等。

我們在實際進行選擇時,一定要考慮自身的業(yè)務(wù)特點,充分驗證分片鍵在各個參數(shù)因子下的數(shù)據(jù)偏斜程度,并提前規(guī)劃考慮好后續(xù)擴容的方案。

作者:Han Lei

編輯:陶家龍

出處:轉(zhuǎn)載自公眾號vivo互聯(lián)網(wǎng)技術(shù)(ID:vivoVMIC)

 

責任編輯:武曉燕 來源: vivo互聯(lián)網(wǎng)技術(shù)
相關(guān)推薦

2020-12-29 09:23:40

分庫分表訂單

2020-08-16 11:46:33

SaaS數(shù)據(jù)技術(shù)

2020-08-24 08:30:41

DevOps運維開發(fā)

2019-07-25 13:13:25

AndroidHandler消費機制

2022-07-03 10:21:19

MYSQL存儲緩沖池

2019-04-18 14:06:35

MySQL分庫分表數(shù)據(jù)庫

2020-07-30 17:59:34

分庫分表SQL數(shù)據(jù)庫

2017-05-23 14:56:15

柯潔人機大戰(zhàn)AlphaGo

2020-06-24 09:00:43

分庫分表MySQL

2019-11-12 09:54:20

分庫分表數(shù)據(jù)

2020-09-27 08:00:49

分庫分表

2024-05-21 08:40:21

分庫分表源碼

2024-08-13 17:09:00

架構(gòu)分庫分表開發(fā)

2022-06-30 07:34:46

分庫分表外賣訂單系統(tǒng)

2022-06-22 07:32:53

Sharding分庫數(shù)據(jù)源

2021-08-31 20:21:11

VitessMySQL分庫

2023-08-11 08:59:49

分庫分表數(shù)據(jù)數(shù)據(jù)庫

2020-11-18 09:39:02

MySQL數(shù)據(jù)庫SQL

2024-10-31 08:50:14

2013-04-01 10:27:37

程序員失業(yè)
點贊
收藏

51CTO技術(shù)棧公眾號

男人天堂视频在线| 给我看免费高清在线观看| 麻豆av免费在线观看| 久久99最新地址| 久久久久久久电影一区| 手机免费看av| 日本少妇精品亚洲第一区| 午夜精品影院在线观看| 日韩三级电影| 免费观看的毛片| 日韩激情av在线| 欧美刺激性大交免费视频| 中文字幕一区二区久久人妻网站| 欧美jizz18| 无码av免费一区二区三区试看| 视频一区视频二区视频三区高| www.天天干.com| 日本女人一区二区三区| 欧美极品美女视频网站在线观看免费| 美女久久久久久久久久| 日本成人手机在线| 欧美午夜寂寞影院| 日本xxxxxxxxxx75| 国产丝袜在线| 国产农村妇女毛片精品久久麻豆| caoporen国产精品| 一区二区视频播放| 美女精品网站| 国模极品一区二区三区| 最新av电影网站| 国产精品欧美日韩一区| 亚洲国产精品免费| 久久无码人妻一区二区三区| 91国内外精品自在线播放| 婷婷中文字幕综合| 91成人综合网| jizz性欧美10| 国产精品卡一卡二| 日韩高清av电影| 亚洲色图21p| 成人国产精品免费观看动漫| 99高清视频有精品视频| 国产精品毛片一区二区在线看舒淇 | 精品国产91九色蝌蚪| 天天久久综合网| 日韩成人一区| 欧美二区在线观看| 爱爱爱爱免费视频| 九九久久国产| 欧美日韩国产一级片| 亚洲精品高清无码视频| 91看片一区| 91福利国产成人精品照片| 欧美少妇性生活视频| 神马午夜在线视频| 欧美日韩午夜剧场| 欧美 日韩 国产一区| 日本不良网站在线观看| 午夜不卡在线视频| 男人日女人bb视频| japanese23hdxxxx日韩| 91久久奴性调教| 蜜臀av免费观看| 日韩第二十一页| 制服丝袜亚洲色图| 麻豆精品国产传媒| 国产成人在线中文字幕| 精品一区精品二区| 日本一区二区视频在线播放| 秋霞欧美视频| 久热在线中文字幕色999舞| 午夜爽爽爽男女免费观看| 日韩电影免费在线观看| 久久久999精品| 久久激情免费视频| 国产精品久久久久久久久久妞妞| 国产91色在线免费| 中文字幕人妻色偷偷久久| 精品一区二区免费视频| 国产富婆一区二区三区| 青青草手机在线| 国产精品乱码久久久久久| 男女h黄动漫啪啪无遮挡软件| 美女尤物在线视频| 色欲综合视频天天天| 在线观看国产一级片| 深夜激情久久| 亚洲美女av黄| 日韩欧美123区| 夜夜精品视频| 国产精品色午夜在线观看| 精品黑人一区二区三区国语馆| www.亚洲人| 亚洲最新在线| h片在线观看视频免费| 婷婷丁香久久五月婷婷| 少妇一级淫免费播放| 97精品久久| 中文字幕欧美日韩| 精品在线视频免费| 免费的国产精品| 激情久久av| 色哟哟免费在线观看| 午夜av电影一区| 国产精品v日韩精品v在线观看| 欧美变态网站| 久久视频这里只有精品| 黄瓜视频在线免费观看| 国产呦萝稀缺另类资源| 久久影视中文粉嫩av| 18videosex性欧美麻豆| 在线观看视频一区二区欧美日韩| 韩国三级hd中文字幕有哪些| 精品午夜久久| 97激碰免费视频| 国产精品久久久久久久久久久久久久久久| av亚洲精华国产精华| 最新黄色av网站| 久久久成人av毛片免费观看| 亚洲成人国产精品| 日韩福利小视频| 人人超碰91尤物精品国产| 国产私拍一区| 亚洲小说区图片区都市| 欧美日韩一区小说| 亚洲最大成人网站| 亚洲一区二区免费看| 成人黄色片视频网站| 黄视频在线观看网站| 欧美性猛交一区二区三区精品| 亚洲观看黄色网| 国产综合自拍| 999国产视频| 成人黄色网址| 欧美日韩的一区二区| 美国黑人一级大黄| 麻豆九一精品爱看视频在线观看免费| 国产一区精品在线| 欧美6一10sex性hd| 日韩欧美高清在线| 黑人巨大精品一区二区在线| 久久97超碰国产精品超碰| 天天爽天天狠久久久| 欧美日韩国产v| 亚洲人午夜精品免费| 精品国产午夜福利| 91麻豆福利精品推荐| 欧美精品99久久| 香蕉久久夜色精品国产使用方法 | 我家有个日本女人| 精品一区二区av| 91免费网站视频| 国产精品成人3p一区二区三区| 日韩综合中文字幕| 91久久精品无码一区二区| 国产精品精品国产色婷婷| 一路向西2在线观看| 久久人人88| 91中文字幕在线| 日本aa在线| 亚洲电影中文字幕| 国产成人一级片| 欧美—级在线免费片| 亚洲 欧美 另类人妖| 亚洲激情久久| 国产传媒一区二区| 中文字幕人成乱码在线观看| 国产一区二区三区欧美| 亚洲无码久久久久久久| 亚洲精品中文在线| 中文字幕第3页| 日韩精品一级二级| 国产人妻互换一区二区| 大伊香蕉精品在线品播放| 91精品国产免费久久久久久 | 国产精品免费视频一区| 中文字幕精品一区二区三区在线| 伊人久久大香线| 国产一区在线免费观看| 日韩国产网站| 久久中国妇女中文字幕| 欧美一区二区公司| 日本久久一区二区三区| 麻豆精品一区二区三区视频| 91视频免费播放| 色婷婷成人在线| 狠狠爱综合网| 精品中文字幕一区| 日韩成人在线一区| 51久久精品夜色国产麻豆| 1024视频在线| 精品国产a毛片| 中文字幕在线观看第二页| 亚洲一区二区三区激情| 久久精品—区二区三区舞蹈| 国产一区视频导航| 午夜精品久久久内射近拍高清| 欧美第一精品| 麻豆精品视频| 亚洲一区二区三区免费| 国产成人极品视频| 亚洲综合影视| 在线观看日韩欧美| 人成网站在线观看| 欧美日韩精品一区二区三区四区 | 精品国产大片大片大片| av不卡免费电影| 欧美性受xxxxxx黑人xyx性爽| 亚洲毛片网站| 成人免费看片视频在线观看| 深爱激情综合| 精品卡一卡二| 在线精品自拍| 国产主播欧美精品| gay欧美网站| 欧美激情视频网站| 米奇精品一区二区三区| 亚洲欧美精品一区| 亚洲精品久久久久久动漫器材一区 | 一区二区三区国产视频| 国产91麻豆视频| 欧美一卡二卡在线观看| 中文字幕乱码中文字幕| 欧美色道久久88综合亚洲精品| 欧美日韩一级大片| 中文字幕五月欧美| 中文字幕成人动漫| 久久综合一区二区| 亚洲一级av无码毛片精品| 国产精品自拍毛片| 手机免费看av网站| 麻豆成人在线观看| 四季av一区二区| 久久久久国产精品午夜一区| 18禁网站免费无遮挡无码中文| 欧美成人精品| 成人午夜免费剧场| 你懂的国产精品永久在线| 中文字幕一区二区中文字幕| 青青草综合网| 伊人情人网综合| 日韩欧美网站| 在线一区亚洲| 我不卡伦不卡影院| 干日本少妇视频| 女生裸体视频一区二区三区| 91成人在线视频观看| 午夜天堂精品久久久久| 8x8ⅹ国产精品一区二区二区| 香蕉av一区二区 | 国产欧美在线| www.中文字幕在线| 亚洲影视综合| 国产激情在线观看视频| 三级成人在线视频| 久久久久久久久久久久91| 日本在线播放一区二区三区| 成年人三级黄色片| 国产一区二区三区四区五区美女 | 免费av网站观看| 亚洲成人激情在线观看| 色欲av伊人久久大香线蕉影院| 亚洲精品国产品国语在线| 青青草观看免费视频在线 | 91最新在线视频| 九九九久久国产免费| 丁香花在线高清完整版视频 | 99久久久无码国产精品免费蜜柚 | 一区二区三区欧美激情| 国产一级在线免费观看| 精品久久久久久中文字幕| 日韩精品成人免费观看视频| 欧美三级电影在线观看| 国产丰满美女做爰| 亚洲精品成人久久电影| 国产小视频在线| xxav国产精品美女主播| 日本高清在线观看视频| 热99久久精品| 国产美女精品视频免费播放软件| 国产精品久久久久久久久婷婷| 久久不见久久见免费视频7| 亚洲欧洲日韩综合二区| 禁久久精品乱码| 国产视频一区二区视频| 国产一区不卡精品| a级在线观看视频| 亚洲欧洲三级电影| 日本少妇裸体做爰| 欧美日韩一区精品| 亚洲精品国产精品乱码不卡| 一本久久综合亚洲鲁鲁| 1区2区在线观看| 欧美中文字幕在线视频| 国产精品va视频| 日本一区二区三区视频在线观看| 亚洲国产精品久久久久蝴蝶传媒| 免费在线激情视频| 国产乱码精品一区二区三区五月婷| 日本japanese极品少妇| 亚洲欧洲av另类| 日韩熟女一区二区| 欧美va亚洲va在线观看蝴蝶网| 免费理论片在线观看播放老| 欧美精品videos| 黄色成人小视频| 精品午夜一区二区| 中文一区一区三区免费在线观看| 波多野结衣乳巨码无在线| 国产一区二区三区精品视频| 中文字幕丰满孑伦无码专区| 一区二区三区欧美视频| 中文字幕av片| 亚洲男人天堂2023| 91豆花视频在线播放| 亚洲一区精品电影| 成人精品久久| 亚洲熟妇av一区二区三区| 成人免费视频视频| 日本精品人妻无码77777| 欧美性猛交一区二区三区精品| 手机看片福利在线观看| 欧美国产日韩免费| 精品国产亚洲日本| 中文字幕av日韩精品| 日本成人中文字幕| 色一情一交一乱一区二区三区| 婷婷综合久久一区二区三区| 亚洲黄色小说网址| 九九热这里只有精品免费看| 视频欧美精品| 中文字幕乱码一区二区三区| 日本欧美一区二区在线观看| 欧美老熟妇乱大交xxxxx| 丁香五六月婷婷久久激情| 少妇高潮一区二区三区99小说| 欧美激情亚洲另类| 亚洲国产欧美国产第一区| 国产av第一区| 国产美女娇喘av呻吟久久| 亚洲xxxx3d动漫| 3d成人h动漫网站入口| 黄色网页在线免费看| 成人a视频在线观看| 国产精品成人一区二区不卡| 天天看片天天操| 亚洲天堂精品在线观看| 国产农村老头老太视频| 久久夜色精品国产| 亚洲综合影院| 免费在线观看视频a| 99re8在线精品视频免费播放| 日韩污视频在线观看| 亚洲精品国产福利| 免费成人美女女| 亚洲精品久久久久久一区二区| 美女网站一区二区| 很污很黄的网站| 欧美成人a视频| 国产乱码精品一区二三赶尸艳谈| 久久久精品国产一区二区三区| 久久国产高清| 特黄一区二区三区| 欧美一二三四在线| 91黄页在线观看| 日韩欧美一区二区三区久久婷婷| 免费在线观看不卡| 免费中文字幕在线| 日韩精品久久久久久福利| 欧美影视资讯| 中国老女人av| 97se狠狠狠综合亚洲狠狠| 久久久精品毛片| 久久91精品国产91久久跳| 老牛精品亚洲成av人片| 国产男女激情视频| 亚洲三级电影全部在线观看高清| 亚洲精品福利网站| 秋霞午夜一区二区| 在线观看国产精品入口| 少妇被狂c下部羞羞漫画| 在线影视一区二区三区| 高潮毛片在线观看| 久久久人人爽| 极品美女销魂一区二区三区免费| 久久久久亚洲天堂| 国产亚洲精品91在线| 欧一区二区三区| 99精品视频播放| 亚洲精品成人悠悠色影视| 青青草免费观看免费视频在线| 91精品免费视频| 一区二区黄色| 免费中文字幕日韩| 亚洲免费视频网站| 国产成人澳门| 57pao国产成永久免费视频| 黑丝美女久久久| 4438x成人网全国最大| 亚洲高清不卡一区|