數據庫高可用架構設計,看這篇就夠了!!! 數據庫高可用架構設計,看這篇就夠了!!! 一、 高可用背景 二、 數據庫複製原理 三、 常見的架構方案 四、 容災方案 五、 兜底策略:數據覈對 總結

數據庫高可用架構設計,看這篇就夠了!!!

又趕上一年一度的金九銀十的日子,這段期間的招聘崗位相對前幾個月會多些,如果在目前公司沒有進步、沒有前途時,這段時間可以準備一下,去外面看看機會。不過在外面找工作時,可以提前在網上看看招聘信息,看看自己是否達到公司要求。如果多看下高薪資的技術人員招聘要求時,就會發現對三高都有一定的要求,比如下面一家公司的要求就對高併發、高負載和高可用性系統設計要有開發經驗。

現實是我們大部分的公司都很少會遇到三高的場景,即很少有這方面的設計開發經驗,不過我們可以提前學習三高的方案,儘量把這些方案用在工作上,即使在工作中用不到,那麼在面試中也會有好處或者對以後的工作也會起到一定的幫助。這篇文章我就來和大家聊一聊,我在工作中用到的數據庫高可用方案,以及採用這些方案所遇到的問題,希望這些心得可以幫助到你。

一、 高可用背景

高可用概念

高可用(High Availability)是系統所能提供無故障服務的一種能力。簡單地說就是避免因服務器宕機而造成的服務不可用。

通常來說,系統至少要達到 4 個 9(99.99%),也就是每年宕機時間不超過 52.56 分鐘,否則用戶體驗會非常差,感覺系統不穩定。

99.99% = 1 - 52.56 / (3652460)

不過

4 個 9 宕機 52 分鐘對於生產環境的影響還是比較大,但是 5 個 9 對大部分系統來說要求又太高。所以一些雲服務商會提出一個

99.995% 的可用性概念,那麼系統一年的不可用時長爲:不可用時長 = (1 - 99.995%)36524*60 = 26.28

(分鐘),即一年最多的影響服務的時間爲 26.28 分鐘。

簡單瞭解“高可用”有多麼重要之後,接下來我們就來看一下,怎麼設計數據庫高可用架構。系統要達到高可用,一定要做好軟硬件的冗餘,消除單點故障(SPOF single point of failure)。冗餘是高可用的基礎,通常認爲,系統投入硬件資源越多,冗餘也就越多,系統可用性也就越高。除了做好冗餘,系統還要做好故障轉移(Failover)的處理。也就是在最短的時間內發現故障,然後把業務切換到冗餘的資源上。在介紹高可用架構之前,我們先了解一下數據庫複製的原理

二、 數據庫複製原理

數據庫複製本質上就是數據同步。MySQL 數據庫是基於二進制日誌(binary log)進行數據增量同步,而二進制日誌記錄了所有對於 MySQL 數據庫的修改操作。

在默認 ROW 格式二進制日誌中,一條 SQL 操作影響的記錄會被全部記錄下來,比如一條 SQL語句更新了三行記錄,在二進制日誌中會記錄被修改的這三條記錄的前項(before image)和後項(after image)。

在有二進制日誌的基礎上,MySQL 數據庫就可以通過數據複製技術實現數據同步了。而數據複製的本質就是把一臺 MySQL 數據庫上的變更同步到另一臺 MySQL 數據庫上,下面這張圖顯示了當前 MySQL 數據庫的複製架構:

可以看到,在 MySQL 複製中,一臺是數據庫的角色是 Master(也叫 Primary),剩下的服務器角色是 Slave(也叫 Standby):

a. Master 服務器會把數據變更產生的二進制日誌通過 Dump 線程發送給 Slave 服務器;

b. Slave 服務器中的 I/O 線程負責接受二進制日誌,並保存爲中繼日誌;

c. SQL/Worker 線程負責並行執行中繼日誌,即在 Slave 服務器上回放 Master 產生的日誌。

得益於二進制日誌,MySQL 的複製相比其他數據庫,如 Oracle、PostgreSQL 等,非常靈活,用戶可以根據自己的需要構建所需要的複製拓撲結構,比如:

在上圖中,Slave1、Slave2、Slave3

都是 Master 的從服務器,而 Slave11 是 Slave1 的從服務器,Slave1 服務器既是 Master 的從機,又是

Slave11 的主機,所以 Slave1 是個級聯的從機。同理,Slave3 也是臺級聯的從機。

MySQL複製類型及應用選項

MySQL 複製可以分爲以下幾種類型:

默認的複製是異步複製,而很多新同學因爲不瞭解 MySQL 除了異步複製還有其他複製的類型,所以錯誤地在業務中使用了異步複製。爲了解決這個問題,我們一起詳細瞭解一下每種複製類型,以及它們在業務中的選型,方便你在業務做正確的選型。

異步複製

在異步複製(async

replication)中,Master 不用關心 Slave 是否接收到二進制日誌,所以 Master 與 Slave

沒有任何的依賴關係。你可以認爲 Master 和 Slave 是分別獨自工作的兩臺服務器,數據最終會通過二進制日誌達到一致。

異步複製的性能最好,因爲它對數據庫本身幾乎沒有任何開銷,除非主從延遲非常大,Dump Thread 需要讀取大量二進制日誌文件。

如果業務對於數據一致性要求不高,當發生故障時,能容忍數據的丟失,甚至大量的丟失,推薦用異步複製,這樣性能最好(比如像微博這樣的業務,雖然它對性能的要求極高,但對於數據丟失,通常可以容忍)。但往往核心業務系統最關心的就是數據安全,比如監控業務、告警系統。

半同步複製

半同步複製要求 Master 事務提交過程中,至少有 N 個 Slave 接收到二進制日誌,這樣就能保證當 Master 發生宕機,至少有 N 臺 Slave 服務器中的數據是完整的。

半同步複製並不是 MySQL 內置的功能,而是要安裝半同步插件,並啓用半同步複製功能,設置 N 個 Slave 接受二進制日誌成功,比如:

plugin-load="rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"

rpl-semi-sync-master-enabled = 1

rpl-semi-sync-slave-enabled = 1

rpl_semi_sync_master_wait_no_slave = 1

上面的配置中:

第 1 行要求數據庫啓動時安裝半同步插件;

第 2、3 行表示分別啓用半同步 Master 和半同步 Slave 插件;

第 4 行表示半同步複製過程中,提交的事務必須至少有一個 Slave 接收到二進制日誌。

在半同步複製中,有損半同步複製是 MySQL 5.7 版本前的半同步複製機制,這種半同步複製在Master 發生宕機時,Slave 會丟失最後一批提交的數據,若這時 Slave 提升(Failover)爲Master,可能會發生已經提交的事情不見了,發生了回滾的情況。

有損半同步複製原理如下圖所示:

可以看到,有損半同步是在

Master 事務提交後,即步驟 4 後,等待 Slave 返回 ACK,表示至少有 Slave

接收到了二進制日誌,如果這時二進制日誌還未發送到 Slave,Master 就發生宕機,則此時 Slave 就會丟失 Master

已經提交的數據。

而 MySQL 5.7 的無損半同步複製解決了這個問題,其原理如下圖所示:

從上圖可以看到,無損半同步複製 WAIT ACK 發生在事務提交之前,這樣即便 Slave 沒有收到二進制日誌,但是 Master 宕機了,由於最後一個事務還沒有提交,所以本身這個數據對外也不可見,不存在丟失的問題。

所以,對於任何有數據一致性要求的業務,如電商的核心訂單業務、銀行、保險、證券等與資金密切相關的業務,務必使用無損半同步複製。這樣數據纔是安全的、有保障的、即使發生宕機,從機也有一份完整的數據。

多源複製

無論是異步複製還是半同步複製,都是 1 個 Master 對應 N 個 Slave。其實 MySQL 也支持 N 個 Master 對應 1 個 Slave,這種架構就稱之爲多源複製。

多源複製允許在不同 MySQL 實例上的數據同步到 1 臺 MySQL 實例上,方便在 1 臺 Slave 服務器上進行一些統計查詢,如常見的 OLAP 業務查詢。

多源複製的架構如下所示:

上圖顯示了訂單庫、庫存庫、供應商庫,通過多源複製同步到了一臺 MySQL 實例上,接着就可以通過 MySQL 8.0 提供的複雜 SQL 能力,對業務進行深度的數據分析和挖掘。

延遲複製

前面介紹的複製架構,Slave 在接收二進制日誌後會儘可能快地回放日誌,這樣是爲了避免主從之間出現延遲。而延遲複製卻允許Slave 延遲迴放接收到的二進制日誌,爲了避免主服務器上的誤操作,馬上又同步到了從服務器,導致數據完全丟失。

我們可以通過以下命令設置延遲複製:

CHANGE MASTER TO master_delay = 3600

這樣就人爲設置了 Slave 落後 Master 服務器1個小時。

延遲複製主要用於誤操作防範,也在數據庫的備份架構設計中非常常見,比如可以設置一個延遲一天的延遲備機,這樣本質上說,用戶可以有 1 份 24 小時前的快照。

那麼當線上發生誤操作,如 DROP TABLE、DROP DATABASE 這樣災難性的命令時,用戶有一個 24 小時前的快照,數據可以快速恢復

對金融行業來說,延遲複製是你備份設計中,必須考慮的一個架構部分。

在我們瞭解了數據同步的原理後,接下來我們進入常見的數據庫高可用架構方案。

三、 常見的架構方案

1. 常見的架構方案

方案一:主備架構,只有主庫提供讀寫服務,備庫冗餘作故障轉移用

1、高可用分析:高可用,主庫掛了,keepalive(只是一種工具)會自動切換到備庫。這個過程對業務層是透明的,無需修改代碼或配置。

2、高性能分析:讀寫都操作主庫,很容易產生瓶頸。大部分互聯網應用讀多寫少,讀會先成爲瓶頸,進而影響寫性能。另外,備庫只是單純的備份,資源利用率50%,這點方案二可解決。

3、一致性分析:讀寫都操作主庫,不存在數據一致性問題。

4、擴展性分析:無法通過加從庫來擴展讀性能,進而提高整體性能。

5、可落地分析:兩點影響落地使用。第一,性能一般,這點可以通過建立高效的索引和引入緩存來增加讀性能,進而提高性能。這也是通用的方案。第二,擴展性差,這點可以通過分庫分表來擴展。

方案二:雙主架構,兩個主庫同時提供服務,負載均衡

1、高可用分析:高可用,一個主庫掛了,不影響另一臺主庫提供服務。這個過程對業務層是透明的,無需修改代碼或配置。

2、高性能分析:讀寫性能相比於方案一都得到提升,提升一倍。

3、一致性分析:存在數據一致性問題。請看,一致性解決方案

4、擴展性分析:當然可以擴展成三主循環,但筆者不建議(會多一層數據同步,這樣同步的時間會更長)。如果非得在數據庫架構層面擴展的話,擴展爲方案四。

5、可落地分析:兩點影響落地使用。第一,數據一致性問題,一致性解決方案可解決問題第二,主鍵衝突問題,ID統一地由分佈式ID生成服務來生成可解決問題。

方案三:主從架構,一主多從,讀寫分離

1、高可用分析:主庫單點,從庫高可用。一旦主庫掛了,寫服務也就無法提供。

2、高性能分析:大部分互聯網應用讀多寫少,讀會先成爲瓶頸,進而影響整體性能。讀的性能提高了,整體性能也提高了。另外,主庫可以不用索引,線上從庫和線下從庫也可以建立不同的索引(線上從庫如果有多個還是要建立相同的索引,不然得不償失;線下從庫是平時開發人員排查線上問題時查的庫,可以建更多的索引)。

3、一致性分析:存在數據一致性問題。請看,一致性解決方案

4、擴展性分析:可以通過加從庫來擴展讀性能,進而提高整體性能。(帶來的問題是,從庫越多需要從主庫拉取binlog日誌的端就越多,進而影響主庫的性能,並且數據同步完成的時間也會更長)

5、可落地分析:兩點影響落地使用。第一,數據一致性問題,一致性解決方案可解決問題第二,主庫單點問題,筆者暫時沒想到很好的解決方案。

注:思考一個問題,一臺從庫掛了會怎樣?讀寫分離之讀的負載均衡策略怎麼容錯?

方案四:雙主+主從架構,看似完美的方案

1、高可用分析:高可用。

2、高性能分析:高性能。

3、一致性分析:存在數據一致性問題。請看,一致性解決方案

4、擴展性分析:可以通過加從庫來擴展讀性能,進而提高整體性能。(帶來的問題同方案二

5、可落地分析:同方案二,但數據同步又多了一層,數據延遲更嚴重

2. 一致性問題解決方案

第一類:主庫和從庫一致性解決方案

注:圖中圈出的是數據同步的地方,數據同步(從庫從主庫拉取binlog日誌,再執行一遍)是需要時間的,這個同步時間內主庫和從庫的數據會存在不一致的情況。如果同步過程中有讀請求,那麼讀到的就是從庫中的老數據。如下圖。

既然知道了數據不一致性產生的原因,有下面幾個解決方案供參考:

1、直接忽略,如果業務允許延時存在,那麼就不去管它。

2、強制讀主,採用主備架構方案,讀寫都走主庫。用緩存來擴展數據庫讀性能 。

有一點需要知道:如果緩存掛了,可能會產生雪崩現象,不過一般分佈式緩存都是高可用的。

3、選擇讀主,寫操作時根據庫+表+業務特徵生成一個key放到Cache裏並設置超時時間(大於等於主從數據同步時間)。讀請求時,同樣的方式生成key先去查Cache,再判斷是否命中。若命中,則讀主庫,否則讀從庫。代價是多了一次緩存讀寫,基本可以忽略。

4、半同步複製,等主從同步完成,寫請求才返回。就是大家常說的“半同步複製”semi-sync。這可以利用數據庫原生功能,實現比較簡單。代價是寫請求時延增長,吞吐量降低。

5、數據庫中間件,引入開源(mycat等)或自研的數據庫中間層。個人理解,思路同選擇讀主。數據庫中間件的成本比較高,並且還多引入了一層。

第二類:DB和緩存一致性解決方案

先來看一下常用的緩存使用方式:

第一步:淘汰緩存;

第二步:寫入數據庫;

第三步:讀取緩存?返回:讀取數據庫;

第四步:讀取數據庫後寫入緩存。

注:如果按照這種方式,圖一,不會產生DB和緩存不一致問題;圖二,會產生DB和緩存不一致問題,即r2.read先於w3.sync執行。如果不做處理,緩存裏的數據可能一直是髒數據。解決方式如下:

3. 總結

1、架構演變

1、架構演變一:方案一 -> 方案一+分庫分表 -> 方案二+分庫分表 -> 方案四+分庫分表;

2、架構演變二:方案一 -> 方案一+分庫分表 -> 方案三+分庫分表 -> 方案四+分庫分表;

3、架構演變三:方案一 -> 方案二 -> 方案四 -> 方案四+分庫分表;

4、架構演變四:方案一 -> 方案三 -> 方案四 -> 方案四+分庫分表;

2、架構實踐

1、加緩存和索引是通用的提升數據庫性能的方式;

2、分庫分錶帶來的好處是巨大的,但同樣也會帶來一些問題。

3、不管是主備+分庫分表還是主從+讀寫分離+分庫分表,都要考慮具體的業務場景。58到家發展四年,絕大部分的數據庫架構還是採用方案一和方案一+分庫分表,只有極少部分用方案三+讀寫分離+分庫分表。另外,阿里雲提供的數據庫雲服務也都是主備方案,要想主從+讀寫分離需要二次架構。

4、記住一句話:不考慮業務場景的架構都是耍流氓。

四、 容災方案

高可用用於處理各種宕機問題,而宕機可以分成服務器宕機、機房級宕機,甚至是一個城市發生宕機。

機房級宕機:機房光纖不通/被挖斷,機房整體掉電(雙路備用電源也不可用);

城市級宕機:一般指整個城市的進出口網絡,骨幹交換機發生的故障(這種情況發生的概率很小)。

如果綜合考慮的話,高可用就成了一種容災處理機制,對應的高可用架構的評判標準就上升了。

機房內容災:機房內某臺數據庫服務器不可用,切換到同機房的數據庫實例,保障業務連續性;

同城容災:機房不可用,切換到同城機房的數據庫實例,保障業務連續性;

跨城容災:單個城市機房都不可用,切換到跨城機房的數據庫實例,保障業務連續性。

前面我們談到的高可用設計,都只是機房內的容災。也就是說,我們的主服務器和從服務器都在一個機房內,現在我們來看一下同城和跨城的容災設計(我提醒一下,不論是機房內容災、同城容災,還是跨城容災,都是基於 MySQL 的無損半同步複製,只是物理部署方式不同,解決不同的問題)。

對於同城容災,我看到很多這樣的設計:

這種設計沒有考慮到機房網絡的抖動。如果機房 1 和機房 2 之間的網絡發生抖動,那麼因爲事務提交需要機房 2 中的從服務器接收日誌,所以會出現事務提交被 hang 住的問題。

而機房網絡抖動非常常見,所以核心業務同城容災務要採用三園區的架構,如下圖所示:

該架構稱爲“三園區的架構”,如果三個機房都在一個城市,則稱爲“ 一地三中心”,如果在相鄰兩個城市,那麼就叫“兩地三中心”。但這種同城/近城容災,要求機房網絡之間的延遲不超過 5ms。

在三園區架構中,一份數據被存放在了 3 個機房,機房之間根據半同步複製。這裏將 MySQL 的半同步複製參數

rpl_semi_sync_master_wait_for_slave_count 設置爲 1,表示只要有 1 個半同步備機接收到日誌,主服務器上的事務就可以提交。

這樣的設計,保證除主機房外,數據在其他機房至少一份完整的數據。

另外,即便機房 1 與機房 2 發生網絡抖動,因爲機房 1 與機房 3 之間的網絡很好,不會影響事務在主服務器上的提交。如果機房 1 的出口交換機或光纖發生故障,那麼這時高可用套件會 FAILOVER 到機房 2 或機房 3,因爲至少有一份數據是完整的。

機房 2、機房 3 的數據用於保障數據一致性,但是如果要實現讀寫分離,或備份,還需要引入異步複製的備機節點。所以整體架構調整爲:

從圖中可以看到,我們加入兩個異步複製的節點,用於業務實現讀寫分離,另外再從機房 3 的備機中,引入一個異步複製的延遲備機,用於做數據誤刪除操作的恢復。

當設計成類似上述的架構時,你才能認爲自己的同城容災架構是合格的!

另一個重要的點:因爲機房 1 中的主服務器要向四個從服務器發送日誌,這時網卡有成爲瓶頸的可能,所以請務必配置萬兆網卡。

在明白三園區架構後,要實現跨城容災也就非常簡單了, 只要把三個機房放在不同城市就行。但這樣的設計,當主服務器發生宕機時,數據庫就會切到跨城,而跨城之間的網絡延遲超過了25 ms。所以,跨城容災一般設計成“三地五中心”的架構,如下圖所示:

由於有五個機房,所以 ACK 設置爲 2,保證至少一份數據在兩個機房有數據。這樣當發生城市級故障,則城市 2 或城市 3 中,至少有一份完整的數據。

在真實的互聯網業務場景中,“三地五中心”應用並不像“三園區”那樣普遍。這是因爲 25ms的延遲對業務的影響非常大,一般這種架構應用於讀多寫少的場景,比如用戶中心。

另外,真實的互聯網業務場景中,實現跨城容災,一般基於同城容災架構,然後再由業務層來保障跨城的數據一致性。

五、 兜底策略:數據覈對

到目前爲止,我們的高可用是基於 MySQL 的複製技術。但你有沒有想過這樣幾個問題:

萬一數據庫的複製有 Bug 呢?導致最終的數據在邏輯上不一致呢?主從的數據一定一致嗎?你如何判斷一定一致呢?

所以,除了高可用的容災架構設計,我們還要做一層兜底服務,用於判斷數據的一致性。這裏要引入數據覈對,用來解決以下兩方面的問題。

數據在業務邏輯上一致:這個保障業務是對的;

主從服務器之間的數據一致:這個保障從服務器的數據是安全的、可切的。

業務邏輯覈對由業務的同學負責編寫, 從整個業務邏輯調度看賬平不平。例如“今天庫存的消耗”是否等於“訂單明細表中的總和”,“在途快遞” + “已收快遞”是否等於“已下快遞總和”。總之,這是個業務邏輯,用於對賬。

主從服務器之間的核對,是由數據庫團隊負責的。需要額外寫一個主從覈對服務,用於保障主從數據的一致性。這個覈對不依賴複製本身,也是一種邏輯覈對。思路是:將最近一段時間內主服務器上變更過的記錄與從服務器覈對,從邏輯上驗證是否一致。其實現如圖所示:

那麼現在的難題是:如何判斷最近一段時間內主服務器上變更過的記錄?這裏有兩種思路:

  1. 表結構設計規範中,有講過每張表有一個 last_modify_date,用於記錄每條記錄的最後修改時間,按照這個條件過濾就能查出最近更新的記錄,然後每條記錄比較即可。

  2. 覈對服務掃描最近的二進制日誌,篩選出最近更新過記錄的表和主鍵,然後覈對數據。這種的實現難度會更大一些,但是不要求在數據庫上進行查詢。

如果在覈對過程中,記錄又在主上發生了變化,但是還沒有同步到從機,我們可以加入複覈邏輯,按理來說多複覈幾次,主從數據應該就一致了。如果複覈多次不一致,那麼大概率,主從數據就已經是不一致的了。

覈對服務的邏輯比較簡單,但是要實現線上業務的數據覈對,開發上還是有一些挑戰,但這不就是我們 DBA 的價值所在嗎?

總結

小編總結了數據庫高可用的架構設計,內容非常乾貨,建議你反覆閱讀,其中涉及的內容在原理上並不複雜,但在實現細節上需要不斷打磨,歡迎你在後續的架構設計過程中與我交流,總結來說:

1. 核心業務複製務必爲無損半同步複製;

2. 同城容災使用三園區架構,一地三中心,或者兩地三中心,機房見網絡延遲不超過5ms;

3. 跨城容災使用"三地五中心",跨城機房距離超過200KM,延遲超過25ms;

4. 跨城容災架構由於網絡耗時高,因此一般僅用於讀多寫少的業務,例如用戶中心;

5. 除了複製進行數據同步外,還需要額外的核對程序進行邏輯覈對;

6. 數據庫層的邏輯覈對,可以使用last_modify_date字段,取出最近修改的記錄。

最後,小編提出一個思考題:對於跨城容災,有什麼優化技術可以減少耗時增大帶來的性能影響呢?歡迎大家留言與小編交流,一同探討和學習。

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