分庫分表的基本原理

vipshop_fin_dev發表於2018-08-05
  1. 分庫分表的場景
    首先提供一組mysql的效能資料:對於一個MySQL例項,CRUD上限經驗值如下:
    Query:3w/s
    Insert:2w/s
    Update:8000/s
    Delete:8000/s
    出處:https://blog.csdn.net/weixin_41545330/article/details/80734844
    而大型的網際網路應用中,資料量是非常龐大的,上億甚至是數十億都是非常常見的事情,同時也會伴隨著非常高的讀寫併發,很容易就會突破單個資料庫例項的效能上限。單單的讀寫分離(master->slave)模式,所有的寫併發還是落在同一個主庫上,無法擴充套件寫的併發能力,同時也只能有限地擴充套件讀的併發能力,因為受master的同步能力影響,掛載的從庫數量是有限的。
    分庫分表的思想是把一個資料庫分成多個部分,存放在多個資料庫上,把一個大表拆成多張小表,從而有效緩解單個資料庫或者單個表的讀寫壓力,讓資料庫服務獲得無限擴充套件的能力。

  2. 常見的分庫分表策略
    2.1 分庫分表的策略有垂直劃分和水平劃分。先上圖:
    這裡寫圖片描述
    圖片出處https://www.cnblogs.com/panxuejun/p/5958879.html
    2.2 垂直劃分
    垂直劃分是指可以考慮將業務上相近,並且具有相近資料增長速率、訪問壓力的資料放在同一個資料來源裡,反之分到不同的資料來源。舉例:一個線上教育的的應用包含使用者模組、刷題模組,課件模組等,可以按照不同的模組分別分到不同的資料庫。而使用者模組包含的資訊有賬號密碼、使用者姓名、性別、生日、星座、愛好、積分等。其中使用者登陸認證的訪問最頻繁,而生日、星座、愛好、積分等資料訪問的頻率會顯著低於賬號密碼,那麼又可以根據訪問的頻率不同而分到不同的表。如下表:
    這裡寫圖片描述
    2.3 水平劃分
    經過垂直分庫後,單個業務內資料量和併發量依舊非常龐大,則需要進一步進行水平劃分。比如提供刷題服務的功能模組,使用者提交的答案資料會非常驚人。水平劃分通常是先確定主表,將主表與其關聯表和間接關聯表劃分到同一個sharding。例如刷題模組,確定答題卡表為主表,那麼與其關聯的答案表也一起劃分到同一個sharding.
    2.4 常見的水平劃分策略
    常見的水平劃分策有連續分片和隨機分片。連續分片,比如確定userId為關鍵字,userId在(0,10000000]在shard_0,userId在(10000000,20000000]在shard_1。隨機分片,常常是通過對關鍵字進行hash取模進行分片,例如確定userId為關鍵字,劃分成32個分片,則index=userId%32.
    連續分片和隨機分片的的優缺點如下:
    擴容方面,連續分片更加容易擴容,當關鍵字增長到達某一個段的時候,新增新的分片即可,不需要進行資料遷移。而隨機分片在進行擴容的時候需要進行資料遷移。
    資料熱度方面,連續分片很多時候存在資料熱度不一致的問題,比如1年前的微博資料和現在的微博資料熱度差異巨大,連續分片的情況下會導致各個庫的訪問壓力不均勻。而隨機分片,資料熱度通常更加均勻。補充提一下,在隨機分片中,也可能會存在超級Id,這些Id的記錄遠遠超過其他Id,比如電商平臺,可能蘋果手機的訪問數遠超過其他商品,針對這些超級ID,通常是使用獨立的分片進行特殊處理。
    而對於隨機分片的資料遷移問題,也有一些優化的策略。比如採用一致性雜湊演算法進行分片,那麼每次擴容的時候,所要遷移的資料量則會大大減少。而如果資料量的上限是已知的,則還可以進一步進行優化,比如筆者做過一個業務,使用者基於手機號碼進行註冊登出,資料量最多也是14億,開始的時候直接劃分成64個邏輯的資料庫,由於前期資料量很少,這64個資料庫直接使用兩臺物理DB提供服務,等資料量上來以後,再增加物理機器,把其中的資料庫整個遷移到新的機器,避免行級別的遷移,這個過程邏輯分片數不會變。
    3. ID問題
    一旦資料庫被切分到多個物理結點上,我們將不能再依賴資料庫自身的主鍵生成機制。一方面,某個分割槽資料庫自生成的ID無法保證在全域性上是唯一的;另一方面,應用程式在插入資料之前需要先獲得ID,以便進行SQL路由.
    一些常見的主鍵生成策略:
    UUID/GUID(一般應用程式和資料庫均支援)
    Twitter的Snowflake(又名“雪花演算法”)
    MongoDB ObjectID(類似UUID的方式)
    Ticket Server(資料庫生存方式,Flickr採用的就是這種方式)

UUID/GUID方式,在資料庫中建立一個Sequence表,表的結構類似於:

CREATE TABLE `SEQUENCE` (  
    `table_name` varchar(18) NOT NULL,  
    `nextid` bigint(20) NOT NULL,  
    PRIMARY KEY (`table_name`)  
) ENGINE=InnoDB

每當需要為某個表的新紀錄生成ID時就從Sequence表中取出對應表的nextid,並將nextid的值加1後更新到資料庫中以備下次使用。但是意味著所有寫的壓力都會落到該表,容易成為效能瓶頸。優化的方式是,如果不要求id嚴格按照1的間隔來遞增,可以每次自增100,應用每次取一段資料回去快取到本地內容,並且為新請求分配id,這樣可以減少sequence表99%的訪問。

Snowflake方式,除去配置資訊,核心程式碼就是毫秒級時間41位 機器ID 10位 毫秒內序列12位。

10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000

在上面的字串中,第一位為未使用(實際上也可作為long的符號位),接下來的41位為毫秒級時間,然後5位datacenter標識位,5位機器ID(並不算識別符號,實際是為執行緒標識),然後12位該毫秒內的當前毫秒內的計數,加起來剛好64位,為一個Long型。

這樣的好處是,整體上按照時間自增排序,並且整個分散式系統內不會產生ID碰撞(由datacenter和機器ID作區分),並且效率較高,經測試,snowflake每秒能夠產生26萬ID左右,完全滿足需要。
詳見http://blog.sina.com.cn/s/blog_6b7c2e660102vbi2.html
4. 分頁排序問題
如果分頁排序的資料都落到同一個分片,則跟單個資料來源的處理沒有區別,例如答案資料根據答題卡ID進行分庫分表,那麼這些答案資料最終會落在同一個分片,直接使用例如select * from answer_idex where card_id=#cardId order by id limit 10,20的語句即可搞定。
這裡寫圖片描述
如果需要分頁排序的資料不是落在同一個分辨,比如查詢全部學生的答案資料,按照時間排序,那麼資料就會散落在多個分片上,這個時候就需要從各個分片上分別查詢出資料,然後再彙總查詢,分頁的難度會隨著頁碼的增加而增加。例如查詢第一頁的資料,只需要在各個分片上查詢出第一頁的資料,然後再彙總排序,例如,查詢第一頁的資料select * from answer_idex order by create_time limit 0。但如果查詢第20頁的資料,就需要把各個分片的前20頁的資料都查詢出來,然後再彙總進行分頁排序,這個資料量就非常大,效能也會急速下降。
這裡寫圖片描述
解決跨分片的分頁排序難題更多的是在產品設計上進行優化,比如儘量規避這種跨分片分頁排序的需求;只允許使用者檢視前面幾頁;儘量縮小查詢範圍。
而技術角度的解決,通常是通過一些冗餘資料來進行,比如上述按照時間分頁排序的需求如果很強烈的話,可以冗餘一張時間和答案ID的對映關係表,先在這個表裡查詢出最終顯示的答案ID,然後再根據這些答案ID去查詢答案資料。或者通過大資料平臺,把所有資料彙總起來解決。
5. 分散式事務問題
關於分散式事務,通常涉及到的核心內容是兩階段提交、一階段提交、Best Efforts 1PC模式和事務補償機制的研究。具體可以參考以下網址:
http://blog.csdn.net/bluishglc/article/details/7612811
https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html
6. 常見的分庫分表框架
瞭解清楚分庫分表的原理以後,其實也不一定需要我們親手去造一個輪子,開源平臺上已經有很多分庫分表的中介軟體了,可以先分析清楚這些中介軟體是否滿足我們的技術需要,再進行技術選型。
簡單易用的元件:
噹噹sharding-jdbc
蘑菇街TSharding
強悍重量級的中介軟體:
sharding
TDDL Smart Client的方式(淘寶)
Atlas(Qihoo 360)
alibaba.cobar(是阿里巴巴(B2B)部門開發)
MyCAT(基於阿里開源的Cobar產品而研發)
Oceanus(58同城資料庫中介軟體)
OneProxy(支付寶首席架構師樓方鑫開發)
vitess(谷歌開發的資料庫中介軟體)

感謝閱讀 by ivan.rong

相關文章