數(shù)據(jù)庫(kù)讀寫(xiě)分離如何保證主從一致性
讀寫(xiě)分離
當(dāng)我們的數(shù)據(jù)庫(kù)壓力主鍵變大的時(shí)候,我們會(huì)嘗試增加一些從節(jié)點(diǎn)來(lái)分?jǐn)傊鞴?jié)點(diǎn)的查詢(xún)壓力。而一般來(lái)說(shuō),我們是用一主多從的結(jié)構(gòu)來(lái)作為讀寫(xiě)分離的基本結(jié)構(gòu)。
而一般來(lái)說(shuō)我們有兩種常用的方法來(lái)實(shí)現(xiàn)讀且分離架構(gòu):
客戶(hù)端直接分離
這種方式是由客戶(hù)端,或者我們的微服務(wù)直接進(jìn)行數(shù)據(jù)庫(kù)的讀寫(xiě)選擇。將讀庫(kù)選擇路由到主庫(kù)上進(jìn)行,將查詢(xún)路由到從主庫(kù)上進(jìn)行。
這種方式的優(yōu)點(diǎn)在于因?yàn)槭侵边B所以性能比較高,但是需要由業(yè)務(wù)團(tuán)隊(duì)了解數(shù)據(jù)庫(kù)的實(shí)例細(xì)節(jié),當(dāng)數(shù)據(jù)庫(kù)做調(diào)整的時(shí)候就需要業(yè)務(wù)側(cè)同步改造。
使用數(shù)據(jù)中間件代理
這種方式是由一層代理層對(duì)數(shù)據(jù)的讀寫(xiě)做分發(fā),業(yè)務(wù)層將所有的請(qǐng)求都通過(guò)代理來(lái)實(shí)現(xiàn)。
這種方式的優(yōu)點(diǎn)在于對(duì)于業(yè)務(wù)層不需要感知到數(shù)據(jù)庫(kù)的存在,但問(wèn)題在于數(shù)據(jù)中間件的性能要求較高,還需要專(zhuān)人來(lái)進(jìn)行優(yōu)化和維護(hù),整體架構(gòu)較為復(fù)雜。
但是我們發(fā)現(xiàn),盡管這兩種方式各有優(yōu)劣。但核心都是通過(guò)數(shù)據(jù)的寫(xiě)入、查詢(xún)請(qǐng)求的路由而實(shí)現(xiàn)的,那么這就會(huì)引發(fā)標(biāo)題的問(wèn)題:
主備同步存在延遲,所以在延遲時(shí)間內(nèi)對(duì)插入的內(nèi)容進(jìn)行查詢(xún)則無(wú)法查詢(xún)到最新提交的事務(wù)。
那么如何保證主從一致性的問(wèn)題,其實(shí)就變成了如何處理主從延遲的問(wèn)題。
低改造成本
根據(jù)項(xiàng)目的大小,團(tuán)隊(duì)的規(guī)模以及主機(jī)的部署模式。我們處理問(wèn)題的方法也有很多種。
強(qiáng)制讀主庫(kù)
最簡(jiǎn)單強(qiáng)硬的就是強(qiáng)制讀主庫(kù)。
一般情況下我們?cè)诓煌牟樵?xún)中會(huì)有不同程度的一致性要求。我們可以將需要保證數(shù)據(jù)一致性的請(qǐng)求配置強(qiáng)制查詢(xún)主庫(kù),而對(duì)于無(wú)強(qiáng)依賴(lài)的查詢(xún)請(qǐng)求仍然查詢(xún)備庫(kù)。
盡管這個(gè)方案不是很優(yōu)雅,但是是最簡(jiǎn)單實(shí)現(xiàn)的方法,并且在Spring等框架的支持下一般只需要加一個(gè)注解就能實(shí)現(xiàn)。但這個(gè)方法的問(wèn)題也是顯而易見(jiàn)的,如果存在大量的強(qiáng)一致性要求的查詢(xún)語(yǔ)句,則相當(dāng)于沒(méi)有進(jìn)行讀寫(xiě)分離與擴(kuò)展。那么這種方法就會(huì)導(dǎo)致系統(tǒng)在數(shù)據(jù)庫(kù)層面沒(méi)有有效的擴(kuò)展手段了。
等待時(shí)間
由于問(wèn)題產(chǎn)生的來(lái)源是主從延遲,所以在下一次查詢(xún)的時(shí)候進(jìn)行一段時(shí)間的等待以彌補(bǔ)這種延遲即可。
所以在進(jìn)行主庫(kù)的數(shù)據(jù)插入之后,讓數(shù)據(jù)庫(kù)數(shù)據(jù)連接或者對(duì)應(yīng)的執(zhí)行線程等待一段時(shí)間后返回。通過(guò)等待時(shí)間來(lái)消化掉主從備份的延遲時(shí)間。但是這個(gè)方法也有一些問(wèn)題比如:這個(gè)等待時(shí)間一般是固定的,即便主從已經(jīng)無(wú)延遲了也會(huì)繼續(xù)等待到時(shí)間結(jié)束;如果在服務(wù)高峰時(shí)期,有可能數(shù)據(jù)在等待時(shí)間結(jié)束后仍然沒(méi)有完成同步則仍然會(huì)存在一致性問(wèn)題。
但這種方法優(yōu)雅的地方是可以配合業(yè)務(wù)來(lái)進(jìn)行實(shí)現(xiàn),舉例來(lái)說(shuō)當(dāng)用戶(hù)下單之后,通過(guò)下單送卷或者下單抽獎(jiǎng)的方式從前端拖住用戶(hù),從而當(dāng)用戶(hù)在一次連續(xù)操作中再次查詢(xún)自己訂單的時(shí)候中間必然會(huì)間隔一定時(shí)間,也就讓需要再次查詢(xún)數(shù)據(jù)的時(shí)候保證了數(shù)據(jù)的一致性。
一主一備情況
上述兩種方案看起來(lái)可能不那么“技術(shù)”,感覺(jué)有點(diǎn)投機(jī)取巧。那么下面咱們可以分兩種情況來(lái)討論用更高技術(shù)的方法如何實(shí)現(xiàn)一致性。
查詢(xún)延遲方案
對(duì)于主從復(fù)制來(lái)說(shuō),是當(dāng)主庫(kù)完成一個(gè)事務(wù)后,通知給從庫(kù),當(dāng)從庫(kù)接受到后,則主庫(kù)完成返回客戶(hù)端。所以當(dāng)主庫(kù)完成事務(wù)后,僅能確保從庫(kù)已經(jīng)接受到了,但是不能保證從庫(kù)執(zhí)行完成,也就是導(dǎo)致了主從備份延遲。
但是從庫(kù)執(zhí)行數(shù)據(jù)是有進(jìn)度的,而這個(gè)進(jìn)度是可以通過(guò)show slave status語(yǔ)句中的seconds_behind_master來(lái)進(jìn)行描述,這個(gè)參數(shù)描述從庫(kù)落后了主庫(kù)數(shù)據(jù)多少秒,當(dāng)這個(gè)參數(shù)為0時(shí),我們可以認(rèn)為從庫(kù)和主庫(kù)已經(jīng)基本上沒(méi)有延遲了,那么這時(shí)候就可以查詢(xún)請(qǐng)求。
但seconds_behind_master是秒級(jí)的,所以只能大概地判斷,由于精度較低,所以還是可能出現(xiàn)不一致的情況。
如果要求精準(zhǔn)執(zhí)行的話,我們可以比較同步文件的執(zhí)行記錄,具體來(lái)說(shuō)是:
- Master_Log_File和Read_Master_Log_Pos,表示的是讀到的主庫(kù)的最新位點(diǎn);
- Relay_Master_Log_File和Exec_Master_Log_Pos,表示的是備庫(kù)執(zhí)行的最新位點(diǎn)。
所以當(dāng)Relay_Master_Log_File和Exec_Master_Log_Pos和其一致的時(shí)候,就說(shuō)明從庫(kù)的已執(zhí)行數(shù)據(jù)已經(jīng)追上主庫(kù)了,那么這時(shí)就可以說(shuō)保證了主從一致性了
半同步復(fù)制方案
但是比較同步文件的執(zhí)行記錄方法的問(wèn)題在于,如果當(dāng)前的這個(gè)事務(wù)的binlog尚未傳入到從庫(kù),即Master_Log_File和Read_Master_Log_Pos未更新,也就無(wú)法保證從庫(kù)已經(jīng)包含最新的主庫(kù)事務(wù)了。
而為了保證在一主一備的情況下,從庫(kù)里一定接受到數(shù)據(jù)了,也就是Master_Log_File和Read_Master_Log_Pos中的數(shù)據(jù)是和主庫(kù)一致的,我們可以開(kāi)啟semi-sync replication半同步復(fù)制。
半同步復(fù)制的原理是在主庫(kù)提交事務(wù)前先將binlog發(fā)送給從庫(kù),然后當(dāng)從庫(kù)接受后返回一個(gè)應(yīng)答,主庫(kù)只有在接受到這個(gè)應(yīng)答之后才返回事務(wù)執(zhí)行完成。這樣就可以保證從庫(kù)的Master_Log_File和Read_Master_Log_Pos與主庫(kù)是一致的,從而解決了主從一致的問(wèn)題。
一主多備情況
半同步復(fù)制可以解決一主一備的情況,但是當(dāng)一主多備的時(shí)候,只要主庫(kù)接受到一個(gè)從庫(kù)的應(yīng)答,就會(huì)返回事務(wù)執(zhí)行完成。而這時(shí)當(dāng)請(qǐng)求打到未完成同步的從庫(kù)上時(shí)就會(huì)發(fā)生主從延遲。
等主庫(kù)記錄方案
所以針對(duì)一主多備的情況,我們可以將目光集中在執(zhí)行查詢(xún)的從庫(kù)上,即確保我們即將查詢(xún)的備庫(kù)已經(jīng)執(zhí)行了我們預(yù)期的事務(wù)。那么我們的問(wèn)題就變成兩部分:1. 確認(rèn)主庫(kù)事務(wù),2. 查詢(xún)數(shù)據(jù)條件。
確認(rèn)主庫(kù)事務(wù)
當(dāng)我們提交完一個(gè)事務(wù)后,可以通過(guò)執(zhí)行show master status來(lái)得到主庫(kù)中的數(shù)據(jù)事務(wù)文件(File)和位置記錄(Position)。
查詢(xún)數(shù)據(jù)條件
當(dāng)我們要查詢(xún)從庫(kù)數(shù)據(jù)的時(shí)候,我們可以通過(guò)語(yǔ)句select master_pos_wait(File, Position, 1);來(lái)查詢(xún)當(dāng)前是否已經(jīng)執(zhí)行到了該記錄(當(dāng)返回值>=0的時(shí)候說(shuō)明已經(jīng)執(zhí)行過(guò)了)。其中最后的數(shù)字1表示阻塞時(shí)長(zhǎng)。
通過(guò)先確認(rèn)主庫(kù)事務(wù)記錄,再判確認(rèn)備庫(kù)是否已經(jīng)執(zhí)行了了主庫(kù)對(duì)應(yīng)的事務(wù)。
但是可以發(fā)現(xiàn),這種方法要求查詢(xún)的時(shí)候知道主庫(kù)的事務(wù)信息,對(duì)場(chǎng)景有很大的限制。
最后
主從一致的問(wèn)題源自主從延遲,所以我們就是從如何消除延遲來(lái)解決問(wèn)題。簡(jiǎn)單點(diǎn)的方案我們可以不走備庫(kù)、或者直接等待一段時(shí)間來(lái)忽略延遲的影響。在一主一備的情況下我們可以粗力度的用seconds_behind_master來(lái)判斷或者用Relay_Master_Log_File和Exec_Master_Log_Pos來(lái)判斷。而當(dāng)一主多從的情況下我們則需要在查詢(xún)前傳入主庫(kù)執(zhí)行的事務(wù)記錄才能保證數(shù)據(jù)一致性。
可以看出,當(dāng)數(shù)據(jù)規(guī)模和部署方式變更的時(shí)候,好的解決方案將會(huì)越來(lái)越多。我認(rèn)為根據(jù)實(shí)際業(yè)務(wù)情況選擇最合適的方法才是最重要的。



































