在MYSQL 部署架構選型上,許多公司都會用到主從讀寫分離的架構,如下是一個一主一從的架構,主庫master負責寫入,從庫slave進行讀取。
但是既然是讀寫分離,必然會面臨這樣一個問題,當在主庫上進行更新後,有可能資料還沒來得及同步到從庫,但是這個時候又有讀資料的需求,為了能正確讀取出資料,這個時候就只有讀主庫了。但是這樣做增加了主庫的壓力,違反了我們做讀寫分離的初衷。所以這一節我們就來針對這種情況探討下,如何儘量的避免對主庫的壓力,儘量的從從庫讀取資料。
主從複製的原理
在探討解決方案前,我們先要對主從複製的原理有所瞭解,資料庫的操作都會記錄到binlog,如下圖所示,
1,從資料庫(slave
)會啟動兩個執行緒io_thread
和sql_thread
,透過io_thread將自身與主資料庫(master
)建立連線。
2,slave向master發出要同步的位置資訊(包含同步的檔名和偏移量),表示需要從該位置發起同步。
3,主資料庫master 將位置點後的binlog傳送給slave, slave獲取到本地形成relay log
(中轉日誌)。
4, 接著透過sql_thread解析relay log,執行sql。
從主從複製的過程可以看出,主從延遲時間是 在主庫master執行sql的時間點到從庫透過解析relay log 執行sql後的時間點之間的差值。如果應用程式能夠在master寫入資料後等待這麼一段時間,再去slave讀取,就能正確的讀取出來資料了。
但是這個時間差值是不確定的,究竟應用程式需要等待多久才去讀取slave,就成了我們需要思考🤔的問題。
如何避免延遲期間的主從資料不一致
比起在寫入資料後讀取主庫或者寫入資料後sleep一段時間讀取從庫,我給出兩個我覺得比較靠譜點的方法。
判斷位點是否同步
第一種方法是透過等待slave 將master寫入資料後的 binlog的位點同步完成再對slave進行讀取。
每次修改型sql的執行會將master的binlog 的位點(日誌偏移量)前移,如果在修改型sql執行完成後,能夠獲取到master的binlog 位點,並且在客戶端阻塞等待slave同步該位點完畢,再從slave讀取就可以了。
MYSQL中提供了一個函式select master_pos_wait(file, pos[, timeout])
用於在slave上執行等待master節點上的位點同步完成,其中file,和pos是在master上的檔案和位點,timeout 為了讓master_pos_wait
函式在timeout秒內沒有返回,則會直接觸發超時返回。
返回結果解析,
- 返回結果正常情況下是一個大於0的整數,表示從pos位點開始完成了多少個事務。
- 如果直接返回結果0,則說明在執行
select master_pos_wait(file, pos[, timeout])
時,位點已經同步完成。 - 如果觸發超時則返回-1。
- 如果執行期間slave發生錯誤,則返回NULL。
所以,在判斷是否應該在寫入資料後讀從庫的邏輯,我們可以這樣來寫,
1, 在master寫入資料後立馬執行 show master status
,可以獲取如下結果
可以看到master的binlog檔名稱以及位點。
2, 在slave上執行 select master_pos_wait('mysql-bin.232011',3129472,1);
,如果1s內沒有返回,則直接返回-1。
3, 在上一步如果觸發超時返回返回-1,則直接讀取主庫,如果是>=0 的值,則直接讀取從庫。
這樣便能最大程度從從庫讀取資料。
判斷GTID 是否同步
接著,我們來看下第二種方式,其實第二種方式和透過位點的方式類似,不同的是slave判斷是否將資料同步完成的依據是看GTID的值。
什麼是GTID值?
GTID 的全稱是 Global Transaction Identifier
,全域性事務 ID,是一個事務在提交的時候生成的,是這個事務的唯一標識。
MYSQL開啟 GTID 模式的方式是 在啟動一個 MySQL 例項的時候,加上引數 gtid_mode=on
和 enforce_gtid_consistency=on
。
每個事務是和GTID 值一一對應的,每個MYSQL例項會維護一個GTID 集合,來表示例項執行過的事務。
在slave節點上,透過show slave status
可以看到 GTID集合,如下圖所示,
Auto_Position=1
,表示這對主備關係使用了 GTID 協議。Retrieved_Gtid_Set
,是備庫收到的所有日誌的 GTID 集合。Executed_Gtid_Set
,是備庫所有已經執行完成的 GTID 集合。
如果Executed_Gtid_Set 等於Retrieved_Gtid_Set 說明slave將從master那裡獲取到的binlog全部執行完畢。
在master節點執行 show master status
,也能看到GTID集合,Executed_Gtid_Set
為master節點執行過的GTID集合。如下圖所示,
GTID 模式下判斷同步的步驟
在GTID 模式下,從庫slave從主庫master取binlog的邏輯將不再是直接告訴master 要取的檔案和位點了,而是由slave將自身的GTID集合告訴master。
master再結合自身的GTID集合,找出在master中有但是在slave中沒有的GTID集合,然後從binlog中找到第一個不在GTID集合中的事務,從該事務的binlog位點開始,往後讀取binlog傳送給slave。
MYSQL針對於GTID同樣提供 了一個函式select wait_for_executed_gtid_set(gtid_set, 1);
來讓slave去判斷對master執行過的gtid_set 是否已經同步完成。
wait_for_executed_gtid_set
函式的返回結果解析如下,
- 如果slave 執行的事務中包含傳入的 gtid_set,返回 0。
- 如果等待1s後還沒同步完成,則返回1。
所以在GTID 模式下的,在判斷是否應該在寫入資料後讀從庫的邏輯,我們可以這樣來寫,
1, 在master寫入資料後立馬執行 show master status
,可以獲取如下結果
可以看到master的Executed_Gtid_Set的值。
2, 在slave上執行
select wait_for_executed_gtid_set('76cd5ea1-c541-11ee-87ef-fa163eefe144:1-56382789,
808d2fb8-687b-11ec-b8b9-fa163e410530:1-144078103,
9081c19b-63de-11ed-9755-fa163eb8b97f:1-1093294115', 1);
,如果1s內沒有返回,則直接返回1。
3, 在上一步如果觸發超時即返回1,則直接讀取主庫,如果是=0 ,則直接讀取從庫。
這樣便能最大程度從從庫讀取資料。
自薦一波:
歡迎朋友們關注我的公眾號📢📢:【藍胖子的程式設計夢】!
歡迎點贊 👍、收藏 💙、關注 💡 三連支援一下~🎈🎈
我是藍胖子,下期見~🙇💻