MySQL 分庫分表及其平滑擴容方案


分庫分表概述

在業務量不大時,單庫單表即可支撐。

當數據量過大存儲不下、或者並發量過大負荷不起時,就要考慮分庫分表。

本文總結了分庫分表的相關概念、全局ID的生成策略、分片策略、平滑擴容方案、以及流行的方案。

分庫分表相關術語

讀寫分離: 不同的數據庫,同步相同的數據,分別只負責數據的讀和寫。

分區: 指定分區列表達式,把記錄拆分到不同的區域中(必須是同一服務器,可以是不同硬盤),應用看來還是同一張表,沒有變化。

分庫:一個系統的多張數據表,存儲到多個數據庫實例中。

分表: 對於一張多行(記錄)多列(字段)的二維數據表,又分兩種情形:

  • 垂直分表: 豎向切分,不同分表存儲不同的字段,可以把不常用或者大容量、或者不同業務的字段拆分出去;
  • 水平分表(最復雜): 橫向切分,按照特定分片算法,不同分表存儲不同的記錄。

什么時候采用分庫分表

分庫分表會為數據庫維護和業務邏輯帶來一系列復雜性和性能損耗,除非預估的業務量大到萬不得已,切莫過度設計、過早優化。

規划期內的數據量和性能問題,嘗試能否用下列方式解決:

  • 當前數據量:如果沒有達到幾百萬,通常無需分庫分表;
  • 數據量問題:增加磁盤、增加分庫(不同的業務功能表,整表拆分至不同的數據庫);
  • 性能問題:升級CPU/內存、讀寫分離、優化數據庫系統配置、優化數據表/索引、優化 SQL、分區、數據表的垂直切分;
  • 如果仍未能奏效,才考慮最復雜的方案:數據表的水平切分。

分庫分表

水平分庫


以字段為依據,按照一定策略(hash、range等),將一個庫中的數據拆分到多個庫中。
結果:

  • 每個庫的結構都一樣;
  • 每個庫的數據都不一樣,沒有交集;
  • 所有庫的並集是全量數據;

場景:系統絕對並發量上來了,分表難以根本上解決問題,並且還沒有明顯的業務歸屬來垂直分庫。

分析:庫多了,io和cpu的壓力自然可以成倍緩解。

水平分表


以字段為依據,按照一定策略(hash、range等),將一個表中的數據拆分到多個表中。
結果:

  • 每個表的結構都一樣;
  • 每個表的數據都不一樣,沒有交集;
  • 所有表的並集是全量數據;

場景:系統絕對並發量並沒有上來,只是單表的數據量太多,影響了SQL效率,加重了CPU負擔,以至於成為瓶頸。

分析:表的數據量少了,單次SQL執行效率高,自然減輕了CPU的負擔。

垂直分庫


以表為依據,按照業務歸屬不同,將不同的表拆分到不同的庫中。
結果:

  • 每個庫的結構都不一樣;
  • 每個庫的數據也不一樣,沒有交集;
  • 所有庫的並集是全量數據;

場景:系統絕對並發量上來了,並且可以抽象出單獨的業務模塊。

分析:基本上可以服務化了。例如,隨着業務的發展一些公用的配置表、字典表等越來越多,這時可以將這些表拆到單獨的庫中,甚至可以服務化。再有,隨着業務的發展孵化出了一套業務模式,這時可以將相關的表拆到單獨的庫中,甚至可以服務化。

垂直分表


以字段為依據,按照字段的活躍性,將表中字段拆到不同的表(主表和擴展表)中。
結果:

  • 每個表的結構都不一樣;
  • 每個表的數據也不一樣,一般來說,每個表的字段至少有一列交集,一般是主鍵,用於關聯數據;
  • 所有表的並集是全量數據;

場景:系統絕對並發量並沒有上來,表的記錄並不多,但是字段多,並且熱點數據和非熱點數據在一起,單行數據所需的存儲空間較大。以至於數據庫緩存的數據行減少,查詢時會去讀磁盤數據產生大量的隨機讀IO,產生IO瓶頸。

分析:垂直分表的拆分原則是將熱點數據(可能會冗余經常一起查詢的數據)放在一起作為主表,非熱點數據放在一起作為擴展表。這樣更多的熱點數據就能被緩存下來,進而減少了隨機讀IO。拆了之后,要想獲得全部數據就需要關聯兩個表來取數據。

  • 千萬別用join,因為join不僅會增加CPU負擔並且會講兩個表耦合在一起(必須在一個數據庫實例上)。關聯數據,應該在業務Service層做文章,分別獲取主表和擴展表數據然后用關聯字段關聯得到全部數據

全局ID生成策略

自動增長列

優點:數據庫自帶功能,有序,性能佳。
缺點:單庫單表無妨,分庫分表時如果沒有規划,ID可能重復。

解決方案:
1、設置自增偏移和步長

### 假設總共有 10 個分表
### 級別可選: SESSION(會話級), GLOBAL(全局)
SET @@SESSION.auto_increment_offset = 1; ### 起始值, 分別取值為 1~10
SET @@SESSION.auto_increment_increment = 10; ### 步長增量

如果采用該方案,在擴容時需要遷移已有數據至新的所屬分片。

2、全局ID映射表
在全局 Redis 中為每張數據表創建一個 ID 的鍵,記錄該表當前最大 ID;
每次申請 ID 時,都自增 1 並返回給應用;
Redis 要定期持久至全局數據庫。

UUID(128位)

在一台機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。通常平台會提供生成UUID的API。
UUID 由4個連字號(-)將32個字節長的字符串分隔后生成的字符串,總共36個字節長。形如:550e8400-e29b-41d4-a716-446655440000。
UUID 的計算因子包括:以太網卡地址、納秒級時間、芯片ID碼和許多可能的數字。
UUID 是個標准,其實現有幾種,最常用的是微軟的 GUID(Globals Unique Identifiers)。

優點:簡單,全球唯一;
缺點:存儲和傳輸空間大,無序,性能欠佳。

COMB(組合)

參考資料:The Cost of GUIDs as Primary Keys
組合 GUID(10字節) 和時間(6字節),達到有序的效果,提高索引性能。

Snowflake(雪花) 算法

Snowflake 是 Twitter 開源的分布式 ID 生成算法,其結果為 long(64bit) 的數值。
其特性是各節點無需協調、按時間大致有序、且整個集群各節點單不重復。
該數值的默認組成如下(符號位之外的三部分允許個性化調整):

  • 1bit: 符號位,總是 0(為了保證數值是正數)。
  • 41bit: 毫秒數(可用 69 年);
  • 10bit: 節點ID(5bit數據中心 + 5bit節點ID,支持 32 * 32 = 1024 個節點)
  • 12bit: 流水號(每個節點每毫秒內支持 4096 個 ID,相當於 409萬的 QPS,相同時間內如 ID 遇翻轉,則等待至下一毫秒)

分片策略

1、連續分片
根據特定字段(比如用戶ID、訂單時間)的范圍,值在該區間的,划分到特定節點。
優點:集群擴容后,指定新的范圍落在新節點即可,無需進行數據遷移。
缺點:如果按時間划分,數據熱點分布不均(歷史數冷當前數據熱),導致節點負荷不均。

2、ID取模分片
缺點:擴容后需要遷移數據。

3、一致性Hash算法
優點:擴容后無需遷移數據。

4、Snowflake 分片
優點:擴容后無需遷移數據。

分庫分表引入的問題

1、分布式事務
參見分布式事務的解決方案
由於兩階段/三階段提交對性能損耗大,可改用事務補償機制。

2、跨節點 JOIN
對於單庫 JOIN,MySQL 原生就支持;
對於多庫,出於性能考慮,不建議使用 MySQL 自帶的 JOIN,可以用以下方案避免跨節點 JOIN:

  • 全局表: 一些穩定的共用數據表,在各個數據庫中都保存一份;
  • 字段冗余: 一些常用的共用字段,在各個數據表中都保存一份;
  • 應用組裝:應用獲取數據后再組裝。

另外,某個 ID 的用戶信息在哪個節點,他的關聯數據(比如訂單)也在哪個節點,可以避免分布式查詢。

3、跨節點聚合
只能在應用程序端完成。
但對於分頁查詢,每次大量聚合后再分頁,性能欠佳。

4、節點擴容
節點擴容后,新的分片規則導致數據所屬分片有變,因而需要遷移數據。

節點擴容方案

常規方案

如果增加的節點數和擴容操作沒有規划,那么絕大部分數據所屬的分片都有變化,需要在分片間遷移:

  • 預估遷移耗時,發布停服公告;
  • 停服(用戶無法使用服務),使用事先准備的遷移腳本,進行數據遷移;
  • 修改為新的分片規則;
  • 啟動服務器。

免遷移擴容

采用雙倍擴容策略,避免數據遷移。擴容前每個節點的數據,有一半要遷移至一個新增節點中,對應關系比較簡單。
具體操作如下(假設已有 2 個節點 A/B,要雙倍擴容至 A/A2/B/B2 這 4 個節點):

  • 無需停止應用服務器;

  • 新增兩個數據庫 A2/B2 作為從庫,設置主從同步關系為:A=>A2、B=>B2,直至主從數據同步完畢(早期數據可手工同步);

  • 調整分片規則並使之生效:
    原 ID%2=0 => A 改為 ID%4=0 => A, ID%4=2 => A2;
    原 ID%2=1 => B 改為 ID%4=1 => B, ID%4=3 => B2。

  • 解除數據庫實例的主從同步關系,並使之生效;

此時,四個節點的數據都已完整,只是有冗余(多存了和自己配對的節點的那部分數據),擇機清除即可(過后隨時進行,不影響業務)。

分庫分表方案

代理層方式

部署一台代理服務器偽裝成 MySQL 服務器,代理服務器負責與真實 MySQL 節點的對接,應用程序只和代理服務器對接。對應用程序是透明的。

比如 MyCAT,它可以支持 MySQL, SQL Server, Oracle, DB2, PostgreSQL等主流數據庫,也支持MongoDB這種新型NoSQL方式的存儲,未來還會支持更多類型的存儲。

MyCAT 不僅僅可以用作讀寫分離,以及分表分庫、容災管理,而且可以用於多租戶應用開發、雲平台基礎設施,讓你的架構具備很強的適應性和靈活性。

應用層方式

處於業務層和 JDBC 層中間,是以 JAR 包方式提供給應用調用,對代碼有侵入性。主要方案有:

  • 淘寶網的 TDDL: 已於 2012 年關閉了維護通道,建議不要使用
  • 當當網的 Sharding-JDBC(仍在活躍維護中)

Sharding-JDBC

當當應用框架 ddframe 是從關系型數據庫模塊 dd-rdb 中分離出來的數據庫水平分片框架,實現透明化數據庫分庫分表訪問,實現了 Snowflake 分片算法;

Sharding-JDBC定位為輕量Java框架,使用客戶端直連數據庫,無需額外部署,無其他依賴,DBA也無需改變原有的運維方式。

Sharding-JDBC分片策略靈活,可支持等號、between、in等多維度分片,也可支持多分片鍵。

SQL解析功能完善,支持聚合、分組、排序、limit、or等查詢,並支持Binding Table以及笛卡爾積表查詢。

Sharding-JDBC直接封裝JDBC API,可以理解為增強版的JDBC驅動,舊代碼遷移成本幾乎為零。

  • 可適用於任何基於Java的ORM框架,如JPA、Hibernate、Mybatis、Spring JDBC Template或直接使用JDBC。
  • 可基於任何第三方的數據庫連接池,如DBCP、C3P0、 BoneCP、Druid等。
  • 理論上可支持任意實現JDBC規范的數據庫。雖然目前僅支持MySQL,但已有支持Oracle、SQLServer等數據庫的計划。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM