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