數(shù)據(jù)庫(kù)讀寫分離是很多公司繞不過(guò)去的一個(gè)發(fā)展階段,從單體數(shù)據(jù)庫(kù),到主備模式,再到讀寫分離,分庫(kù)分表。每一個(gè)階段都能為我們解決一些問(wèn)題,但也帶來(lái)了新的挑戰(zhàn)。本篇文章我們就主要研究一下數(shù)據(jù)庫(kù)讀寫分離及其帶來(lái)的問(wèn)題如何解決。
1 數(shù)據(jù)庫(kù)架構(gòu)的發(fā)展歷程
首先簡(jiǎn)單介紹一下數(shù)據(jù)庫(kù)架構(gòu)的發(fā)展歷程,基本就是單體、主備、讀寫分離、分庫(kù)分表,下面我們分別進(jìn)行介紹。
1.1 單體架構(gòu)

業(yè)務(wù)發(fā)展初期,數(shù)據(jù)庫(kù)的壓力相對(duì)較小,這時(shí)候使用單獨(dú)一個(gè)庫(kù)就可以。
引出的問(wèn)題:如果數(shù)據(jù)庫(kù)出現(xiàn)故障,我們的業(yè)務(wù)就不能使用,只能說(shuō)是停機(jī)重啟修復(fù)故障。
1.2 主備架構(gòu)
由于單體帶出的問(wèn)題,這時(shí)候我們就需要加一個(gè)備用庫(kù),緊急情況可以用備庫(kù)頂上,相當(dāng)于加一個(gè)替補(bǔ)隊(duì)員。

通過(guò)MySQL自帶的主從同步機(jī)制,就可以放我們的替補(bǔ)隊(duì)員上線。
當(dāng)正式隊(duì)員(主庫(kù))發(fā)生故障,我們就可以人工讓其下線,讓替補(bǔ)隊(duì)員(備庫(kù))頂上。
引出的問(wèn)題:隨著業(yè)務(wù)大規(guī)模爆發(fā),主庫(kù)的壓力過(guò)大,我們就想讓備庫(kù)承擔(dān)起更大的責(zé)任來(lái)。
1.3 讀寫分離架構(gòu)
讀寫分離架構(gòu)本質(zhì)也就是主備架構(gòu),與主備架構(gòu)沒(méi)有本質(zhì)區(qū)別,就是在主備架構(gòu)的基礎(chǔ)上,增加一層對(duì)讀寫請(qǐng)求的處理,使其能夠更大程度上利用備用庫(kù)為我們分擔(dān)一些讀的壓力。

讀寫分離架構(gòu),需要在中間加一層控制讀寫請(qǐng)求的路由
1.4 分庫(kù)分表
分庫(kù)分表的本質(zhì)上是切分?jǐn)?shù)據(jù),是由于數(shù)據(jù)量級(jí)的提升,不對(duì)數(shù)據(jù)切分會(huì)嚴(yán)重影響數(shù)據(jù)庫(kù)讀寫性能。
甚至是如果不切分,磁盤、內(nèi)存、CPU無(wú)法承載這樣的壓力,數(shù)據(jù)庫(kù)隨時(shí)在奔潰的邊緣。

分庫(kù)分表與前三者是有本質(zhì)區(qū)別的,分庫(kù)分表后每一個(gè)庫(kù)分片都可以采取以上三種方式的任意一種,可以是單體分片,也可以是主備分片,也可以是做了讀寫分離的分片。
分庫(kù)分表和前三者中的一種是共生的關(guān)系。
不知道如何進(jìn)行分庫(kù)分表設(shè)計(jì)的可以讀我之前的這篇文章《收好這份武林秘籍,讓你分庫(kù)分表再無(wú)煩惱》
2 讀寫分離設(shè)計(jì)方案
主從復(fù)制是MySQL數(shù)據(jù)庫(kù)自帶的功能,但是想要做讀寫分離就需要我們自己做一些工作配合MySQL主從同步配合使用??蛇x擇的方案有很多。
2.1 代理
在應(yīng)用程序和數(shù)據(jù)庫(kù)之間增加代理層,代理層接收應(yīng)用程序?qū)?shù)據(jù)庫(kù)的請(qǐng)求,根據(jù)不同請(qǐng)求類型轉(zhuǎn)發(fā)到不同的實(shí)例,實(shí)現(xiàn)讀寫分離的同時(shí)還可以實(shí)現(xiàn)負(fù)載均衡(讀請(qǐng)求按照負(fù)載均衡的規(guī)則傳入各個(gè)從節(jié)點(diǎn))。
代理也就是借助中間件的方式,控制不同類型請(qǐng)求,進(jìn)入不同的數(shù)據(jù)庫(kù)。
目前常用的mysql的讀寫分離中間件有:
- MySQL-Proxy MySQL自己的一個(gè)開源項(xiàng)目,通過(guò)其自帶的Lua腳本進(jìn)行SQL判斷
- Atlas Qihoo 360,在mysql-proxy 0.8.2版本的基礎(chǔ)上,對(duì)其進(jìn)行了優(yōu)化,增加了一些新的功能特性。
- MyCat
- MaxScale MariaDB 開發(fā)
- Amoeba 阿里開發(fā)
- ...
2.2 應(yīng)用內(nèi)路由
在程序中進(jìn)行控制,我們利用持久層框架的攔截器實(shí)現(xiàn),動(dòng)態(tài)路由不同數(shù)據(jù)源。
利用Sharding-JDBC也可以實(shí)現(xiàn)
實(shí)現(xiàn)思路:
- 配置多數(shù)據(jù)源
- 設(shè)置默認(rèn)的數(shù)據(jù)源,配置數(shù)據(jù)源的切換策略
- 攔截進(jìn)入數(shù)據(jù)庫(kù)的請(qǐng)求,根據(jù)業(yè)務(wù)需求設(shè)置走哪個(gè)數(shù)據(jù)源。
3 讀寫分離造成的讀延遲怎么辦?
凡是采用讀寫分離架構(gòu),就會(huì)有同步延遲問(wèn)題,我們只能想辦法去克服這個(gè)問(wèn)題。
3.1 數(shù)據(jù)同步寫入從庫(kù)
主從復(fù)制模式,一般都是異步寫數(shù)據(jù)到從庫(kù),當(dāng)然這個(gè)異步也可以設(shè)置為同步,只有當(dāng)從庫(kù)寫完成,主庫(kù)上的寫請(qǐng)求才能返回。
這種方案是最佳單也是最有效的一種,但也是性能最差的一種,尤其是有大量從庫(kù)的情況下,嚴(yán)重影響請(qǐng)求效率。
3.2 緩存(中間件)路由法
寫請(qǐng)求時(shí)緩存記錄一個(gè)key,這個(gè)key的失效時(shí)間設(shè)置為主從同步的延時(shí),讀請(qǐng)求的時(shí)候先去緩存中確認(rèn)是否存在key,如果key存在說(shuō)明發(fā)生了寫請(qǐng)求,數(shù)據(jù)未同步到從庫(kù),這時(shí)走主庫(kù)即可,若不存在這個(gè)key,直接走從庫(kù)的查詢即可。
中間件應(yīng)該也是可以判斷是否同步完成,與使用緩存記錄類似。
這種方案最大的弊端是引入了緩存,系統(tǒng)復(fù)雜度上升。
3.3 選擇性強(qiáng)制讀主庫(kù)
對(duì)于一些特殊的業(yè)務(wù)場(chǎng)景,采用強(qiáng)制讀主庫(kù)。
弊端,需要把每一個(gè)這種情況都找出來(lái),設(shè)置成強(qiáng)制走主庫(kù)。
3.4 等GTID 方案
MySQL 在執(zhí)行完事務(wù)后,會(huì)將該事務(wù)的 GTID 會(huì)給客戶端,然后客戶端可以使用該命令去要執(zhí)行讀操作的從庫(kù)中執(zhí)行,等待該 GTID,等待成功后,再執(zhí)行讀操作;如果等待超時(shí),則去主庫(kù)執(zhí)行讀操作,或者再換一個(gè)從庫(kù)執(zhí)行上述流程。
MariaDB 的 MaxScale 就是使用該方案,MaxScale 是 MariaDB 開發(fā)的一個(gè)數(shù)據(jù)庫(kù)智能代理服務(wù)(也支持 MySQL),允許根據(jù)數(shù)據(jù)庫(kù) SQL 語(yǔ)句將請(qǐng)求轉(zhuǎn)向目標(biāo)一個(gè)到多個(gè)服務(wù)器,可設(shè)定各種復(fù)雜程度的轉(zhuǎn)向規(guī)則。
3.5 以不變應(yīng)萬(wàn)變
有延遲就有延遲,對(duì)數(shù)據(jù)強(qiáng)一致性要求不高的場(chǎng)景可以放任不管。
點(diǎn)擊此處 即可免費(fèi)領(lǐng)取更多面試資料