《面試官系列:深入數據庫分區分庫分表》

一、為什麼要分庫分表

軟件時代,傳統應用都有這樣一個特點:訪問量、數據量都比較小,單庫單表都完全可以支撐整個業務。隨著互聯網的發展和用戶規模的迅速擴大,對系統的要求也越來越高。因此傳統的MySQL單庫單表架構的性能問題就暴露出來了。而有下面幾個因素會影響數據庫性能:

  • 數據量

MySQL單庫數據量在5000萬以內性能比較好,超過閾值後性能會隨著數據量的增大而變弱。MySQL單表的數據量是500w-1000w之間性能比較好,超過1000w性能也會下降。

  • 磁盤

因為單個服務的磁盤空間是有限制的,如果併發壓力下,所有的請求都訪問同一個節點,肯定會對磁盤IO造成非常大的影響。

  • 數據庫連接

數據庫連接是非常稀少的資源,如果一個庫裡既有用戶、商品、訂單相關的數據,當海量用戶同時操作時,數據庫連接就很可能成為瓶頸。

為了提升性能,所以我們必須要解決上述幾個問題,那就有必要引進分庫分表,當然除了分庫分表,還有別的解決方案,就是NoSQL和NewSQL,NoSQL主要是MongoDB等,NewSQL則以TiDB為代表。

二、分區分庫分表的原理

1、什麼是分區、分表、分庫

(1)分區

就是把一張表的數據分成N個區塊,在邏輯上看最終只是一張表,但底層是由N個物理區塊組成的,分區實現比較簡單,數據庫mysql、oracle等很容易就可支持。

(2)分表

就是把一張表按一定的規則分解成N個具有獨立存儲空間的實體表。系統讀寫時需要根據定義好的規則得到對應的字表明,然後操作它。

(3)分庫

一旦分表,一個庫中的表會越來越多,將整個數據庫比作圖書館,一張表就是一本書。當要在一本書中查找某項內容時,如果不分章節,查找的效率將會下降。而同理,在數據庫中就是分區。

2、什麼時候使用分區?

一張表的查詢速度已經慢到影響使用的時候。

  • sql經過優化
  • 數據量大
  • 表中的數據是分段的
  • 對數據的操作往往只涉及一部分數據,而不是所有的數據

最常見的分區方法就是按照時間進行分區,分區一個最大的優點就是可以非常高效的進行歷史數據的清理。

(1)分區的實現方式

mysql5自5.1開始對分區(Partition)有支持。

(2)分區類型

目前MySQL支持範圍分區(RANGE),列表分區(LIST),哈希分區(HASH)以及KEY分區四種。

(3)RANGE分區實例

基於屬於一個給定連續區間的列值,把多行分配給分區。最常見的是基於時間字段. 基於分區的列最好是整型,如果日期型的可以使用函數轉換為整型。本例中使用to_days函數。

<code>CREATE TABLE my_range_datetime(    id INT,    hiredate DATETIME) PARTITION BY RANGE (TO_DAYS(hiredate) ) (    PARTITION p1 VALUES LESS THAN ( TO_DAYS('20171202') ),    PARTITION p2 VALUES LESS THAN ( TO_DAYS('20171203') ),    PARTITION p3 VALUES LESS THAN ( TO_DAYS('20171204') ),    PARTITION p4 VALUES LESS THAN ( TO_DAYS('20171205') ),    PARTITION p5 VALUES LESS THAN ( TO_DAYS('20171206') ),    PARTITION p6 VALUES LESS THAN ( TO_DAYS('20171207') ),    PARTITION p7 VALUES LESS THAN ( TO_DAYS('20171208') ),    PARTITION p8 VALUES LESS THAN ( TO_DAYS('20171209') ),    PARTITION p9 VALUES LESS THAN ( TO_DAYS('20171210') ),    PARTITION p10 VALUES LESS THAN ( TO_DAYS('20171211') ),    PARTITION p11 VALUES LESS THAN (MAXVALUE) );/<code>

3、什麼時候分表?

一張表的查詢速度已經慢到影響使用的時候。

  • sql經過優化
  • 數據量大
  • 當頻繁插入或者聯合查詢時,速度變慢

分表後,單表的併發能力提高了,磁盤I/O性能也提高了,寫操作效率提高了

(1)分表的實現方式

需要結合相關中間件,需要業務系統配合遷移升級,工作量較大。

三、分庫分表後引入的問題

1、分佈式事務問題

如果我們做了垂直分庫或者水平分庫以後,就必然會涉及到跨庫執行SQL的問題,這樣就引發了互聯網界的老大難問題-"分佈式事務"。那要如何解決這個問題呢?

1.使用分佈式事務中間件 2.使用MySQL自帶的針對跨庫的事務一致性方案(XA),不過性能要比單庫的慢10倍左右。3.能否避免掉跨庫操作(比如將用戶和商品放在同一個庫中)

2、跨庫join的問題

分庫分表後表之間的關聯操作將受到限制,我們無法join位於不同分庫的表,也無法join分表粒度不同的表, 結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。粗略的解決方法: 全局表:基礎數據,所有庫都拷貝一份。 字段冗餘:這樣有些字段就不用join去查詢了。 系統層組裝:分別查詢出所有,然後組裝起來,較複雜。

3、橫向擴容的問題

當我們使用HASH取模做分表的時候,針對數據量的遞增,可能需要動態的增加表,此時就需要考慮因為reHash導致數據遷移的問題。

4、結果集合並、排序的問題

因為我們是將數據分散存儲到不同的庫、表裡的,當我們查詢指定數據列表時,數據來源於不同的子庫或者子表,就必然會引發結果集合並、排序的問題。如果每次查詢都需要排序、合併等操作,性能肯定會受非常大的影響。走緩存可能一條路!

四、分庫分表中間件設計

分表又分為單庫分表(表名不同)和多庫分表(表名相同),不管使用哪種策略都還需要自己去實現路由,制定路由規則等,可以考慮使用開源的分庫分表中間件,無侵入應用設計,例如淘寶的tddl等。

分庫分表中間件全部可以歸結為兩大類型:

  • CLIENT模式;
  • PROXY模式;

CLIENT模式代表有阿里的TDDL,開源社區的sharding-jdbc(sharding-jdbc的3.x版本即sharding-sphere已經支持了proxy模式)。

架構如下:

《面試官系列:深入數據庫分區分庫分表》

PROXY模式代表有阿里的cobar,民間組織的MyCAT。架構如下:

《面試官系列:深入數據庫分區分庫分表》

無論是CLIENT模式,還是PROXY模式。幾個核心的步驟是一樣的:SQL解析,重寫,路由,執行,結果歸併。

五、分庫分表常用中間件

目前應用比較多的基本有以下幾種,

  • TDDL
  • Sharding-jdbc
  • Mycat
  • Cobar

1、TDDL

淘寶團隊開發的,屬於 client 層方案。支持基本的 crud 語法和讀寫分離,但不支持 join、多表查詢等語法。

2、Sharding-jdbc

噹噹開源的,屬於 client 層方案,目前已經更名為 ShardingSphere。SQL 語法支持也比較多,沒有太多限制,支持分庫分表、讀寫分離、分佈式 id 生成、柔性事務(最大努力送達型事務、TCC 事務)。

3、Cobar

阿里 b2b 團隊開發和開源的,屬於 proxy 層方案,就是介於應用服務器和數據庫服務器之間。應用程序通過 JDBC 驅動訪問 Cobar 集群,Cobar 根據 SQL 和分庫規則對 SQL 做分解,然後分發到 MySQL 集群不同的數據庫實例上執行。

4、Mycat

基於 Cobar 改造的,屬於 proxy 層方案,支持的功能完善,社區活躍。

六、常見分表、分庫常用策略

  1. 平均進行分配hash(object)%N(適用於簡單架構)。
  2. 按照權重進行分配且均勻輪詢。
  3. 按照業務進行分配。
  4. 按照一致性hash算法進行分配(適用於集群架構,在集群中節點的添加和刪除不會造成數據丟失,方便數據遷移)。

七、全局ID生成策略

1、自動增長列

優點:數據庫自帶功能,有序,性能佳。

缺點:單庫單表無妨,分庫分表時如果沒有規劃,ID可能重複。

解決方案,一個是設置自增偏移和步長。

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

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

另一個是全局ID映射表。

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

2、UUID(128位)

在一臺機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。通常平臺會提供生成UUID的API。

UUID 由4個連字號(-)將32個字節長的字符串分隔後生成的字符串,總共36個字節長。形如:550e8400-e29b-41d4-a716-446655440000。

UUID 的計算因子包括:以太網卡地址、納秒級時間、芯片ID碼和許多可能的數字。

UUID 是個標準,其實現有幾種,最常用的是微軟的 GUID(Globals Unique Identifiers)。

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

3、COMB(組合)

組合 GUID(10字節) 和時間(6字節),達到有序的效果,提高索引性能。

4、Snowflake(雪花) 算法

Snowflake 是 Twitter 開源的分佈式 ID 生成算法,其結果為 long(64bit) 的數值。

其特性是各節點無需協調、按時間大致有序、且整個集群各節點單不重複。

該數值的默認組成如下(符號位之外的三部分允許個性化調整):

《面試官系列:深入數據庫分區分庫分表》

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

八、優雅實現分庫分表的動態擴容

優雅的設計擴容縮容的意思就是 進行擴容縮容的代價要小,遷移數據要快。

可以採用邏輯分庫分表的方式來代替物理分庫分表的方式,要擴容縮容時,只需要將邏輯上的數據庫、表改為物理上的數據庫、表。

第一次進行分庫分表時就多分幾個庫,一個實踐是利用32 * 32來分庫分表,即分為32個庫,每個庫32張表,一共就是1024張表,根據某個id先根據先根據數據庫數量32取模路由到庫,再根據一個庫的表數量32取模路由到表裡面。

剛開始的時候,這個庫可能就是邏輯庫,建在一個mysql服務上面,比如一個mysql服務器建了16個數據庫。

如果後面要進行拆分,就是不斷的在庫和mysql實例之間遷移就行了。將mysql服務器的庫搬到另外的一個服務器上面去,比如每個服務器創建8個庫,這樣就由兩臺mysql服務器變成了4臺mysql服務器。我們系統只需要配置一下新增的兩臺服務器即可。

比如說最多可以擴展到32個數據庫服務器,每個數據庫服務器是一個庫。如果還是不夠?最多可以擴展到1024個數據庫服務器,每個數據庫服務器上面一個庫一個表。因為最多是1024個表麼。

這麼搞,是不用自己寫代碼做數據遷移的,都交給dba來搞好了,但是dba確實是需要做一些庫表遷移的工作,但是總比你自己寫代碼,抽數據導數據來的效率高得多了。

哪怕是要減少庫的數量,也很簡單,其實說白了就是按倍數縮容就可以了,然後修改一下路由規則。

文末福利:

《面試官系列:深入數據庫分區分庫分表》


分享到:


相關文章: