海量數據分庫分表方案(一)算法方案

本文主要描述分庫分表的算法方案、按什麼規則劃分。按部就班比較目前出現的幾種規則方式,最後第五種增量遷移方案是我設想和推薦的方式。後續章再講述技術選型和分庫分表後帶來的問題。java

背景

隨着業務量遞增,數據量遞增,一個表將會存下大量數據,在一個表有一千萬行數據時,經過sql優化、提高機器性能還能承受。爲了將來長遠角度應在必定程度時進行分庫分表,如出現數據庫性能瓶頸、增長字段時須要耗時比較長的時間的狀況下。解決獨立節點承受全部數據的壓力,分佈多個節點,提供容錯性,沒必要一個掛整個系統不能訪問。node

目的

本文講述的分庫分表的方案,是基於水平分割的狀況下,選擇不一樣的規則,比較規則的優缺點。 通常網上就前三種,正常一點的會說第四種,但不是很完美,前面幾種遷移數據都會很大影響,推薦我認爲比較好的方案五。git

  • 方案一:對Key取模,除數逐步遞增
  • 方案二:按時間劃分
  • 方案三:按數值範圍
  • 方案四:一致性Hash理念——平均分佈方案(大衆點評用這種,200G而且一步到位)
  • 方案五:一致性Hash理念——按迭代增長節點(爲了方便增量遷移)
  • 方案六:一致性Hash理念——按範圍分庫(迭代遷移)

點贊再看,關注公衆號:【地藏思惟】給你們分享互聯網場景設計與架構設計方案 掘金:地藏Kelvin https://juejin.im/user/5d67da8d6fb9a06aff5e85f7算法

方案選擇


方案一:對Key取模,除數逐步遞增

公式:key mod x (x爲天然數)sql

Key能夠爲主鍵,也能夠爲訂單號,也能夠爲用戶id,這個須要根據場景決定,哪一個做爲查詢條件機率多用哪一個。數據庫

優勢:服務器

  • 按需增長庫、表,逐步增長
  • 分佈均勻,每一片差別很少

缺點:架構

  • 不少時候會先從2開始分兩個庫逐級遞增,而後分3個、4個、5個。如在mod 3 變 mod 5的狀況下,取模後的大部分的數據的取模結果會變化,如key=3時,mod 3=0,忽然改變爲mod 5=3,則將會從第0表遷移到第3表,將會形成不少數據多重複移動位置。
  • 會重複遷移數據,當分2個時,有數據A在第0號表,分3個時數據A去了第1號表、到分4個時數據A會回到第0號表

方案二:按時間劃分

能夠按日、按月、按季度。分佈式

tb_20190101
tb_20190102
tb_20190103
……

這個算法要求在訂單號、userId上添加年月日或者時間戳,或者查詢接口帶上年月日,才能定位在哪一個分片。函數

優勢:

  • 數據按時間連續
  • 看數據增加比較直觀

缺點:

  • 由於考慮到歷史數據一開始沒分庫分表後續進行分庫分表時,歷史數據的訂單號不必定有時間戳,歷史數據可能爲自增或者自定義算法得出的分佈式主鍵,致使查詢時必需要上游系統傳訂單號、建立時間兩個字段。
  • 若上游系統沒有傳時間,或者上游系統的建立時間與當前系統對應訂單的建立時間不在同一天的狀況下,則當前數據庫表的數據記錄須要有時間字段。由於上游系統只傳訂單號,這個時候須要獲取建立時間,當前系統就必需要有一個主表維護訂單號和建立時間的關係,而且每次查詢時都須要先查當前系統主表,再查具體表,這樣就會消耗性能。
  • 分佈不必定均勻:每個月增加數據不同,可能會有些月份多有些月份少

推薦使用場景:日誌記錄


方案三:按數值範圍

表0 [0,10000000) 
表1 [10000000,20000000)
表2 [20000000,30000000)
表3 [30000000,40000000)
……

優勢:

  • 分佈均勻

缺點:

  • 由於未知最大值,因此沒法用時間戳做爲key,這個方法不能用表的自增主鍵,由於每一個表都自增數量不是統一維護。因此須要有一個發號器或發號系統作統一維護key自增的地方。

說後續推薦的方案中先簡單說說一致性hash

先說一下一致性hash,有些文章說一致性Hash是一種算法,我認爲它並非具體的計算公式,而是一個設定的思路。

1.先假定一個環形Hash空間,環上有固定最大值和最小值,頭尾相連,造成一個閉環,如int,long的最大值和最小值。 不少文章會假定2^32個位置,最大值爲2^32-1最小值爲0,即0~(2^32)-1的數字空間,他們只是按照經常使用的hash算法舉例,真實分庫分表的狀況下不是用這個數字,因此我纔會認爲一致性hash算法實際上是一個理念,並非真正的計算公式。 以下圖

2. 設計一個公式函數 value = hash(key),這個公式將會有最大值和最小值,如 key mod 64 = value; 這個公式最大爲64,最小爲0。而後把數據都落在環上。

3. 設定節點node。設定節點的方式如對ip進行hash,或者自定義固定值(後續方案是使用固定值)。而後node逆時針走,直到前一個節點爲止,途經value=hash(key)的全部數據的都歸這個節點管。 如 hash(node1)=10,則hash(key)=0~10的數據都歸node1管。

歸納

這裏不詳細說明這個理論,它主要表達的意思是固定好最大值,就再也不修改最大值到最小值範圍,後續只修改節點node的位置和增長node來達到減小每一個node要管的數據,以達到減小壓力。

備註:
* 不推薦對ip進行hash,由於可能會致使hash(ip)得出的結果很大,例如得出60,若這個節點的前面沒有節點,則60號位置的這個節點須要管大部分的數據了。
* 最好生成key的方式用雪花算法snowFlake來作,至少要是不重複的數字,也不要用自增的形式。
* 推薦閱讀銅板街的方案 訂單號末尾添加user%64

方案四:一致性Hash理念——平均分佈方案

利用一致性hash理論,分庫選擇hash(key)的公式基準爲 value= key mod 64,分表公式value= key / 64 mod 64,key爲訂單號或者userId等這類常常查詢的主要字段。(後續會對這個公式有變化) 咱們假定上述公式,則能夠分64個庫,每一個庫64個表,假設一個表1千萬行記錄。則最大64 * 64 * 1000萬數據,我相信不會有這一天的到來,因此咱們以這個做爲最大值比較合理,甚至選擇32 * 32均可以。

由於前期用不上這麼多個表,一開始創建這麼多表每一個表都insert數據,會形成浪費機器,因此在咱們已知最大值的狀況下,咱們從小的數字開始使用,因此咱們將對上述計算得出的value進行分組。

分組公式:64 = 每組多少個count  * group須要分組的個數 
數據所在環的位置(也就是在哪一個庫中):value = key mode 64 / count  * count

如下舉例爲16組,也就是16個庫,group=16 這個時候庫的公式爲value = key mode 64 / 4 * 4,除以4後,會截取小數位得出一個整數,而後 * 4倍,就是數據所在位置。

// 按4個爲一組,分兩個表
count = 4:
Integer dbValue = userId % 64 / count * count ;

hash(key)在0~3之間在第0號庫
hash(key)在4~7之間在第4號庫
hash(key)在8~11之間在第8號庫
……

備註:其實一開始能夠64個爲一組就是一個庫,後續變化32個爲一組就是兩個庫,從一個庫到兩個庫,再到4個庫,逐步遞進。

從分1庫開始擴容的迭代:

下圖中舉例分16組後,變爲分到32組,須要每一個庫都拿出一半的數據遷移到新數據,擴容直到分64個組。

能夠看到當須要進行擴容一倍時須要遷移一半的數據量,以2^n遞增,因此進行影響範圍會比較大。

優勢:

  • 若是直接拆分32組,那麼就比較一勞永逸
  • 若是數據量比較大,未作過度表能夠用一勞永逸方式。
  • 分佈均勻
  • 遷移數據時不須要像方案一那樣大部分的數據都須要進行遷移並有重複遷移,只須要遷移一半

缺點:

  • 能夠擴展,可是影響範圍大。
  • 遷移的數據量比較大,雖然不像方案一那樣大部分數據遷移,當前方案每一個表或庫都須要一半數據的遷移。
  • 若要一勞永逸,則須要總體停機來遷移數據

方案五:一致性Hash理念——按迭代增長節點

(我認爲比較好的方案)

一致性hash方案結合比較範圍方案,也就是方案三和方案四的結合。

解析方案四問題所在

方案四是設定最大範圍64,按2^n指數形式從1增長庫或者表數量,這樣帶來的是每次拆分進行遷移時會影響當整體數據量的1/2的數據,影響範圍比較大,因此要麼就直接拆分到32組、64組一勞永逸,要麼每次1/2遷移。

方案四對應遷移方案:

  1. 第一種是停機遷移數據,成功後,再從新啓動服務器。影響範圍爲全部用戶,時間長。
  2. 第二種是把數據源切到從庫,讓用戶只讀,主庫遷移數據,成功後再切到主庫,雖然用戶能適用,影響業務增量
  3. 第三種是設定數據源根據規則讓一半的用戶能只讀,另外一半的用戶能讀能寫,由於方案四遷移都是影響通常的數據的,因此最多能作到這個方式。

方案五詳解

如今我想方法時,保持一致性hash理念,1個1個節點來增長,而不是方案四的每次增長2^n-n個節點。可是代碼上就須要進行對新節點內的數據hash值判斷。

咱們基於已經發生過1次迭代分了兩個庫的狀況來作後續迭代演示,首先看看已經拆分兩個庫的狀況:

數據落在第64號庫名爲db64和第32號庫名爲db32


迭代二: 區別與方案四直接增長兩個節點,咱們只增長一個節點,這樣遷移數據時由本來影響1/2的用戶,將會隻影響1/4的用戶。

在代碼中,咱們先把分組從32個一組改成16個一組,再給代碼特殊處理 0~16的去到新的節點 16~32走回原來的32號節點 32~63走回原來64號節點 因此下面就要對節點特殊if else

// 按32改成16個爲一組,分兩變爲4個庫
count = 16;
Integer dbValue = userId  % 64 / count * count ;
if(dbValue<16){
    // 上一個迭代這些數據落在db32中,如今走新增節點名爲db16號的那個庫
    dbValue =  16;
    return dbValue;
} else {
    // 按原來規則走
    return dbValue;
}

迭代三:

這樣就能夠分迭代完成方案四種的一輪的遷移

遷移前能夠先上線,增長一段開關代碼,請求接口特殊處理hash值小於16的訂單號或者用戶號,這樣就只會影響1/4的人

// 在請求接口中增長邏輯
    public void doSomeService(Integer userId){
        if(遷移是否完成的開關){
            // 若是未完成
            Integer dbValue = userId  % 64 / count * count ;
            if(dbValue<16){
                //這部分用戶暫時不能走下面的邏輯
                return ;
            }
        }
        return dbValue;
    }
}
// 在分片時按32個爲一組,分兩個庫
count = 16;
Integer dbValue = userId  % 64 / count * count ;
if(dbValue<16){
    // 上一個迭代這些數據落在db32中,有一半須要走新增節點名爲db16號的那個庫
    if(遷移是否完成的開關){
        // 若是已經完成,就去db16的庫
        dbValue =  16;
    }
    return dbValue;
} else {
    // 按原來規則走
    return dbValue;
}

如此類推,下一輪總共8個節點時,每次遷移只須要遷移1/8。

其實也能夠在第一個迭代時,不選擇dbValue小於16號的來作。直接8個分一組,只選擇dbValue<8的來作,這樣第一個迭代的影響範圍也會比較案例中小。上述案例用16只是比較好演示

優勢:

  • 易於擴展
  • 數據逐漸增大過程當中,慢慢增長節點
  • 影響用戶數量少
  • 按迭代進行,減小風險
  • 遷移時間短,如敏捷迭代思想

缺點:

  • 一段時間下不均勻

方案六:一致性Hash理念——按範圍分庫(迭代遷移)

如同上述方案五是方案四+方案一,能夠達到逐步遷移數據,還有一種方案。就是方案四+方案三,只是不用取模後分組。

userId % 64 / count * count

由於上述公式,得出結果中,不必定每一片數據都是平均分佈的。其實咱們能夠取模後,按範圍劃分分片,以下公式。

第一片 0<userId  % 64<15
 第二片 16<userId  % 64<31
 第三片 32<userId  % 64<47
 第四片 48<userId  % 64<63

固然範圍能夠自定義,看取模後落入哪一個值的數量比較多,就切某一片數據就行了,具體就不畫圖了,跟方案四相似。

由於遷移數據的緣由,方案四中,若是數據量大,達到1000萬行記錄,每次遷移都須要遷移不少的數據,因此不少公司會盡早分庫分表。

可是在業務優先狀況下,一直迭代業務,數據一進達到不少的狀況下16分支一也是不少的數據時,咱們就能夠用一致性Hash理念--按範圍分庫


歡迎關注

個人公衆號 :地藏思惟

掘金:地藏Kelvin

簡書:地藏Kelvin

CSDN:地藏Kelvin

個人Gitee: 地藏Kelvin https://gitee.com/dizang-kelvin

相關文章
相關標籤/搜索