MySQL主從復(fù)制引發(fā)的這個問題,99%的人都不知道
一、背景
電商業(yè)務(wù)場景,隨著平臺訂單規(guī)模的日益增長,訂單現(xiàn)有的存儲已經(jīng)沒辦法支撐后面業(yè)務(wù)的發(fā)展。在得物五彩石項目的時候就對訂單進(jìn)行了分庫分表的拆分,為了解決分庫分表后賣家維度的查詢問題,單獨創(chuàng)建了一個賣家維度的訂單庫。
目前訂單分為買家和賣家兩個庫,賣家?guī)斓臄?shù)據(jù)是通過監(jiān)聽買家?guī)靊inlog異構(gòu)出來的一個庫。現(xiàn)在訂單主要有兩張表,分別是訂單的主表和子表。
在異構(gòu)的邏輯中,我們會對這兩張表的binlog消息進(jìn)行處理,異構(gòu)成我們的賣家訂單表。在監(jiān)聽到插入的消息時,只會處理子表的插入消息,其余需要補(bǔ)充的主訂單表數(shù)據(jù)直接查詢主表。
查詢訂單主表的時候如果為空就會拋一個異常,依賴MQ的重試功能進(jìn)行下一次重試,這是目前的邏輯。正常情況下訂單主表是不可能會出現(xiàn)查不到的情況,在19號凌晨的時候,有一大批訂單主表查不到的告警,于是通過關(guān)鍵字去日志平臺搜索,如下圖所示:
二、分析
2.1 業(yè)務(wù)影響
通過對報錯的消息進(jìn)行排查,消息在二次重新投遞的時候,數(shù)據(jù)成功的保存到了數(shù)據(jù)庫里面,對業(yè)務(wù)無影響,只是會有異常告警。
2.2 主從延遲
目前的現(xiàn)象是第一次處理的時候,查詢主表為空,異常重試后就能查詢到數(shù)據(jù)。給我們的直覺就是數(shù)據(jù)庫的主從延遲產(chǎn)生的問題。
根據(jù)這個想法,去排查對應(yīng)的代碼,發(fā)現(xiàn)從代碼層面是走的主庫查詢。這里順帶介紹下主從路由是如何實現(xiàn)的,項目中依賴了數(shù)據(jù)庫代理中間件彩虹橋的jar包,支持通過配置(bifrost.read-write-separate-model=SQL_READ_DEFAULT_MASTER)指定默認(rèn)的讀寫分離模式。我們的模式默認(rèn)是路由到主庫,如果有需要走從庫查詢的場景會在對應(yīng)的dao方法上加一個注解進(jìn)行標(biāo)記。
然后會通過Mybatis的攔截器,對SQL進(jìn)行處理,通過hint的方式將路由方式帶給彩虹橋,彩虹橋內(nèi)部根據(jù)指定的方式進(jìn)行路由。
2.3 創(chuàng)單數(shù)據(jù)一致性
主從延遲的排除后,懷疑點在另一個方面。會不會創(chuàng)單的時候數(shù)據(jù)保存沒有在同一個事務(wù)里面,比如說子單先保存,然后再保存主單,此時子單的binlog肯定會早于主單,也就會存在查詢主單為空的情況。
這個也很快被否決了,如果是這個情況,那肯定是100%必現(xiàn)的場景,不是存在偶現(xiàn)的情況。其次,創(chuàng)單的代碼中對于數(shù)據(jù)存儲那塊是在一個事務(wù)內(nèi),所以不存在分批保存的情況。
2.4 數(shù)據(jù)庫內(nèi)部問題
排除了外部相關(guān)的問題,那么只剩下數(shù)據(jù)庫內(nèi)部的問題。接下來從binlog的寫入流程來看下是否存在有問題的地方,首先我們看下整體的流程,如下圖:
這里比較懷疑的點就在于redo log 二階段提交時,這個時候會把redo日志刷數(shù)據(jù)盤,也就是MySQL的存儲數(shù)據(jù)真正落庫。正常情況下,這個速度很快,當(dāng)數(shù)據(jù)庫的IO很高的時候,刷盤的性能也會有所影響。所以在刷盤時稍微延遲了X毫秒的話,binlog已經(jīng)被應(yīng)用給消費了,然后查詢不到。
根據(jù)告警的時間點,去看了從節(jié)點數(shù)據(jù)庫的監(jiān)控,那段時間確實IO較高,如下圖所示:
上面只是猜測,我們?nèi)绾稳ヲ炞C這個猜測是對的呢?
目前用的數(shù)據(jù)庫是主從模式,那么必然涉及到數(shù)據(jù)的復(fù)制,先簡單介紹下復(fù)制的幾種模式:
- 強(qiáng)同步
應(yīng)用發(fā)起數(shù)據(jù)插入/更新/刪除操作在主實例執(zhí)行完成后,會將日志同步傳輸?shù)剿袀鋵嵗?,至?個備實例收到并存儲日志后,事務(wù)才完成提交。
- 半同步
應(yīng)用發(fā)起數(shù)據(jù)插入/更新/刪除操作在主實例執(zhí)行完成后,會將日志同步傳輸?shù)?個備實例,備實例收到日志,事務(wù)就算完成了提交,不需要等待備實例執(zhí)行日志內(nèi)容。
- 異步
應(yīng)用發(fā)起數(shù)據(jù)插入/更新/刪除請求,主實例完成操作后會立即響應(yīng)應(yīng)用,同時主實例向備實例異步復(fù)制數(shù)據(jù)。
目前我們數(shù)據(jù)庫用的是半同步的方式,在半同步里面支持兩種模式,分別是AFTER_COMMIT(5.6版本默認(rèn))和AFTER_SYNC(5.7版本才有,默認(rèn))。
1)AFTER_COMMIT
master將每個事務(wù)寫入binlog,傳遞到slave刷新到磁盤,同時master提交事務(wù)。master等待slave反饋收到relay log,只有收到ACK后master才將commit OK結(jié)果反饋給客戶端。
AFTER_COMMIT意味在master上,剛剛提交的事務(wù)對數(shù)據(jù)庫的修改,對其他事務(wù)是可見的。因此,如果在等待Slave ACK的時候crash了,那么會對其他事務(wù)出現(xiàn)幻讀,數(shù)據(jù)丟失的問題。
2)AFTER_SYNC
master將每個事務(wù)寫入binlog , 傳遞到slave刷新到磁盤。master等待slave反饋接收到relay log的ACK之后,再提交事務(wù)并且返回commit OK結(jié)果給客戶端。即使master crash,所有在master上已經(jīng)提交的事務(wù)都能保證已經(jīng)同步到slave的relay log中。
AFTER_SYNC在寫完binlog后,就開始傳輸,但此時還沒有提交事務(wù),意味著當(dāng)前這個事務(wù)對數(shù)據(jù)庫的修改,其他事務(wù)也是不可見的。所以不會出現(xiàn)幻讀,數(shù)據(jù)丟失風(fēng)險。
我們目前是用的AFTER_SYNC模式,也就是說binlog寫入后,master會等待slave的反饋結(jié)果,然后才會commit,這里也就能正常解釋我們這個查詢不到的問題是什么原因了。原因就是master寫完binlog后在等待中,slave收到binlog后,由于IO高,寫入relay log比較慢,此時我們的訂閱平臺也相當(dāng)于是一個從節(jié)點,同樣也收到了binlog,然后投遞給應(yīng)用,應(yīng)用這個時候去數(shù)據(jù)庫查詢,因為master還沒commit,自然就查不到。
三、解決方案
針對這個問題,解決方案有很多,梳理后發(fā)現(xiàn)這個場景其實不影響業(yè)務(wù),將告警信息調(diào)整下,并不需要改造業(yè)務(wù),如果想改也有一些可以解決的方案。
3.1 重試機(jī)制
對于這類場景,可以利用重試機(jī)制來解決。而目前的binlog監(jiān)聽也是利用MQ來投遞消息給業(yè)務(wù)方使用,可以直接依賴MQ的重試即可。
這個業(yè)務(wù)場景本身就是一個異步的過程,對實時性要求沒有那么高,其次對業(yè)務(wù)也不會有影響。第一次消息過來的時候沒有查到,終止流程,然后等到MQ重試,就可以查到數(shù)據(jù),完成整個流程。
3.2 延時消息
可以對訂閱平臺進(jìn)行改造,在配置訂閱任務(wù)的時候可以指定監(jiān)聽到binlog后延遲多久發(fā)送給業(yè)務(wù)應(yīng)用,這個延遲的處理直接用MQ的延時消息,這樣就可以將消息晚幾秒送到業(yè)務(wù)應(yīng)用,也能解決問題。
延時消息也不能保證一定解決,重點在于延時的時間怎么設(shè)置比較合理。因為我們也不能保證數(shù)據(jù)庫從節(jié)點收到binlog后ack的時間有多長。還有就是如果配置的很長,要對現(xiàn)有場景的業(yè)務(wù)進(jìn)行評估,是否能夠接受數(shù)據(jù)的延遲。
3.3 不依賴binlog
不依賴binlog指的是將消息改成業(yè)務(wù)動作觸發(fā)后發(fā)出的消息,比如創(chuàng)建訂單后,在代碼中通過MQ發(fā)出一條訂單創(chuàng)建的消息,里面包含了訂單的數(shù)據(jù)。這樣業(yè)務(wù)應(yīng)用就可以不直接依賴binlog的消息,監(jiān)聽到業(yè)務(wù)消息的時候,事務(wù)必定已經(jīng)提交了,業(yè)務(wù)應(yīng)用進(jìn)行反查的時候數(shù)據(jù)已經(jīng)有了。
四、總結(jié)
數(shù)據(jù)庫可以說是作為研發(fā)同學(xué)必須要掌握的一個技能,但在日常工作中我們只需要掌握一些基本的語法就可以滿足開發(fā)需求,于是會忽略很多底層的原理。通過本文這個案例你會發(fā)現(xiàn)整個數(shù)據(jù)庫體系還有很多內(nèi)容值得去學(xué)習(xí),試想一下,如果平時對主從復(fù)制的原理比較熟的話,問題排查起來也會簡單很多。
其次作為研發(fā),對于每一個問題都要有認(rèn)真的態(tài)度,不能草草了事。從一個小問題,如果采取忽略的方式,就會失去求真的動力,從而失去很多可以學(xué)習(xí)的點。這個問題也是一樣,通過一步步排查,又積累了新的經(jīng)驗。