在分佈式系統中,咱們每每會考慮系統的高可用,對於無狀態程序來說,高可用實施相對簡單一些,縱向、橫向擴展起來相對容易,然而對於數據密集型應用,像數據庫的高可用,就不太好擴展。咱們在考慮數據庫高可用時,主要考慮發生系統宕機意外中斷的時候,儘量的保持數據庫的可用性,保證業務不會被影響;其次是備份庫,只讀副本節點須要與主節點保持數據實時一致,當數據庫切換後,應當保持數據的一致性,不會存在數據缺失或者數據不一致影響業務。不少分佈式數據庫都把這個問題解決了,也可以經過很靈活的方式去知足業務需求,如同步、半同步方式、數據副本數量、主從切換、failover 等等(下面會提到),然而咱們平時使用的社區官方版 mysql5.7及之前的版本 (不包括 Mysql 其餘分支像 PhxSQL,Percona XtraDB Cluster,MariaDB Galera Cluster) 都在支持分佈式和系統可用性這塊處理得不是很完善。針對這個系列問題,下面分析下如何解決這個問題。html
在這期間發現並提交合並了一個mha在線切換master 而致使master 和slave 數據不一致的bug前端
何爲failovernode
提mha以前,提早普及一下failover。何爲failover,即當活動的服務或應用意外終止時,快速啓用冗餘或備用的服務器、系統、硬件或者網絡接替它們工做,故障轉移(failover)與交換轉移操做基本相同,只是故障轉移一般是自動完成的,沒有警告提醒手動完成,而交換轉移須要手動進行,對於要求高可用和高穩定性的服務器、系統或者網絡,系統設計者一般會設計故障轉移功能。
簡單來講就是當系統某塊服務不可用了,系統其它服務模塊可以自動的繼續提供服務,有不少設計良好的開源軟件設計都會自動包含failover,像負載均衡nginx,haproxy能夠支持後端檢測,backup,當檢測到後端upstream(endpoints)異常,會自動的無縫進行切換到正常的backup上,而後像分佈式的數據密集型應用也會包含failover,包括mongdb副本集,etcd/zookeeper 節點選舉,elasticsearch副本集等等,當存在部分數據節點異常,選舉數據節點爲master/primary/leader,甚至消息隊列像rabbitmq鏡像隊列,kafka replicas都會包含failover。mysql
連接:
zookeeper leader選舉
etcd-raft
etcd-raft可視化
mongdb副本集
rabbitmq鏡像隊列
kafka ISR和同步
elasticsearch replicslinux
數據複製nginx
前面提到了failover,那麼既然系統支持failover,那必需要保證能有 backup 或者是 replics 來做爲新 "master"(這裏把leader/master/primary都統稱爲master )繼續提供服務。前面提到了不少開源軟件設計過程當中就自帶了數據同步功能,就是數據全部的插入、刪除、更新都在 master 上進行,而後數據會同步到 slave 裏,簡單來講須要保持 master-slave 數據一致。這裏同步的方式能夠像 mysql-bin log,mongdb optlog 經過日誌的方式實現,將 update(),delete(),insert() 等操做記錄到 log 中,而後這些語句都轉發給每一個從庫,每一個從庫解析並執行該SQL語句,就像從客戶端請求收到同樣,在從庫中重放該數據;也能夠經過傳輸預寫式日誌(wal)的方式,日誌優先寫入到磁盤,如SSTables 和 LSM 樹實現的引擎,因此日誌都是包含全部數據庫寫入的僅追加字節序列,可使用徹底相同的日誌在另外一個節點上構建副本,將日誌寫入磁盤同時,主庫能夠經過網絡將其發送給其它從節點,如etcd狀態機同步; 還有的方式是經過集羣內部的進程直接發送須要同步的數據,如rabbitmq鏡像隊列。git
下圖就是一個數據複製的應用場景:一個用戶有寫入操做更新到寫庫,而後其餘用戶可能從從庫中讀取數據,可能數據是最新的,也可能出現從庫因爲延時不是最新的,複製系統針對這種場景化分爲了幾種複製方式。
github
複製系統的一個重要細節是:複製是同步(synchronously)仍是異步(asynchronousl)算法
關於複製方式:sql
同步複製(synchronous):
同步複製就是當有數據更新請求到 master 節點,須要保證該更新操做在 slave 節點上執行成功後才返回客戶端,從庫保證有與主庫徹底一致的最新數據副本。若是主庫忽然失效,咱們能夠確信這些數據仍能在從庫上找到,可是該方式也有缺點,若是從庫沒有響應(好比它已經崩潰,或者出現網絡故障,或其它任何緣由),主庫就沒法處理寫入操做,主庫必須阻止全部寫入,並等待同步副本再次可用,這個過程數據庫是沒法更新插入數據。
異步複製(asynchronous):
異步複製就是當有更新數據請求到 master 節點,master 操做結束直接返回客戶端,不須要 slave 確認,slave 後臺從 master 更新數據。該方式的缺點是,若是主庫失效且不可恢復,則任何還沒有複製給從庫的寫入都會丟失,這意味着即便已經向客戶端確認成功,寫入也不能保證持久(Durable)。該方式的優勢是:即便全部的從庫都落後了,主庫也能夠繼續處理寫入操做,服務繼續運行。
半同步複製(semi-synchronous):
半同步複製是一種中間策略,當有更新數據請求到 master 節點,須要保證該操做在某個 slave 上也執行成功才最終返回客戶端,若是某個同步的 slave 變得緩慢,則可使一個異步 slave 變爲同步,這樣在保證必定數據一致性的前提下也能保證可靠性(這裏可能會致使數據不一致,還可能產生數據延時)。
mysql的半同步複製(mysql semi-synchronous):
master在執行完更新操做後當即向 slave 複製數據,slave 接收到數據並寫到 relay log (無需執行)後才向 master 返回成功信息,master 必須在接受到 slave 的成功信息後再向客戶端返回響應,僅在數據複製發生異常(slave 節點不可用或者數據複製所用網絡發生異常)的狀況下,master 纔會暫停(mysql 默認約 10 秒左右) 對客戶端的響應,將複製方式降級爲異步複製。當數據複製恢復正常,複製方式將恢復爲半同步複製。
mysql 的數據同步和 failover
mysql 支持相對嚴格的 ACID,是一個性能和穩定性都很是不錯的關係型數據庫,可是對分佈式支持不是很友好,雖然它實現了NDB,不過感受使用不太普遍,國內使用較多的仍是基礎的主從複製方式。mysql支持前面提到的各類數據複製方式,因此只須要針對各類的場景選取對應的複製方式便可。像對可用性要求較高,數據一致性要求較低的能夠選取異步複製;對數據一致性要求很高的場景,金融場景能夠選用強同步複製;互聯網場景對可用性和一致性可能都有必定要求,但要求不是特別高,能夠選擇半同步複製。下面簡述 mysql 主從同步的邏輯
首先開啓mysql master上的 binlog, mysql slave上經過一個 I/O 線程從 mysql master上讀取binlog,而後傳輸到 mysql slave 的中繼日誌中,接着mysql slave 的 sql 線程從中繼日誌中讀取中繼日誌,應用到mysql slave的 數據庫中,這樣就實現了主從數據同步功能。
不過 mysql 自身沒有實現 failover,因此當 master 異常的時候,須要制定策略去實現 failover 並處理數據庫切換。failover 的邏輯是當 master 異常,slave 自動提高爲主庫的,而後讓其餘從庫知道新的 master,並繼續重新的 master同步數據。在這裏咱們就要用到 mha了,一個mysql 高可用管理工具。mha 能作到在 0~30 秒以內自動完成數據庫的故障切換操做,而且在進行故障切換的過程當中,能在最大程度上保證數據的一致性,以達到真正意義上的高可用。關於 mha 的各類細節我這裏就不詳細展開了,這些內容都在官方wike中(文檔真的很是詳細,做者考慮不少通用的場景,不少參數可配置化)。
連接:
mha安裝
mha優點
mha架構
mha配置
等等等....
這裏只分析他實現 failover 的架構與原理,結構以下(官網的圖片,略模糊)
mha 由兩部分組成:
mha manager(管理節點): 單獨部署在一臺獨立的機器上管理多個 master-slave 集羣(最好和mysql相關服務器管理),也能夠部署在一臺 slave 節點上,做用是多mysql server服務的管理,master檢測,master選舉,鏈接檢查,master故障切換等工做。
mha node(數據節點): 運行在每臺 mysql 服務器上,做用是拷貝 master 二進制日誌;而後擁有最新數據的slave上生成差別中繼日誌,應用差別日誌;最後在不中止SQL線程的狀況下刪除中繼日誌。
原理:
(1)從宕機崩潰的master保存二進制日誌事件(binlog events);
(2)識別含有最新更新的slave;
(3)應用差別的中繼日誌(relay log)到其餘的slave;
(4)應用從master保存的二進制日誌事件(binlog events);
(5)使其餘的slave鏈接新的master進行復制;
(6)使其餘的slave鏈接新的master進行復制;
mha須要解決的問題:
如何肯定新的master:
因爲 mysql 沒有像 elasticsearch, etcd 這樣分佈式的集羣決策節點,因此這裏的 master 選舉節點就是 mha manager節點,mha 主要參考幾個因素:1 配置文件中手動配置的候選servers參數 **candidate_master=1**(如: 但願同機房,或則同機架的機器優先爲 master), 2 依據各個 slave 中最新的二進制文件,最新的slave節點便可提高爲master。
如何保證數據一致性:
mha 最大程度的保證數據不丟失,當 mysql master 異常時,可是機器正常提供服務,那麼 mha 會去對比 master 節點與將成爲 master 節點的 slave 節點的數據差,而後將差值數據拷貝到新的 slave,而後應用這部分數據差去補全數據。
若是是 mysql master 異常,機器也異常,那麼系統中保存的二進制 binlog 文件也沒法訪問,這就無法拷貝,那麼會略過拷貝流程,直接會從 salve 候選者中選取新的 master。若是使用 mysql 5.5 的半同步複製,能夠大大下降數據丟失的風險。
節點之間數據如何拷貝:
因爲 mysql 內部沒有作這樣的 bin-log 拷貝功能,因此咱們有自定義的需求去實現複製。這裏 mha 須要依賴 ssh 協議,就是經過 scp 協議傳輸文件(搭建 mha 須要保證各個主機間能夠 ssh 互通)。
其餘 slave 節點如何知道新的 master:
當候選 master 提高爲 master 後,mha manager 會用 mysql change replication 的方式更改目前集羣的全部 slave的同步源。
管理節點如何解決網絡分區問題:
在上邊的網絡結構中,咱們能夠猜到系統可能存在一個很大問題,就是網絡分區。網絡分區指的是因爲網絡分離形成系統分裂爲兩個集羣,各自相互不信任。對應無狀態的系統,幾乎沒有影響,正常處理請求,好比nginx;數據系統出現分區問題時,若是系統設計或者配置存在不合理就會致使數據不一致,而這個問題修復起來會很是複雜,因此 elasticsearch , etcd, mongdb 等天生支持分佈式的數據系統中,都有機制避免因爲網絡分區致使的數據不一致問題,解決方式是讓集羣大多數能正常通訊的節點正常服務。好比你的集羣是有 5 個節點,分區致使一個分區 2 個節點,一個分區 3 個,那麼 2 個節點的分區就會被認爲是異常的,不能正常提供服務,這裏也會有一些特定的算法能夠解決相似的問題,如raft。
好比下邊這個圖,3個節點,那麼最少信任集羣的節點數應該有2個,因此C節點會被標記爲異常,不會正常提供服務
mha 的網絡分區和上邊提到的有點不一樣,因爲集羣只有一個 mha management(注意這裏只能部署一個management,不容許部署多個,不然會出現異常),因此 mha management 不存在腦裂問題,這裏指的網絡分區指的 mha management 節點與 mysql master 節點出現分區,以下:
當 mha management 和 mysql master 出如今兩個分區,mha 認爲 mysql master 異常,可是實際 mysql master 和 mysql slave都正常工做,提供服務,可是這時候 mha 仍是會切換 master,可能對應用程序來講(若是前端有負載均衡器),會出現2個master,而致使數據不一致。 面對這種狀況,mha 提供了一種二次檢測的方式,既多條鏈路檢測,1 條鏈路是 mha management 是經過直接檢測 mysql master節點, 2 其餘鏈路是 mha managerment 經過ssh登陸到其餘 slave 的方式去檢測 mysql master 是否正常,這樣就可以解決 mha managerment 和 mysql master 的網絡分區問題,防止誤切換。
客戶端應用自動恢復
通常來講自帶 failover 的分佈式系統系統都可以本身恢復服務,像elasticsearch , etcd, 他們客戶端和集羣都可以自動感知集羣節點的變化,客戶端鏈接的是一組集羣地址,看下邊示例,像 etcd 鏈接的服務端地址 Endpoints []string 是個數組,這就保證了當某個節點 ip 失效,或者機器不能訪問,機器異常,機器負載的狀況加, 都有其餘節點進行處理,etcd鏈接以下:
cfg := client.Config {
Endpoints: []string{" http://127.0.0.1:2379"},
Transport: client.DefaultTransport, // set timeout per request to fail fast when the target endpoint is unavailable
HeaderTimeoutPerRequest: time.Second,
}
然而像 mysql 默認的鏈接方式,應用 tomcat 或其餘 client 鏈接數據庫的默認的方式是mysql 驅動,就無法鏈接一個數組。因此咱們的解決方案是要減小客戶端感知,減小邏輯變動,讓客戶端和原來同樣只須要鏈接一個 ip就好,這裏的 ip是 proxy ip, 這裏會有多種方式(這裏不考慮分片和其餘高級的路由,只考慮對應用鏈接,proxy的高可用方式能夠經過keepalived來配合作互備)
經過代理的方式對客戶端體驗最好,原理上是 proxy 解析了mysql協議,而後根據不一樣的庫,表,請求類型路由(讀寫分離)到後端合適的 mysql 服務器,可是由於加了這樣一個 7 層的proxy解析, 因此性能會損失,通常在 20% 左右,上邊的各個 proxy 會有各自的優點,和功能,詳情能夠去看看相關對比,咱們須要作的就是在 proxy 後端配置咱們的服務應用組,配置讀寫分離,當 master異常,可以切換。
4層 proxy 就不會解析出 mysql 7層 協議,只能解析4層,因此保證 mysql 後端端口通就能夠了,就是檢測到後端master 不可用的時候,切換到 backup master,因爲是 4層協議 因此不能配置自動讀寫分離,只能單獨配置 master 端口,slave 端口了(若是配置keepalived能夠自定義有腳本能夠進行切換,自定義腳本能夠配置主從同步延時)
最後這個方式邏輯就是:
手動配置vip: 在 master 機器上配置虛擬vip,當mysql master異常,mha management 經過配置的 master_ip_online_change_script 的腳本,當 master 異常的時候,利用該腳本在新的 slave 上啓動新的 ip,這樣對客戶端來講也是無影響的,配置vip比較容易
關於配置流程[配置流程](www.cnblogs.com/gomysql/p/3…),這裏說的很清楚了,我就不詳細解析了。
咱們生產環境其實是使用maxscale,利用它來進行讀寫分離,他的文檔特別全面,咱們選用他的緣由是他穩定高效,能無縫配合 mha,不須要 mha 配置任何 ip 切換之類的邏輯,當 mha 進行切換後,maxscale 會自動的進行感知系統中 servres 的角色,master切換它能感知到,對應用是徹底無影響,以下圖:
總結:
這裏解決的是 mysql 原官方社區版的高可用問題,利用 mha + maxscale 的方式,該方案能以最小的代價對現有系統進行變動,提升系統的可用性和穩定性。前面提到之前版本(5.7之前) mysql 對集羣化支持相對較弱,可是其實 mysql 也一直在發展,社區也開發出了不少方案,像PhxSQL,Percona XtraDB Cluster,MariaDB Galera Cluster,mysql 官方也開發出了使用 MySQL Group Replication的GA,來使用分佈式協議來解決數據一致性問題了,很是期待將來愈來愈多的解決方案被提出,來更好的解決mysql高可用問題。