當數據庫性能出現瓶頸時就須要經過擴展來提高性能,對於擴展性來講要麼增強機器自己的性能,要麼把任務分發到不一樣的機器上。對於數據庫來講經過強悍的機器解決成本是很大的,如Oracle。經過多個廉價的機器實現水平擴展是現代的主流解決方案,如Mysql。mysql
數據庫水平擴展的核心是把數據拆分紅不一樣的單元並放在不一樣的獨立的實例上,這樣就作到了負載均衡。拆分分爲邏輯和物理拆分,邏輯拆分是對物理上不可分割的實例進行邏輯上的分割,物理拆分是拆分紅多個獨立的實例:算法
邏輯拆分sql
物理拆分數據庫
我理解的邏輯分區:舉個例子,操做系統中的分區,是將硬盤根據大小進行邏輯分區,就是咱們看到的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) );
Key:鍵分佈式
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條,加快了查詢;
當單表數據行數超過必定量級時,讀/寫 會變慢,查詢須要檢索更多數據,DML操做須要更多時間建立/更新索引;咱們能夠經過把這些數據分散到多個表中來提升效率,這樣只涉及到部分數據而不是全部,最經常使用的分表算法是哈希算法。哈希函數使用除留餘數法,即取餘的方式。
創建所須要的N個表,表名:user_0 ... user_N-1,經過對ID取餘運算直接路由到所在的表
小結:邏輯分區是數據庫提供的功能,不用對應用和業務作任何改變就能實現。哈希分表實現簡單,只須要修改少許代碼就能實現。對單表進行分表後,可以大大提升咱們讀寫的效率。
讀寫分離的核心是把讀/寫操做路由的不一樣實例上,實例之間要的數據要保障一致(經過複製實現),路由能夠本身識別 Insert/Update/Delete/Selete 作路由,也可使用代理(mysql proxy)或中間件。
通常站點的讀操做比寫操做更加密集,查詢量暴增的時候單臺服務器沒法處理這麼多讀操做,咱們須要增長額外的服務器來支撐,使用主從方式,主作寫操做,從作讀操做,經過主從複製達到數據一致性,這樣讀操做壓力會被分散。mysql使用單線程把主機數據複製到從機上實現數據一致性,因此須要對主從進行配置。
在上面的主從架構中,若是從庫有不少個可能會出現複製延遲過大現象,緣由是由於mysql複製須要在slave和master創建長鏈接,而且master須要開啓binlog dump線程進行數據推送,過多的slave會致使複製延遲過大。能夠增長複製源和開啓半同步複製解決。
1.增長複製源:
2.開啓半同步複製:主庫提交事務時,將事件寫入它的二進制日誌,而從庫在準備就緒時請求它們。主庫無需等待從庫的ACK回覆,直接提交事務並返回客戶端。異步複製不確保全部事件都能到達從庫,沒法保證數據完整性
讀寫分離不能解決寫操做頻繁帶來的性能瓶頸,好比主庫寫操做佔80%,這時須要把寫操做拆分到獨立的實例上,垂直拆分是按照業務相關度把數據拆分到不一樣的DB上,這樣寫操做天然就被拆分開來。
拆分了以後還能夠繼續作讀寫分離進一步提高性能,但垂直拆分也帶來了問題,本來在一個事務中的數據操做,在拆分以後就沒法在同一個事務中完成,這使得咱們業務應用須要額外的成本去解決,如經過引入分佈式事務 或 最終一致來解決。
對數據庫作了垂直切分和讀寫分離能夠解決大部分站點的問題,可是在體量巨大的應用中主數據庫寫操做壓力依然會達到極限,這時須要對錶進行水平拆分並分佈在不一樣機器上面。
水平拆分最簡單的方式就是用哈希算法,一個表只能根據一個字段sharding。下面列舉了一些經常使用的拆分方法:
1.簡單hash算法
創建所須要的N個表,表名:user_0 ... user_N-1,經過對ID取餘運算直接路由到所在的表:
優勢:
缺點:
2.一致性hash算法
在擴容時簡單hash算法須要全量數據遷移成本和風險很高,一致性hash算法對該算法進行了優化,經過對固定值2^32-1進行取餘保證hash結果不變,再經過範圍把環拆分紅N份,增長節點時隻影響新節點到逆時針第一個節點之間的數據。
總體擴容:若是分片數量不足須要擴容,由於要保證數據分佈均勻,因此受影響的節點會佔總量的一半。
局部擴容:一致性hash經過在局部增長節點實現靈活擴容,而沒必要每次都翻倍擴容,能夠對熱點數據表進行再拆分,隻影響新節點到逆時針第一個節點之間的數據,可是須要額外再維護映射表保證其餘節點還映射到舊錶。
優勢:
缺點:
3.動態映射
熱點數據集中多是因爲某個ID產生的數據過多形成的,經過配置指定到具體的分片上能夠過熱問題。
優勢:能夠作局部擴容解決熱點數據問題。
缺點:實現比較複雜,每次都須要查詢獲取對應分片性能比簡答hash差,會影響查詢效率。
物理拆分帶來好處的同時也帶來的一些問題:
跨庫事務
跨庫Join
跨表分頁和排序:
接下來說一下CQRS是怎麼作的。
CQRS是對應用作讀寫職責分離,每次寫操做都會以相似日誌的形式記錄在Event Store中,並非直接修改字段值到指望值,再由Event Bus把事件同步到讀服務,讀服務對讀庫數據進行修改,全部查詢都會走讀服務。在該架構模式中讀服務能夠把想要的業務數據聚合到讀庫中,其實就是經過冗餘數據的方式避免應用去多庫中查詢和聚合數據,以空間換時間。