Mysql架構

爲什麼要關注Mysql架構?

好處

提高可用性

如果某個數據庫實例出現問題,對業務來說,這時可由其他的數據庫實例提供服務,或者可以快速切換到其他的數據庫實例,對業務來說基本無感知,也不會導致業務的中斷。同時通過數據在多個實例之間的複製,提高數據的安全性和可用性。

提高性能

業務對數據的訪問可以分散到不同的數據庫實例上,可以根據數據訪問類型不同,將不同性質的訪問操作,進行分離,都可以降低單個數據庫實例的訪問壓力。

Mysql集羣的可行方案

MySQL Cluster

由Mysql本身提供,優勢:可用性非常高,性能非常好。每份數據至少可在不同主機存一份拷貝,且冗餘數據拷貝實時同步。但它的維護非常複雜,存在部分Bug,目前還不適合比較核心的線上系統,所以不推薦。

DRBD磁盤網絡鏡像方案

Distributed Replicated Block Device,其實現方式是通過網絡來鏡像整個設備(磁盤).它允許用戶在遠程機器上建立一個本地塊設備的實時鏡像,與心跳鏈接結合使用,也可看做一種網絡RAID。

優勢:軟件功能強大,數據可在底層快設備級別跨物理主機鏡像,且可根據性能和可靠性要求配置不同級別的同步。IO操作保持順序,可滿足數據庫對數據一致性的苛刻要求。但非分佈式文件系統環境無法支持鏡像數據同時可見,性能和可靠性兩者相互矛盾,無法適用於性能和可靠性要求都比較苛刻的環境,維護成本高於MySQL Replication。另外,DRBD也是官方推薦的可用於MySQL高可用方案之一,所以這個大家可根據實際環境來考慮是否部署。

MySQL Replication

在實際應用場景中,MySQL Replication是使用最爲廣泛的一種提高系統擴展性的設計手段。衆多的MySQL使用者通過Replication功能提升系統的擴展性後,通過簡單的增加價格低廉的硬件設備成倍 甚至成數量級地提高了原有系統的性能。

Mysql複製

什麼是Mysql複製?

複製是指將主數據庫的 DDL和 DML 操作通過二進制日誌傳到複製服務器(也叫從庫)上,然後在從庫上對這些日誌重新執行(也叫重做),從而使得從庫和主庫的數據保持同步。MysQL支持一臺主庫同時向多臺從庫進行復制,從庫同時也可以作爲其他服務器的主庫,實現鏈狀的複製 。

注意:

由於MySQL實現的是並不是完全同步的複製,所以主從庫之間存在一定的差距,在從庫上進行的査詢操作需要考慮到這些數據的差異, 一般只有更新不頻繁的數據或者對實時性要求不高的數據可以通過從庫查詢, 實時性要求高的數據仍然需要從主數據庫獲得。

名稱解釋:

DML(data manipulation language)數據操縱語言:

就是我們最經常用到的 SELECT、UPDATE、INSERT、DELETE。 主要用來對數據庫的數據進行一些操作。

DDL(data definition language)數據庫定義語言:

其實就是我們在創建表的時候用到的一些sql,比如說:CREATE、ALTER、DROP等。DDL主要是用在定義或改變表的結構,數據類型,表之間的鏈接和約束等初始化工作上

好處

  1. 如果主庫出現問題,可以快速切換到從庫提供服務。
  2. 可以在從庫上執行查詢操作, 降低主庫的訪問壓力。
  3. 某些數據庫維護工作,比如備份,可以在從庫上執行,以避免備份期間影響主庫的服務。

原理概述

( 1 )首先, MySQL主庫在事務提交時會把數據變更作爲事件 Events 記錄在二進制日誌文件Binlog中; MySQL主庫上的 sync_binlog參數控制 Binlog日誌刷新到磁盤。

( 2 )主庫推送二進制日誌文件 Binlog中的事件到從庫的中繼日誌 Relay Log, 之後從庫根據中繼日誌 Relay Log重做數據變更操作,通過邏輯複製以此來達到主庫和從庫的數據一致。

MySQL通過3個線程來完成主從庫間的數據複製:其中 Binlog Dump線程跑在主庫上, I/0線程和 SQL線程跑在從庫上。當在從庫上啓動複製時,首先創建I/0程連接主庫,主庫隨後創建 Binlog Dump線程讀取數據庫事件併發送給 I/0線程, I0線程獲取到事件數據後更新到從庫的中繼日誌 Relay Log中去,之後從庫上的 SQL線程讀取中繼日誌RelayLog中更新的數據庫事件並應用。

可以通過 SHOW PROCESSLIST命令在主庫上査看 BinlogDump線程,從 BinlogDump 線程的狀態可以看到, Mysql的複製是主庫主動推送日誌到從庫去的,是屬於“”日誌的方式來做同步。同樣地,在從庫上通過 SHOW PROCESSLIST可以看到l/O線程和 SQL線程, l/O線程等待主庫上的 Binlog Dump線程.發送事件並更新到中繼日誌 RelayLog, SQL線程讀取中繼日誌並應用變更到數據庫。

複製中的各類文件解析

日誌文件

複製過程中涉及了兩類非常重要的日誌文件: 二進制日誌文件( Binlog)和中繼日誌文件( Relay Log)。二進制日誌文件( Binlog)會把 MysQL中的所有數據修改操作以二進制的形式記錄到日誌文件中,包括 Create、 Drop、 Insert、 Update、 Delete 操作等,但二進制日誌文件(Binlog) 不會記錄 Select操作, 因爲 Select操作並不修改數據。

可以通過 show variables査看 Binlog的格式, Binlog支持 Statement、 Row、 Mixed三種格式,也對應了 MysQL的3種複製技術。

中繼日誌文件 Relay Log的文件格式、 內容和二進制日誌文件 Binlog一樣, 唯一的區別在於從庫上的 SQL線程在執行完當前中繼日誌文件 Relay Log中的事件之後, SQL線程會自動刪除當前中繼日誌文件 Relay Log,避免從庫上的中繼日誌文件 Relay Log佔用過多的磁盤空間。爲了保證從庫 Crash重啓之後,從庫的 I/0線程和 SQL線程仍然能夠知道從哪裏開始複製, 從庫上默認還會創建兩個日誌文件 master.info和 relay_log.info用來保存複製的進度。這兩個文件在磁盤上以文件形式分別記錄了從庫的 l/0線程當前讀取主庫二進制日誌 Binlog的進度和SQL線程應用中繼日誌 RelayLog的進度。

可以通過 show slave status命令能夠看到當前從庫複製的狀態。

主要參數:

Master Host: 主庫的 IP.

Master User 主庫上, 主從複製使用的用戶賬號,

Master Port:主庫 MySQL的端口號,

Master_Log_File:從庫的I/0線程當前正在讀取的主庫 Binlog的文件 。

Read_Master Log_Pos:從庫I/0線程當前讀取到的位置。

Relay_Log_File: 從庫 SQL線程正在讀取和應用的中繼日誌 Relay Log的文件名 。

Relay_Log_Pos: 從庫 SQL線程當前讀取並應用的中繼日誌 Relay Log的位置。

Relay_Master_Log_File:從庫 SQL線程正在讀取和應用的 Relay Log對應於主庫Binlog的文件名 。

Exec_Master_Log_Pos:中繼日誌 RelayLog中 Relay_Log_Pos位置對應於主庫 Binlog 的位置。

三種複製技術

二進制日誌文件 Binlog有三種格式:

Statement:基於 SQL語句級別的 Binlog,每條修改數據的 SQL都會保存到 Binlog裏。

Row:基於行級別,記錄每一行數據的變化,也就是將每行數據的變化都記錄到 Binlog 裏面, 記錄得非常詳細, 但是並不記錄原始 SQL; 在複製的時候, 並不會因爲存儲過程或觸發器造成主從庫數據不一致的問通, 但是記錄的日誌量較 Statement格式要大得多 。

Mixed:混合Statement和Row模式,默認情況下采用 Statement模式記錄,某些情況下會切換到 Row模式

同時也對應了 MysQL複製的3種技術。

在 binlog_format設置爲 Row格式時, MySQL實際上在 Binlog中逐行記錄數據的變更, Row格式比 Statement格式更能保證從庫數據的一致性(複製的是記錄,而不是單純操作 SQL)。當然, Row格式下的 Binlog的日誌量很可能會增大非常多,在設置時需要考慮到磁盤空間間題。

參數 binlog_format可以在全局設置或者在當前 session動態設置: 在全局設置會影響所有session,而在當前 session設置則僅僅影響當前 Session。可以通過 SET命令來實時修改二進日誌文件(Binlog)的格式。

相關命令

查看當前複製方式

show variables like '%binlog%format%';

更改複製方式

set global binlog_format = 'ROW';

set global binlog_format = 'STATEMENT';

常用的複製架構

複製的3種常見架構有一主多從複製架構、多級複製架構和雙主複製/DrualMaster架構

一主多從

在主庫讀取請求壓力非常大的場景下, 可以通過配置一主多從複製架構實現讀寫分離, 把大量對實時性要求不是特別高的讀請求通過負載均衡分佈到多個從庫上, 降低主庫的讀取壓力,在主庫出現異常宕機的情況下, 可以把一個從庫切換爲主庫繼續提供服務 。

多級複製

一主多從的架構能夠解決大部分讀請求壓力特別大的場景的需求, 考慮到 MysQL的複製是主庫“推送” Binlog日誌到從庫,主庫的 I/0壓力和網絡壓力會隨着從庫的增加而增長(每個從庫都會在主庫上有一個獨立的 Binlog Dump線程來發送事件), 而多級複製架構解決了一主多從場景下,主庫額外的 I/0和網絡壓力。

雙主複製/Dual Master

其實就是主庫 Master和 Master2互爲主從, client客戶端的寫請求都訪問主庫 Master,而讀請求可以選擇訪問主庫 Master或 Master2。

雙主多級複製架構

當然雙主複製還能和主從複製聯合起來使用:在 Master2庫下配置從庫 Slave、 Slave2等,這樣即可通過從庫 Slave等來分擔讀取壓力,MyQL的雙主多級複製架構如圖所示

複製過程搭建

異步複製

主庫執行完Commit後,在主庫寫入Binlog日誌後即可成功返回客戶端,無需等Binlog日誌傳送給從庫。

步驟:

1、確保主從庫安裝了相同版本的數據庫。

2、在主庫上,設置一個複製使用的賬戶,並授予 REPLICATION SLAVE權限。這裏創建一個複製用戶repl,可以從IP爲192.169.56.103的主機進行連接:

命令文本:GRANT REPLICATION SLAVE ON *.* To 'rep1'@'192.168.56.103' IDENTIFIED BY '1234test';

3、修改主數據庫服務器的配置文件 my.cnf,開啓 BINLOG,並設置 server-id的值。這兩個參數的修改需要重新啓動數據庫服務纔可以生效

在 my cnf中修改如下:

[mysqld]

log-bin=/home/ mysql/log/mysql-bin. log

server-id= 1

注意:如果mysql目錄下無log目錄,請先創建log目錄

4、然後得到主庫上當前的二進制日誌名和偏移量值。這個操作的目的是爲了在從數據庫啓動以後,從這個點開始進行數據的恢復。

執行show master status:

5、修改從數據庫的配置文件 my.cnf,增加 server-id參數。注意 server-id的值必須是唯一的,不能和主數據庫的配置相同,如果有多個從數據庫服務器,每個從數據庫服務器必須有自己唯一的 server-id值。

在 mycnf中修改如下:

[mysqld]

server-id=2

6、在從庫上,使用 - -skip-slave- start選項啓動從數據庫,這樣不會立即啓動從數據庫服務上的複製進程,方便我們對從數據庫的服務進行進一步的配置:

操作命令:./bin/mysqld_safe  --defaults-file=/home/mysql/mysql3307/my.cnf --skip-slave-start

7、對從數據庫服務器做相應設置,指定複製使用的用戶,主數據庫服務器的IP、端口

以及開始執行復制的日誌文件和位置等,參考代碼如下:

mysql> CHANGE MASTER TO

->MASTER_HOST=master host name

->MASTER_USER=replication_user_name

-> MASTER PASSWORD=replication_password

->MASTER_LOG_FILE='recorded_log_file_name

->MASTER_LOG_POS=recorded _log_position

舉例說明如下:

CHANGE MASTER TO MASTER_HOST='192.168.56.103',MASTER_PORT=3306,MASTER_USER='rep1',MASTER_PASSWORD='1234test' ,MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=428;

8、在從庫上,啓動 slave線程:

9、這時slave上執行 show processlist命令將顯示類似如下的進程:

執行show slave status;將顯示:

11、在master上執行 show processlist命令將顯示類似如下的進程:

12、測試一下:

在主庫上新建數據庫和表,並插入數據,看看從庫中是否會自動創建相關的數據庫和表、插入數據。

新建數據庫和表之前:

主庫中創建數據庫orders和表order_exp,並插入數據

檢查從庫

半同步複製

在 MySQL5.5之前, MySQL的複製是異步操作,主庫和從庫的數據之間存在一定的延遲,這樣存在一個隱患:當在主庫上寫人一個事務並提交成功,而從庫尚未得到主庫推送的 Binlog日誌時,主庫宕機了,例如主庫可能因磁盤損壞、內存故障等造成主庫上該事務 Binlog丟失,此時從庫就可能損失這個事務,從而造成主從不一致。

而半同步複製,是等待其中一個從庫也接收到Binlog事務併成功寫入Relay Log之後,才返回Commit操作成功給客戶端;如此半同步就保證了事務成功提交後至少有兩份日誌記錄,一份在主庫Binlog上,另一份在從庫的Relay Log上,從而進一步保證數據完整性;半同步複製很大程度取決於主從網絡RTT(往返時延),以插件 semisync_master/semisync_slave 形式存在。

安裝比較簡單,在上一小節異步複製的環境上,安裝半同步複製插件即可。

(1)首先,判斷 MySQL服務器是否支持動態增加插件:

mysql> select @@have_dynamic_loading;

2)確認支持動態增加插件後,檢查 MySQL的安裝目錄下是否存在插件:

安裝插件:

在主庫上安裝插件semisync_master.so:

mysql> install plugin rpl_semi_sync_master SONAME 'semisync_master.so'

從庫上則安裝 semisync_slave.so插件:

mysql> install plugin rpl_semi_sync_slave SONAME 'semisync_slave.so';

安裝完成後,從 plugin表中能夠看到剛纔安裝的插件

mysql> select * from mysql.plugin;

3)需要分別在主庫和從庫上配置參數打開半同步semi-sync,默認半同步設置是不打開的,主庫上配置全局參數:

mysql> set global rpl_semi_sync_master_enabled=1;

mysql> set global rpl_semi_sync_master timeout 30000;

從庫上一樣配置全局參數:

mysql> set global rpl_semi_sync_slave_enabled=1;

4)其他步驟同異步複製

讀寫分離實戰

SpringBoot+MyBatis結合MySQL讀寫分離

讀寫分離要做的事情就是對於一條SQL該選擇哪個數據庫去執行,至於誰來做選擇數據庫這件事兒,無非兩個,要麼中間件幫我們做,要麼程序自己做。因此,一般來講,讀寫分離有兩種實現方式。第一種是依靠中間件幫我們做SQL分離;第二種是應用程序自己去做分離。這裏我們選擇程序自己來做,主要是利用Spring提供的路由數據源,以及AOP

然而,應用程序層面去做讀寫分離最大的弱點(不足之處)在於無法動態增加數據庫節點,因爲數據源配置都是寫在配置中的,新增數據庫意味着新加一個數據源,必然改配置,並重啓應用。當然,好處就是相對簡單。

主要思路:

1、選擇數據庫執行這個關鍵性問題,考察Spring的封裝體系中

 的AbstractRoutingDataSource內部維護了一組目標數據源,並且做了路由key與目標數據源之間的映射,提供基於key查找數據源的方法。因此我們需要聲明一個自己的的數據源MyRoutingDataSource來負責,並應該繼承自AbstractRoutingDataSource,覆蓋determineCurrentLookupKey方法,這個方法返回結果就是我們對數據源的選擇。

2、考察AbstractRoutingDataSource的內部,有兩個容器:

其中targetDataSources是指我們通過方法

設置的目標數據源,而resolvedDataSources是實際的數據源,Spring通過方法

進行了轉換。

而在實際運行時,Spring通過getConnection方法獲取數據庫連接,在getConnection方法中最終調用 determineTargetDataSource方法來定位實際的數據源,在determineTargetDataSource方法就用到了我們前面覆蓋的determineCurrentLookupKey方法來從容器resolvedDataSources獲得實際的數據源

3、所以,我們先定義一個枚舉類

來表示當前有幾個數據源,並用這個類作爲兩個數據源容器的key。

4、同時爲了保證線程安全,每個對數據庫操作的線程當前所持有的數據源對象,我們用一個ThreadLocal來保存

然後用類DBContextHolder進行包裝,並對外提供主從庫切換方法、每個線程的數據源get和set方法

5、在我們自己的的數據源MyRoutingDataSource的determineCurrentLookupKey方法中,只需要即可。

6、在application.yml中配置原生數據源後通過配置類DataSourceConfig加載,

7、然後在 DataSourceConfig中生成一個虛擬數據源myRoutingDataSource的實例,專門負責實際數據源的持有和路由,

8、應用中的事務管理器和MyBatis均使用這個虛擬數據源myRoutingDataSource

9、爲了減少對業務代碼的侵入,我們定義了一個SpringAop類DataSourceAop,其中定義了兩個切點,slavePointcut和masterPointcut,按照方法名進行數據源的切換,比如查詢方法要求爲query開頭等等。

這樣默認情況下,所有的查詢都走從庫,插入/修改/刪除走主庫

10、特殊情況是某些情況下我們需要強制讀主庫,針對這種情況,我們定義一個註解用該註解標註的就讀主庫,對應的,修改下DataSourceAop中切點的定義。

11、測試下我們的代碼即可,對應的代碼在模塊rw-separation下。

Mysql+Keepalived實現雙主高可用

什麼是Keepalived?

Keepalived是一個工作在IP/TCP協議棧的IP層,TCP層及應用層實現交換機制的軟件。

Keepalived的作用是檢測服務器的狀態,如果有一臺服務器宕機,或工作出現故障,Keepalived將檢測到,並將有故障的服務器從系統中剔除,同時使用其他服務器代替該服務器的工作,當服務器工作正常後Keepalived自動將服務器加入到服務器羣中,這些工作全部自動完成,不需要人工干涉,需要人工做的只是修復故障的服務器。

原理如下:

IP層: Keepalived會定期向服務器羣中的服務器發送一個ICMP的數據包(既我們平時用的Ping程序),如果發現某臺服務的IP地址沒有激活,Keepalived便報告這臺服務器失效,並將它從服務器羣中剔除。

TCP層:主要以TCP端口的狀態來決定服務器工作正常與否。如web server的服務端口一般是80,如果Keepalived檢測到80端口沒有啓動,則Keepalived將把這臺服務器從服務器羣中剔除。

應用層:對指定的URL執行HTTP GET。然後使用MD5算法對HTTP GET結果進行求和。如果這個總數與預期值不符,那麼測試是錯誤的,服務器將從服務器池中移除。該模塊對同一服務實施多URL獲取檢查。

Keepalived是基於VRRP協議的一款高可用軟件。Keepailived有一臺主服務器和多臺備份服務器,在主服務器和備份服務器上面部署相同的服務配置,使用一個虛擬IP地址對外提供服務,當主服務器出現故障時,虛擬IP地址會自動漂移到備份服務器。

名詞解釋:

VRRP(Virtual Router Redundancy Protocol,虛擬路由器冗餘協議),由IETF提出的解決局域網中配置靜態網關出現單點失效現象的路由協議,1998年已推出正式的RFC2338協議標準。相類似的協議有:GLBP(思科Cisco獨有), HSRP等等。

和HAProxy的區別是什麼?

HAProxy請求分發
可以理解爲類似於通過nginx來作後端的負載均衡,haproxy可以通過監聽一個統一的端口對外提供能力,然後內部進行分發。除支持http7層處理外,還順便爲mysql支持4層轉發。(更高級的可以考慮採用lvs) 。

舉例說明,將兩個mysql分別配置在haproxy當中,程序進行訪問時,就不再訪問具體的mysql機器,而是訪問這個haproxy所在的機器。這裏就提到需要一個額外的機器來提供服務,但是由於只爲haproxy使用,其根據很低,一個最低配機器即可,費用不大。同時,相應的端口也填寫haproxy所暴露的端口即可。對外即認爲也只仍然只有一個mysql,即對外是透明的。

haproxy在進行處理時,將自己根據相應的策略進行轉發,最簡單的策略就是輪詢(ribbon),當然其它加權或者是隨機等,需要具體進行配置。同時,根據設定的具體時間間隔,對後端服務進行有效性檢測,當mysqlA或B不能工作時,將自動從可用列表中移除。

在加上haproxy之後,負載的問題被解決,但另一個問題又來了,即服務單點的問題。如果一旦這個haproxy機器掛掉(或網絡原因)。雖然,mysql服務器沒掛,但整個服務也是不可用了。前端程序不會自動退回到去訪問原始的mysql(甚至由於防火牆的問題,它也不能訪問)。這時候就要用到另一個東西,保證haproxy不會成爲單點,即KeepAlived。

保證服務不會單點的作法就是加機器,但加機器就會出現多個ip,如何保證前端程序使用單個ip又能保證後端的實際處理機器爲多臺,這就是KeepAlived的作用。

我們爲了保證haproxy的高可用,已經又加了一個機器,即爲haproxyA和haproxyB(相當於原來的2個mysql機器,又加了2臺haproxy機器)。

通過KeepAlived,我們可以創造出第3個IP(也稱虛擬IP),由虛擬IP來對外提供服務,但是與HaProxy的轉發性質不同,這裏的虛擬IP實際上就是HaProxyA和HaProxyB的一個同名映射。可以理解爲HaProxyA和HaProxyB都在爭搶這個ip,哪個爭搶到了,就由哪個來提供服務。

因爲KeepAlived不提供任何處理能力,實際上最終的處理落在能夠處理信息的程序上。因此,我們可以將KeepAlived和HaProxy部署在一起,即KeepAlived負責搶ip,接收前端的請求,在接收到了請求之後,由系統自動將請求分發到同一個機器上的HaProxy上進行處理。即一個機器有2個IP,ip1負責接收請求,ip2負責實際的信息處理。

最終部署模型

a、MysqlA(ip5)和MysqlB(ip6)水平擴展,通過雙主進行數據通信,並且同時提供服務能力

b、通過HaProxyA(ip3)和HaProxyB(ip4)提供mysql的負載能力,將請求路由到指定的mysql服務器,同時監控後端的mysql數據庫可用性,如果取消這一層,則Mysql的兩個服務器不能同時提供寫服務。

c、將KeepAlivedA和KeepAlivedB分別和HaProxyA和HaProxyB部署在一起,同時綁定VIP,對外提供訪問ip,同時監控本機的HaProxy的可用性

Mysql+Keepalived雙主搭建說明

最關鍵的部分其實是Mysql的兩個服務器要互爲主從,所以在my.cnf的配置文件中,除了log-bin參數以外,還需要增加並設置參數logs-slave-updates=1

logs-slave-updates參數解釋:

通常情況,從服務器從主服務器接收到的更新不記入它的二進制日誌。該選項告訴從服務器將其SQL線程執行的更新記入到從服務器自己的二進制日誌。爲了使該選項生效,還必須用--logs-bin選項啓動從服務器以啓用二進制日誌。如果想要應用鏈式複製服務器,應使用--logs-slave-updates。例如,可能你想要這樣設置:

A -> B -> C

也就是說,A爲從服務器B的主服務器,B爲從服務器C的主服務器。爲了能工作,B必須既爲主服務器又爲從服務器。你必須用--logs-bin啓動A和B以啓用二進制日誌,並且用--logs-slave-updates選項啓動B。

Mysql其餘的配置同異步複製,比如:Aß--àB

那麼A、B上都要配置複製用戶名,都要執行CHANGE MASTER TO語句,都要執行start slave語句。

至於Keepalived的安裝和配置比較複雜,在技能上至少要求可以編寫sh腳本。有興趣的同學可以參考:

https://www.cnblogs.com/ivictor/p/5522383.html

分庫分表綜述

分庫分表需求的產生和定義

分庫分表, 顧名思義, 就是使用多個庫和多個表甚至多個數據庫實例來存儲海量的數據。爲了分散數據庫的壓力, 我們會採用分庫分表的方案, 將一個表結構分爲多個表, 或者將一個表的數據分片後放入多個表, 這些表可以放在同一個庫裏, 也可以放到不同的庫裏, 甚至可以放在不同的數據庫實例上。

不同的業務規模階段數據庫的變化

單實例單庫

單庫單表是最常見的數據庫設計,例如現在我們有用戶表(以下簡稱 user表),訂單表(以下簡稱 order表), 所有用戶和訂單的信息都被存儲在同一個數據庫中。隨着業務規模的增加,我們的數據庫在架構上的變化有幾種可能的路徑。

多實例(多機)單庫,讀寫分離

用戶表和訂單表仍然歸屬同一個數據庫,但是數據庫有多份,並放在多個實例上,同時實例之間啓用了複製功能和讀寫分離。

多庫(單實例/多實例)

用戶表和訂單表分開來放到不同的數據庫上,比如用戶庫和訂單庫。同時這些庫分佈在單個數據庫實例上或者多個數據庫實例上都有可能。至於是否啓用讀寫分離則看情況而定。

多庫多表

當業務規模進一步增加,單表的數據已經超過了一定量,比如1000萬,這個時候,就會進入另一個變化階段。會將不同的業務表拆分到不同業務數據庫,同時業務表也會進行拆分,也就是分庫分表。

什麼情況下需要分庫分表?

總結一下,首先, 如果在一個庫中的表數據超過了一定的數量, 例如在 MySQL的表中達到千萬級別, 就需要考慮進行分表, 這樣, 數據就被分散在不同的表上, 單表的索引大小得到了撓制, 會提升査詢性能, 對索引及表結構的變更會很方便和高效 。 當數據庫實例的吞吐量達到性能的瓶頸時,我們需要擴展數據庫實例,讓每個數據庫實例承擔其中一部分數據庫的請求,分解總體的大請求量帶來的壓力。

所以,基本上就是兩點:

如果在數據庫中表的數量達到了一定的量級, 則需要進行分表, 分解單表的大數據量對索引査詢帶來的壓力, 並方便對索引和表結構的變更 。

如果數據庫的吞吐量達到了瓶頸, 就需要增加數據庫實例, 利用多個數據庫實例來分解大量的數據庫請求帶來的系統壓力。

分庫分表後,如何對數據進行訪問?

客戶端分片

客戶端分片就是使用分庫分表的數據庫的應用層直接操作分片邏輯, 分片規則需要在同一個應用的多個節點間進行同步, 每個應用層都嵌入一個操作切片的邏輯實現 (分片規則),這一般通過依賴 Jar包來實現具體的實現方式分爲三種:在應用層直接實現、通過定製JDBC協議實現、通過定製 ORM框架實現。

應用層直接實現

這是一種非常通用、 簡單的解決方案, 直接在應用層讀取分片規則, 然後解析分片規則, 根據分片規則實現切分的路由邏輯,從應用層直接決定每次操作應該使用哪個數據庫實例、數據庫及哪個數據庫的表等。

這種解決方案雖然侵入了業務, 但是實現起來比較簡單, 適合快速上線, 而且性能更高,切分邏輯是自己開發的,如果在生產上產生了問題,則都比較容易解決。但是,這種實現方式會讓數據庫保持的連接比較多。

定製JDBC協議

在應用層直接實現或多或少使切片邏輯侵入了應用的業務實現, 而且對開發人員的能力要求較高, 開發人員既需要實現業務邏輯, 也需要實現框架需求。

爲了讓開發人員把精力集中在業務邏輯的實現上,我們可以通過定製 JDBc協議來實現,也就是針對業務邏輯層提供與 JDBC 一致的接口,讓業務開發人員不必關心分庫分表的實現,讓分庫分表在 JDBC的內部實現,對業務層保持透明。

這種解決方案對業務透明,不侵入業務,讓開發和分庫分表的配置人員在一定程度上可以分離,可以讓開發人員更大限度地聚焦在業務邏輯的實現上,但開發人員需要理解 JDBC協議才能實現分庫分表邏輯。Sharding JDBC便採用了這種方案。

定製ORM框架

因爲現在ORM框架有了大量的應用,因此有了通過定製ORM框架實現分庫分表的方案,也就是把分片規則實現放到ORM框架中或者通過ORM框架支持的擴展機制來完成。

代理分片

代理分片就是在應用層和數據庫層之間增加一個代理層。代理層對外提供與JDBC兼容的接口給應用層,應用層的開發人員不用關心分片規則,只需要關心業務邏輯的實現。

這種方案的優點是讓應用層的開發人員專注於業務邏輯的實現, 把分庫分表的配置留給代理層做;缺點是增加了代理層。儘管代理層是輕量級的轉發協議,但是畢竟要實現 JDBC協議的解析, 並通過分片的路由規則來路由請求, 對每個數據庫操作都增加了一層網絡傳輸, 這對性能是有影響的,需要維護增加的代理層,也有硬件成本,還要有能夠解決Bug的技術專家, 成本很高。

通過代理分片實現的主要框架有 Cobar 、Mycat和Sharding-Proxy等。

分庫分表第三方產品

京東金融的ShardingSphere  

網址:http://shardingsphere.io/index_zh.html

脫胎於噹噹網的Sharding-JDBC,在京東金融得到重生,並且目前已經進入Apache孵化器,目前已經組成了分佈式數據庫中間件解決方案組成的生態圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(計劃中)這3款相互獨立的產品組成。 他們均提供標準化的數據分片、分佈式事務和數據庫治理功能。作爲京東第一個進入Apache基金會的開源產品,值得持續關注。

開源的Cobar和MyCat

Cobar 是由 Alibaba 開源的 MySQL 分佈式處理中間件,目前已停止維護,它可以在分佈式的環境下看上去像傳統數據庫一樣提供海量數據服務。社區使用過程中發現存在一些比較嚴重的問題及使用限制,於是Mycat誕生。開源以後,最終Mycat發展成爲一個由衆多軟件公司的實力派架構師和資深開發人員維護的社區型開源軟件。

Mycat對Cobar的代碼進行了徹底的重構,使用NIO重構了網絡模塊,並且優化了Buffer內核,增強了聚合,Join等基本特性,同時兼容絕大多數數據庫成爲通用的數據庫中間件。1.4 版本以後完全的脫離基本Cobar內核。

目前看來,Mycat雖然市場佔有率很高,但是Mycat現在呈現一個發展停滯的狀態,未來前景如何還有待觀察。

網址: http://www.mycat.io/

其他第三方的中間件

例如Oneproxy ,由原支付寶的首席DBA爲創始人的公司開發,提供了免費版和商用版。使用C&C++開發,單個OneProxy實例可支持高達40W的QPS/TPS 。商用版則有更強的性能,更多的功能,還可以得到正式的技術支持。

有專門的運維或者DBA的公司,則可以考慮使用。最大的好處是對開發人員完全透明。

網址: http://www.onexsoft.com/

更多的分庫分表中間件:

TDDL:已經不再使用,下一代是 DDRS。淘寶根據自己的業務特點開發了 TDDL (Taobao Distributed Data Layer 外號:頭都大了 )框架。 TDDL 通過部署在客戶端的 jar 包,將用戶的 SQL 路由到指定的數據庫中。

Heisenberg:源自Coba,目前並不活躍,由百度員工個人編寫。

Atlas : 360 Web平臺部基礎架構團隊開發維護的一個基於 MySQL 協議的數據中間層項目。它是在mysql-proxy 0.8.2版本的基礎上,對其進行了優化,增加了一些新的功能特性

Vitess:谷歌開發的數據庫中間件,集羣基於 ZooKeeper 管理,通過 RPC 方式進行數據處理

DRDS:阿里分佈式關係型數據庫服務(Distribute Relational Database Service,簡稱DRDS)是一種水平拆分、可平滑擴縮容、讀寫分離的在線分佈式數據庫服務。前身爲淘寶 TDDL。

還有:Mango,Amoeba等等

常用切分方式

垂直切分

把單一庫中的表按業務拆分到多個庫,或者把單一的表拆分成多個表,並分散到不同的數據庫(主機)上。

垂直切分除了用於分解單庫單表的壓力, 也用於實現冷熱分離, 也就是根據數據的活躍度進行拆分, 因爲對擁有不同活躍度的數據的處理方式不同 。

我們可將本來可以在同一個表中的內容人爲地劃分爲多個表。比如在微博系統的設計中, 一個微博對象包括文章標題、作者、分類、創建時間等屬性字段,這些字段的變化頻率低,査詢次數多,叫作冷數據。而博客的瀏覽量、回覆數、 點贊數等類似的統計信息, 或者別的變化頻率比較高的數據, 叫作活躍數據或者熱數據。我們把冷熱數據分開存放,就叫作冷熱分離,在 MysQL的數據庫中,冷數據査詢較多, 更新較少可以用MyISAM引擎,而熱數據更新比較頻繁,適合使用InnoDB存儲引擎,這也是垂直拆分的一種。

其次, 對讀多寫少的冷數據可配置更多的從庫來化解大量査詢請求的壓力; 對於熱數據, 可以使用多個主庫構建分庫分表的結構,對於一些特殊的活躍數據或者熱點數據,也可以考慮使用 Memcache、 Redis之類的緩存,等累計到一定的量後再更新數據庫。

優點:

拆分後業務清晰,拆分規則明確。

系統之間進行整合或擴展很容易。

按照成本、應用的等級、應用的類型等將表放到不同的機器上,便於管理。

便於實現動靜分離、冷熱分離的數據庫表的設計模式。

數據維護簡單。

缺點:

部分業務表無法關聯(Join),只能通過接口方式解決,提高了系統的複雜度。

事務處理複雜。

水平切分

根據表中數據的邏輯關係,將同一個表中的數據按照某種條件拆分到多臺數據庫(主機)上。在每個表中包含一部分數據, 所有表加起來就是全量的數據。這種切分方式根據單表的數據量的規模來切分, 保證單表的容量不會太大, 從而保證了單表的査詢等處理能力,例如將用戶的信息表拆分成user1、 User2等,表結構是完全一樣的。我們通常根據某些特定的規則來劃分表, 比如根據用戶的 ID來取模劃分。

優點:

單庫單表的數據保持在一定的量級,有助於性能的提高。

 切分的表的結構相同,應用層改造較少,只需要增加路由規則即可。

 提高了系統的穩定性和負載能力 。

缺點:

切分後,數據是分散的,很難利用數據庫的 Join操作,跨庫 Join性能較差。

 分片事務的一致性難以解決。

 數據擴容的難度和維護量極大。

 我們就可以根據自己的業務需求來選擇, 通常會同時使用這兩種切分方式, 垂直切分更偏向於業務拆分的過程, 在技術上我們更關注水平切分的方案。

水平切分的路由和常見分片緯度

水平切分的路由過程

我們在設計表時需要確定對錶按照什麼樣的規則進行分庫分表。 例如, 當有新用戶登錄時, 程序需要確定將此用戶的信息添加到哪個表中; 同理, 在登錄時我們需要通過用戶的賬號找到數據庫中對應的記錄, 所有這些都需要按照某一規則進行路由請求, 因爲請求所需要的數據分佈在不同的分片表中。

通過分庫分表規則査找到對應的表和庫的過程叫作路由。假設,分庫分表的規則是 user_id%4, 當用戶新註冊了一個帳號時,如果用戶的 ID是123,我們就可以通過123%4=3確定此賬號應該被保存在 User3表中。當 ID爲123的用戶登錄時,我們可通過123 % 4 = 3計算後, 確定其被記錄在 User3中 。

水平切分的分片維度

1)按照哈希切片

對數據的東個字段求哈希, 再除以分片總數後取模, 取模後相同的數據爲一個分片的將數據分成多個分片的方法叫作哈希分片,合希分片常常應用於數據沒有時效性的情況,這種切片方式的好處是數據切片比較均勻, 對數據壓力分散的效果較好, 缺點是數據分片後, 對於査詢需求需要進行聚合處理,

2)按照時間切片

按照哈希切片不同, 這種方式是按照時間的範圍將數據分佈到不同的分片上的, 例如, 我們可以將交易數據按照月進行切片, 或者按照季度進行切片, 由交易數據的多少來決定按照什麼樣的時間週期對數據進行切片,,這種切片方式適用於有明顯時間特點的數據,例如,距離現在1個季度的數據訪問頻繁,距離現在2個季度的數據不需要更新,距離現在3個季度的數據沒有查詢要求。就可以按時間進行分片,用不同的介質進行存放。

在實踐中,按照哈希切片和按時間切片都很常見,而且有時會兩種結合起來使用。

分庫分表引發的問題

 擴容與遷移

在分庫分表後, 如果涉及的分片已經達到了承載數據的最大值, 就需要對集羣進行擴容。 擴容是很麻煩的, 一般會成信地擴容。

分庫分表維度導致的查詢間題

在分庫分表以後, 如果査詢的標準是分片的主鍵, 則可以通過分片規則再次路由並查詢。

但是對於其他主鍵的查詢、範圍査詢、關聯査詢、查詢結果排序等,並不是按照分庫分表維度來査詢的。例如,用戶購買了商品,需要將交易記錄保存下來,那麼如果按照買家的緯度分表,則每個買家的交易記錄都被保存在同一表中, 我們可以很快、 很方便地査到某個買家的購買情況, 但是某個商品被購買的交易數據很有可能分佈在多張表中, 査找起來比較麻煩 。 反之, 按照商品維度分表, 則可以很方便地査找到該商品的購買情況, 但若要査找到買家的交易記錄, 則會比較麻煩 。

所以常見的解決方式如下。

( 1 ) 在多個分片表查詢後合併數據集, 這種方式的效率很低。

( 2 ) 記錄兩份數據, 一份按照買家緯度分表, 一份按照商品維度分表,,

( 3 ) 通過搜索引擎解決, 但如果實時性要求很高, 就需要實現實時搜索

實際上,在高併發的服務平臺下,交易系統是專門做交易的,因爲交易是核心服務,所以需要和査詢系統分離, 査詢一般通過其他系統進行, 數據也可能是冗餘存儲的。

在某電商交易平臺下, 可能有買家査詢自己在某一時間段的訂單, 也可能有賣家査詢自已在某一時間段的訂單, 如果使用了分庫分表方案, 則這兩個需求是難以滿足的, 因此, 通用的解決方案是, 在交易生成時生成一份按照買家分片的數據副本和一份按照賣家分片的數據副本,查詢時分別滿足之前的兩個需求,因此,查詢的數據和交易的數據可能是分別存儲的,並從不同的系統提供接口。

另外,在電商系統中,在一個交易訂單生成後,一般需要引用到訂單中交易的商品實體如果簡單地引用,若商品的金額等信息發生變化,則會導致原訂單上的商品信息也會發生變化這樣買家會很疑惑。因此,通用的解決方案是在交易系統中存儲商品的快照,在査詢交易時使用交易的快照,因爲快照是個靜態數據,永遠都不會更新,所以解決了這個問題。可見查詢的問題最好在單獨的系統中使用其他技術來解決,而不是在交易系統中實現各類查詢功能。當然當然,也可以通過對商品的變更實施版本化,在交易訂單中引用商品的版本信息,在版本更新時保留商品的舊版本,這也是一種不錯的解決方案。

3.跨庫事務難以實現

要避免在一個事務中同時修改數據庫db0和數據庫dbl中的表,因爲操作起來很複雜,對效率也會有一定的影響。

4.同組數據跨庫問題

要儘量把同一組數據放到同一臺數據庫服務器上,不但在某些場景下可以利用本地事務的強一致性,還可以使這組數據自治。以電商爲例,我們的應用有兩個數據庫db0和dbl,分庫分表後,按照id維度,將賣家A的交易信息存放到db0中。當數據庫dbl掛掉時,賣家A的交易信息不受影響,依然可以正常使用。也就是說,要避免數據庫中的數據依賴另一數據庫中的數據。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章