《MySQL——如何解决一主多从的读写分离的过期读问题》
目錄
- 兩種架構(gòu)
- 兩種架構(gòu)特點
- 強(qiáng)制走主庫方案
- Sleep方案
- 判斷主備無延遲方案
- 配合semi-sync
- 等主庫位點方案
- GTID方案
兩種架構(gòu)
基于一主多從的讀寫分離,如何處理主備延遲導(dǎo)致的讀寫分離問題。
讀寫分離的主要目標(biāo):分?jǐn)傊鲙靿毫Α?/p>
有兩種架構(gòu):
1、客戶端主動做負(fù)載均衡,把數(shù)據(jù)庫的連接信息放在客戶端的連接層。由客戶端選擇后端數(shù)據(jù)庫進(jìn)行查詢。
2、MySQL和客戶端之間加上一個中間代理層proxy,客戶端只連接proxy,由proxy根據(jù)請求類型和上下文決定請求的分發(fā)路由
兩種架構(gòu)特點
1、客戶端直連方案,少了一層proxy轉(zhuǎn)發(fā),查詢性能較好,結(jié)構(gòu)也比較簡單。
由于要了解后端部署細(xì)節(jié),所以在出現(xiàn)主備切換、庫遷移等操作時,客戶端都會感知到,并且調(diào)整數(shù)據(jù)庫連接信息。客戶端再分配一個負(fù)責(zé)管理后端的組件,讓業(yè)務(wù)端只注重于業(yè)務(wù)邏輯開發(fā)
2、帶proxy的架構(gòu),使客戶端不需要關(guān)注后端細(xì)節(jié)。
連接維護(hù)、后端信息維護(hù)等工作,都是由proxy完成的。但是架構(gòu)整體相對復(fù)雜
兩種架構(gòu)都會遇到"過期讀"問題:
由于主從可能存在延遲,客戶端執(zhí)行完一個更新事務(wù)后馬上發(fā)起查詢,如果查詢選擇的是從庫的話,就有可能讀到的是事務(wù)更新之前的狀態(tài)。
客戶端希望的是查詢從庫的數(shù)據(jù)結(jié)果和查主庫的數(shù)據(jù)結(jié)果是一樣的。
下面是解決方案:
強(qiáng)制走主庫方案; sleep方案; 判斷主備無延遲方案; 配合semi_sync方案; 等主庫位點方案; 等GTID方案;實際應(yīng)用中,這幾個方案可以混合使用。
比如,現(xiàn)在客戶端對請求做分類,區(qū)分哪些請求可以接受過期讀,哪些請求完全不餓能接受過期讀。然后,對于不能接受過期讀的語句,再使用等GTID或等位點的方案,
過期讀本質(zhì)上是由一寫多讀導(dǎo)致的,為了避免過期讀,只有兩種選擇:
1、超時放棄
2、轉(zhuǎn)到主庫查詢
強(qiáng)制走主庫方案
將查詢請求做分類:
1、對于必須要拿到最新結(jié)果的請求,強(qiáng)制將其發(fā)到主庫上。
2、對于可以讀到舊數(shù)據(jù)的請求,才將其發(fā)到從庫上。
這種方案雖然取巧,但是好用。
此方案最大的問題在于:當(dāng)遇到實時性比較高要求的業(yè)務(wù)需求,就要放棄讀寫分離,所有的讀寫壓力都在主庫,等同于放棄了擴(kuò)展性。
Sleep方案
主庫更新后,讀從庫之前先sleep。如執(zhí)行一條select sleep(1)命令。
方案假設(shè):大多數(shù)情況下主備延遲在1s之內(nèi),做一個sleep可以有很大概率拿到到最新的數(shù)據(jù)
該方案不精確:
1、如果這個查詢請求0.1s就能在從庫拿到正確結(jié)果,sleep(1)也會等1s
2、如果延遲超過1s,還是會出現(xiàn)過期讀
判斷主備無延遲方案
要確保備庫無延遲有三種做法:
在show slave status結(jié)果里的seconds_behind_master參數(shù)可以用來衡量主備延遲時間的長短。
第一種方法
每次從庫執(zhí)行查詢請求前,先判斷seconds_behind_master是否等于0,如果不等于0就要等到這個參數(shù)變?yōu)?才執(zhí)行查詢操作。
第二種方法
比對位點確保主備無延遲。
-
Master_Log_File 和 Read_Master_Log_Pos,表示的是讀到的主庫的最新位點;
-
Relay_Master_Log_File 和 Exec_Master_Log_Pos,表示的是備庫執(zhí)行的最新位點;
如果Master_Log_File 和 Relay_Master_Log_File 值相同,且Read_Master_Log_Pos和Exec_Master_Log_Pos相同,說明接受到的日志已經(jīng)同步完成。
第三種方法
對比GTID集合確保主備無延遲
Auto_Position = 1,表示這對主備關(guān)系使用了GTID協(xié)議 Retrieved_Gtid_Set,是備庫收到的所有日志的GTID集合 Executed_Gtid_Set,是備庫所有已經(jīng)執(zhí)行完成的GTID集合如果兩個集合相同,表示備庫接收到了日志都已同步完成。
一個事務(wù)的binlog在主備庫之間的狀態(tài):
1、主庫執(zhí)行完成,寫入binlog,并反饋給客戶端;
2、binlog被從主庫發(fā)送給備庫,備庫收到
3、在備庫執(zhí)行binlog完成。
我們上面判斷主備無延遲的邏輯是"備庫收到的日志都執(zhí)行完成了",但是有一部分日志會處于客戶端已經(jīng)收到提交確認(rèn),而備庫還沒收到日志的狀態(tài)。
在主庫上執(zhí)行完成了三個事務(wù)trx1、trx2、trx3,前兩個已經(jīng)傳到從庫并且執(zhí)行完成了。trx3在主庫執(zhí)行完成,并且已經(jīng)回復(fù)給客戶端,但是還沒有傳到從庫中。此時在從庫B上執(zhí)行查詢請求,按照上面的三個方法的邏輯,從庫會認(rèn)為已經(jīng)沒有同步延遲,但是還是會查不到trx3.
配合semi-sync
解決上面的問題,要引入半同步復(fù)制,即semi-sync replication
semi-sync是這樣做的:
1、事務(wù)提交的時候,主庫把binlog發(fā)給從庫;
2、從庫收到binlog以后,發(fā)回給主庫一個ack,表示收到了
3、主庫收到這個ack以后,才能給客戶端返回"事務(wù)完成"的確認(rèn)
也就是說,如果啟用了semi-sync,就說明所有給客戶端發(fā)送過確認(rèn)的事務(wù)備庫都已經(jīng)收到了日志。
semi-sync+位點的判斷方案,在一主一備場景是成立的,在一主多從場景中,主庫只要等到一個從庫的ack,就開始給客戶端返回確認(rèn)。
但是這樣會出現(xiàn)問題:
1、查詢落到?jīng)]有收到最新日志的從庫上,產(chǎn)生過期讀。
2、業(yè)務(wù)更新高峰期,主庫的位點或者GTID集合更新很快,兩個位點的等值判斷一直不成立,很可能出現(xiàn)從庫上遲遲無法響應(yīng)查詢請求的情況
等主庫位點方案
select master_pos_wait(file, pos[, timeout]);這個命令邏輯如下:
1、在從庫執(zhí)行
2、參數(shù)file和pos指的是主庫的文件名和位置
3、timeout可選,設(shè)置為正整數(shù)N表示這個函數(shù)最多等待N秒
4、返回正整數(shù)M,表示從命令開始執(zhí)行,到應(yīng)用完file和pos表示的binlog位置執(zhí)行了多少事務(wù)
返回值還有一下異常結(jié)果:
1、NULL,執(zhí)行期間,備庫同步線程發(fā)生異常。
2、-1,等待時間超過N秒
3、0,這個位置已經(jīng)執(zhí)行過了
使用該方法步驟:
1、事務(wù)trx1更新完后,馬上執(zhí)行show master status得到當(dāng)前主庫執(zhí)行到的File和Position
2、選定一個從庫執(zhí)行查詢語句
3、在從庫上執(zhí)行select master_pos_wait(File,Position,1)
4、如果返回值是>=0的正整數(shù),則在這個從庫執(zhí)行查詢語句
5、否則到主庫執(zhí)行查詢語句。
假設(shè),每條select最多在從庫上等待1s,如果1s內(nèi)master_pos_wait返回一個>=0的整數(shù),就確保了從庫上執(zhí)行的這個查詢結(jié)果一定包含trx1數(shù)據(jù)。
如果每個從庫都延遲超過了1s,查詢壓力都會跑到主庫上去。
但是為了不允許過期讀,只有兩種方法:1、超時放棄 2、轉(zhuǎn)到主庫查詢
GTID方案
select wait_for_executed_gtid_set(gtid_set, 1);命令邏輯是:
1、等待,直到這個庫執(zhí)行的事務(wù)中包含傳入的gtid_set,返回0
2、超時返回1
等GTID的執(zhí)行流程為:
1、事務(wù)trx1更新后,從返回包直接獲取這個事務(wù)GTID,記為gtid1
2、選定一個從庫執(zhí)行查詢語句
3、在從庫上執(zhí)行select wait_for_executed_gtid_set(gtid1,1)
4、如果返回值為0,在這個從庫中執(zhí)行查詢語句
5、否則,到主庫執(zhí)行查詢語句
tips:使MySQL在執(zhí)行事務(wù)后,返回包中帶上GTID:
( 1. session_track_gtids設(shè)置為OWN_GTID 2. 通過API接口獲取mysql_session_track_get_first解析出gtid的值 )
總結(jié)
以上是生活随笔為你收集整理的《MySQL——如何解决一主多从的读写分离的过期读问题》的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 青菜多少钱啊?
- 下一篇: 《MySQL tips:并发查询与并发连