Categories
程式開發

Mysql學習筆記:分庫分錶(sharding)


當數據庫性能出現瓶頸時就需要通過擴展來提升性能,對於擴展性來說要么加強機器本身的性能,要么把任務分發到不同的機器上。 對於數據庫來說通過強悍的機器解決成本是很大的,如Oracle。 通過多個廉價的機器實現水平擴展是現代的主流解決方案,如Mysql。

數據庫水平擴展的核心是把數據拆分成不同的單元並放在不同的獨立的實例上,這樣就做到了負載均衡。 拆分分為邏輯和物理拆分,邏輯拆分是對物理上不可分割的實例進行邏輯上的分割,物理拆分是拆分成多個獨立的實例:

邏輯拆分分區(Partition)分錶物理拆分讀寫分離垂直拆分(分庫)水平拆分(分錶)

1.邏輯拆分

1.1 分區

我理解的邏輯分區:舉個例子,操作系統中的分區,是將硬盤根據大小進行邏輯分區,就是我們看到的C、D、E、F盤,邏輯分區還是在同一個操作系統中。 數據庫產品的Partition分區也是一樣的道理,將數據進行邏輯分區,對數據劃分界限。

MySql 支持Range,List,Hash,Key。 最常用的是Range。 注意不同的版本對分區類型的支持有些不同!

Range:範圍

CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN (21)
);

LIST:列表

CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY LIST(store_id) (
PARTITION pNorth VALUES IN (3,5,6,9,17),
PARTITION pEast VALUES IN (1,2,10,11,19,20),
PARTITION pWest VALUES IN (4,12,13,14,18),
PARTITION pCentral VALUES IN (7,8,15,16)
);

CREATE TABLE k1 (
id INT NOT NULL,
name VARCHAR(20),
UNIQUE KEY (id)
)
PARTITION BY KEY()
PARTITIONS 2;

HASH:哈希

CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY HASH( YEAR(hired) )
PARTITIONS 4;

例子:

數據:新聞表,2010開始記錄,假設10年到15年每年的數據為200W,總數1000W;

條件:查詢15年7月所有的新聞數據;

未分區:需要把表遍歷,1000W條數據,查詢性能就不用說了;

分區:按照年份分區,當要查詢15年數據,只會遍歷15年的數據200W條,加快了查詢;

1.2 分錶

當單表數據行數超過一定量級時,讀/寫會變慢,查詢需要檢索更多數據,DML操作需要更多時間創建/更新索引;我們可以通過把這些數據分散到多個表中來提高效率,這樣只涉及到部分數據而不是所有,最常用的分錶算法是哈希算法“。哈希函數使用除留餘數法,即取餘的方式。

建立所需要的N個表,表名:user_0 … user_N-1,通過對ID取餘運算直接路由到所在的表

用戶_0:5%5用戶_1:1%5/6%5用戶_2:2%5用戶_3:3%5用戶_4:4%5

小結:邏輯分區是數據庫提供的功能,不用對應用和業務做任何改變就能實現。 哈希分錶實現簡單,只需要修改少量代碼就能實現。 對單表進行分錶後,能夠大大提高我們讀寫的效率。

2.物理拆分

2.1 讀寫分離(主從復制)

讀寫分離的核心是把讀/寫操作路由的不同實例上,實例之間要的數據要保障一致(通過複製實現),路由可以自己識別Insert/Update/Delete/Selete 做路由,也可以使用代理(mysql proxy)或中間件。

一般站點的讀操作比寫操作更加密集,查詢量暴增的時候單台服務器無法處理這麼多讀操作,我們需要增加額外的服務器來支撐,使用主從方式,主做寫操作,從做讀操作,通過主從復制達到數據一致性,這樣讀操作壓力會被分散。 mysql使用單線程把主機數據複製到從機上實現數據一致性,所以需要對主從進行配置。

Mysql學習筆記:分庫分錶(sharding) 1

在上面的主從架構中,如果從庫有很多個可能會出現複製延遲過大現象,原因是因為mysql複製需要在slave和master建立長連接,並且master需要開啟binlog dump線程進行數據推送,過多的slave會導致複製延遲過大。 可以增加複制源和開啟半同步複製解決。

1.增加複制源:

Mysql學習筆記:分庫分錶(sharding) 2

2.開啟半同步複製:主庫提交事務時,將事件寫入它的二進制日誌,而從庫在準備就緒時請求它們。 主庫無需等待從庫的ACK回复,直接提交事務並返回客戶端。 異步複製不確保所有事件都能到達從庫,無法保證數據完整性

2.2 垂直拆分(分庫)

讀寫分離不能解決寫操作頻繁帶來的性能瓶頸,比如主庫寫操作佔80%,這時需要把寫操作拆分到獨立的實例上,垂直拆分是按照業務相關度把數據拆分到不同的DB上,這樣寫操作自然就被拆分開來。

Mysql學習筆記:分庫分錶(sharding) 3

拆分了之後還可以繼續做讀寫分離進一步提升性能,但垂直拆分也帶來了問題,原本在一個事務中的數據操作,在拆分之後就無法在同一個事務中完成,這使得我們業務應用需要額外的成本去解決,如通過引入分佈式事務或最終一致來解決。

2.3 水平拆分(分錶)

對數據庫做了垂直切分和讀寫分離可以解決大部分站點的問題,但是在體量巨大的應用中主數據庫寫操作壓力依然會達到極限,這時需要對錶進行水平拆分並分佈在不同機器上面。

Mysql學習筆記:分庫分錶(sharding) 4

水平拆分最簡單的方式就是用哈希算法,一個表只能根據一個字段sharding。 下面列舉了一些常用的拆分方法:

1.簡單hash算法

建立所需要的N個表,表名:user_0 … user_N-1,通過對ID取餘運算直接路由到所在的表:

用戶_0:5%5用戶_1:1%5/6%5用戶_2:2%5用戶_3:3%5用戶_4:4%5

優點:

查詢分片位置的時間複雜度為O(1),簡單有效。

缺點:

動態擴容有局限:當容量不足需要增加分片數量來擴容,哈希值會發生改變,涉及全量數據遷移。 熱點數據集中:活躍用戶分到了同一個片上,這個實例壓力非常大可能會過載。

2.一致性hash算法

在擴容時簡單hash算法需要全量數據遷移成本和風險很高,一致性hash算法對該算法進行了優化,通過對固定值2^32-1進行取餘保證hash結果不變,再通過範圍把環拆分成N份,增加節點時只影響新節點到逆時針第一個節點之間的數據。

Mysql學習筆記:分庫分錶(sharding) 5

整體擴容:如果分片數量不足需要擴容,因為要保證數據分佈均勻,所以受影響的節點會佔總量的一半。

Mysql學習筆記:分庫分錶(sharding) 6

局部擴容:一致性hash通過在局部增加節點實現靈活擴容,而不必每次都翻倍擴容,可以對熱點數據表進行再拆分,只影響新節點到逆時針第一個節點之間的數據,但是需要額外再維護映射表保證其他節點還映射到舊表。

Mysql學習筆記:分庫分錶(sharding) 7

優點:

可以靈活選擇局部還是整體擴容,局部擴容可以對某個熱點數據的節點再拆分而不影響其他節點。

缺點:

在節點過多的情況下查詢效率較低,表映射實現複雜。

3.動態映射

熱點數據集中可能是由於某個ID產生的數據過多造成的,通過配置指定到具體的分片上可以過熱問題。

優點:可以做局部擴容解決熱點數據問題。

缺點:實現比較複雜,每次都需要查詢獲取對應分片性能比簡答hash差,會影響查詢效率。

2.4 拆分帶來的問題

物理拆分帶來好處的同時也帶來的一些問題:

跨庫事務通過分佈式事務或最終一致解決跨庫Join把Join操作拆分成多次查詢並在應用中做聚合使用搜索引擎做數據聚合和查詢使用CQRS做數據聚合跨表分頁和排序:由中間件去所有分片聚合數據,再做分頁和排序

接下來講一下CQRS是怎麼做的。

Mysql學習筆記:分庫分錶(sharding) 8

CQRS是對應用做讀寫職責分離,每次寫操作都會以類似日誌的形式記錄在Event Store中,並不是直接修改字段值到期望值,再由Event Bus把事件同步到讀服務,讀服務對讀庫數據進行修改,所有查詢都會走讀服務。 在該架構模式中讀服務可以把想要的業務數據聚合到讀庫中,其實就是通過冗餘數據的方式避免應用去多庫中查詢和聚合數據,以空間換時間。