自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

MySQL讀寫分離,寫完讀不到問(wèn)題如何解決

數(shù)據(jù)庫(kù) MySQL
今天我們來(lái)詳細(xì)了解一下主從同步延遲時(shí)讀寫分離發(fā)生寫后讀不到的問(wèn)題,依次講解問(wèn)題出現(xiàn)的原因,解決策略以及 Sharding-jdbc、MyCat 和 MaxScale 等開源數(shù)據(jù)庫(kù)中間件具體的實(shí)現(xiàn)方案。

[[385898]]

本文轉(zhuǎn)載自微信公眾號(hào)「程序員歷小冰」,作者歷小冰 。轉(zhuǎn)載本文請(qǐng)聯(lián)系程序員歷小冰公眾號(hào)。

大家好,我是歷小冰。

今天我們來(lái)詳細(xì)了解一下主從同步延遲時(shí)讀寫分離發(fā)生寫后讀不到的問(wèn)題,依次講解問(wèn)題出現(xiàn)的原因,解決策略以及 Sharding-jdbc、MyCat 和 MaxScale 等開源數(shù)據(jù)庫(kù)中間件具體的實(shí)現(xiàn)方案。

寫后讀不到問(wèn)題

MySQL 經(jīng)典的一主兩從三節(jié)點(diǎn)架構(gòu)是大多數(shù)創(chuàng)業(yè)公司初期使用的主流數(shù)據(jù)存儲(chǔ)方案之一,主節(jié)點(diǎn)處理寫操作,兩個(gè)從節(jié)點(diǎn)處理讀操作,分?jǐn)偭酥鲙?kù)的壓力。

但是,有時(shí)候可能會(huì)遇到執(zhí)行完寫操作后,立刻去讀發(fā)現(xiàn)讀不到或者讀到舊狀態(tài)的尷尬場(chǎng)景。這是由于主從同步可能存在延遲,在主節(jié)點(diǎn)執(zhí)行完寫操作,再去從節(jié)點(diǎn)執(zhí)行讀操作,讀取了之前舊的狀態(tài)。

 

上圖展示了此類問(wèn)題出現(xiàn)的操作順序示意圖:

•客戶端首先通過(guò)代理向主節(jié)點(diǎn) Master 進(jìn)行了寫入操作

•緊接著第二步去從節(jié)點(diǎn) Slave A 執(zhí)行讀操作,此時(shí) Master 和 Slave A 之間的同步還未完成,所以第二步的讀操作讀取到了舊狀態(tài)

•當(dāng)?shù)谖宀皆俅芜M(jìn)行讀操作時(shí),此時(shí)同步已經(jīng)完成,所以可以從 Slave B 中讀取到正確的狀態(tài)。

下面,我們就來(lái)看一下為什么會(huì)出現(xiàn)此類問(wèn)題。

MySQL 主從同步

理解問(wèn)題背后發(fā)生的原因,才能更好的解決問(wèn)題。MySQL 主從復(fù)制的過(guò)程大致如下圖所示,本篇文章只講解同步過(guò)程中的流程,建立同步連接和失聯(lián)重傳不是重點(diǎn),暫不講解,感興趣的同學(xué)可以自行了解。

 

MySQL 主從復(fù)制,涉及主從兩個(gè)節(jié)點(diǎn),一共四個(gè)四個(gè)線程參與其中:

  • 主節(jié)點(diǎn)的 Client Thread,處理客戶端請(qǐng)求的線程,執(zhí)行如圖所示的1~5步驟,2,3,4步驟是為了保證數(shù)據(jù)的一致性和盡量減少丟失,第三步驟時(shí)會(huì)通知 Dump Thread;
  • 主節(jié)點(diǎn)的 Dump Thread,接收到 Client Thread 通知后,負(fù)責(zé)讀取本地的 binlog 的數(shù)據(jù),將 binlog 數(shù)據(jù),binlog 文件名 以及當(dāng)前發(fā)送 binlog 的位置信息發(fā)送給從節(jié)點(diǎn);
  • 從節(jié)點(diǎn)的 IO Thread 負(fù)責(zé)接收 Dump Thread 發(fā)送的 binlog 數(shù)據(jù)和相關(guān)位置信息,將其追加到本地的 relay log 等文件中;
  • 從節(jié)點(diǎn)的 SQL Thread 檢測(cè)到 relay log 追加了新數(shù)據(jù),則解析其內(nèi)容(其實(shí)就是解析 binlog 文件的內(nèi)容)為可以執(zhí)行的 SQL 語(yǔ)句,然后在本地?cái)?shù)據(jù)執(zhí)行,并記錄下當(dāng)前執(zhí)行的 relay log 位置。

上述是默認(rèn)的異步同步模式,我們發(fā)現(xiàn),從主節(jié)點(diǎn)提交成功到從節(jié)點(diǎn)同步完成,中間間隔了6,7,8,9,10多個(gè)步驟,涉及到一次網(wǎng)絡(luò)傳輸,多次文件讀取和寫入的磁盤 IO 操作,以及最后的 SQL 執(zhí)行的 CPU 操作。

所以,當(dāng)主從節(jié)點(diǎn)間網(wǎng)絡(luò)傳輸出現(xiàn)問(wèn)題,或者從節(jié)點(diǎn)性能較低時(shí),主從節(jié)點(diǎn)間的同步就會(huì)出現(xiàn)延遲,導(dǎo)致文章一開始提及的寫后讀不到的問(wèn)題。在高并發(fā)場(chǎng)景,從節(jié)點(diǎn)一般要過(guò)幾十毫秒,甚至幾百毫秒才能讀到最新的狀態(tài)。

常見的解決策略

一般來(lái)講,大致有如下方案解決寫后讀不出問(wèn)題:

•強(qiáng)制走主庫(kù)

•判斷主備無(wú)延遲

•等主庫(kù)位點(diǎn)或 GTID 方案

強(qiáng)制走主庫(kù)

強(qiáng)制走主庫(kù)方案最容易理解和實(shí)現(xiàn),它也是最常用的方案。顧名思義,它就是強(qiáng)制讓部分必須要讀到最新狀態(tài)的讀操作去主節(jié)點(diǎn)執(zhí)行,這樣就不會(huì)出現(xiàn)寫后讀不出問(wèn)題。這種方案問(wèn)題在于將一部分讀壓力給了主節(jié)點(diǎn),部分破化了讀寫分離的目的,降低了整個(gè)系統(tǒng)的擴(kuò)展性。

一般主流的數(shù)據(jù)庫(kù)中間件都提供了強(qiáng)制走主庫(kù)的機(jī)制,比如,在 sharding-jdbc 中,可以使用 Hint 來(lái)強(qiáng)制路由主庫(kù)。

 

它的原理就是在 SQL 語(yǔ)句前添加 Hint,然后數(shù)據(jù)庫(kù)中間件會(huì)識(shí)別出 Hint,將其路由到主節(jié)點(diǎn)。

下面,我們就來(lái)看一下如果要去從庫(kù)查詢,并且要避免過(guò)期讀的方案,并分析各個(gè)方案的優(yōu)缺點(diǎn)。

判斷主備無(wú)延遲

第二種方案是使用 show slave status 語(yǔ)句結(jié)果中的部分值來(lái)判斷主從同步的延遲時(shí)間:

  1. > show slave status 
  2. *************************** 1. row *************************** 
  3. Master_Log_File: mysql-bin.001822 
  4. Read_Master_Log_Pos: 290072815 
  5. Seconds_Behind_Master: 2923 
  6. Relay_Master_Log_File: mysql-bin.001821 
  7. Exec_Master_Log_Pos: 256529431 
  8. Auto_Position: 0 
  9. Retrieved_Gtid_Set:  
  10. Executed_Gtid_Set:  
  11. ..... 

•seconds_behind_master,表示落后主節(jié)點(diǎn)秒數(shù),如果此值為0,則表示主從無(wú)延遲

•Master_Log_File 和 Read_Master_Log_Pos,表示的是讀到的主庫(kù)的最新位點(diǎn),Relay_Master_Log_File 和 Exec_Master_Log_Pos,表示的是備庫(kù)執(zhí)行的最新位點(diǎn)。如果這兩組值相等,則表示主從無(wú)延遲

•Auto_Position=1 ,表示使用了 GTID 協(xié)議,并且備庫(kù)收到的所有日志的 GTID 集合 Retrieved_Gtid_Set 和 執(zhí)行完成的 GTID 集合 Executed_Gtid_Set 相等,則表示主從無(wú)延遲。

在進(jìn)行讀操作前,先根據(jù)上述方式來(lái)判斷主從是否有延遲,如果有延遲,則一直等待到無(wú)延遲后執(zhí)行。但是這類方案在判斷是否有延遲時(shí)存在著假陽(yáng)和假陰的問(wèn)題:

•判斷無(wú)延遲,其他延遲了。因?yàn)樯鲜雠袛嗍腔趶墓?jié)點(diǎn)的狀態(tài),當(dāng)主節(jié)點(diǎn)的 Dump Thread 尚未將最新狀態(tài)發(fā)送給從節(jié)點(diǎn)的 IO SQL 時(shí),從節(jié)點(diǎn)可能會(huì)錯(cuò)誤的判斷自己和主節(jié)點(diǎn)無(wú)延遲。

•判斷有延遲,但是讀操作讀取的最新狀態(tài)已經(jīng)同步。因?yàn)?MySQL 主從復(fù)制是一直在進(jìn)行的,寫后直接讀的同時(shí)可能還有其他無(wú)關(guān)寫操作,雖然主從有延遲,但是對(duì)于第一次寫操作的同步已經(jīng)完成,所以讀操作已經(jīng)可以讀到最新的狀態(tài)。

對(duì)于第一個(gè)問(wèn)題,需要使用主從復(fù)制的 semi-sync 模式,上文中講解介紹的是默認(rèn)的異步模式,semi-sync 模式的流程如下圖所示:

 

•當(dāng)主節(jié)點(diǎn)事務(wù)提交的時(shí)候,Dump Thread 把 binlog 發(fā)給從節(jié)點(diǎn);

•從節(jié)點(diǎn)的 IO Thread 收到 binlog 以后,發(fā)回給主節(jié)點(diǎn)一個(gè) ack,表示收到了;

•主節(jié)點(diǎn)的 Dump Thread 收到這個(gè) ack 以后,再通知 Client Thread ,此時(shí)才能給客戶端返回執(zhí)行成功的響應(yīng)。

這樣,寫操作執(zhí)行后,就確保從節(jié)點(diǎn)已經(jīng)讀取到主節(jié)點(diǎn)發(fā)送的 binglog 數(shù)據(jù),即 Master_Log_File、 Read_Master_Log_Pos 或 Retrieved_Gtid_Set 是最新的,這樣才能與執(zhí)行的相關(guān)數(shù)據(jù)進(jìn)行對(duì)比,判斷是否有延遲。

可惜的是,上述 semi-sync 模式只需要等待一個(gè)從節(jié)點(diǎn)的ACK,所以一主多從的模式該方案將會(huì)無(wú)效。

雖然該方案有種種問(wèn)題,但是對(duì)于一致性要求不那么高的場(chǎng)景也能適用,比如 MyCat 就是用 seconds_behind_master 是否落后主節(jié)點(diǎn)過(guò)多,如果超過(guò)一定閾值,就將其從有效從節(jié)點(diǎn)列表中刪除,不再將讀請(qǐng)求路由到它身上。

在 MyCAT 的用于監(jiān)聽從節(jié)點(diǎn)狀態(tài),發(fā)送心跳的 MySQLDetector 類中,它會(huì)讀取從節(jié)點(diǎn)的 seconds_behind_master,如果其值大于配置的 slaveThreshold,則將打印日志,并將延遲時(shí)間設(shè)置到心跳信息中。

 

下面,我們就介紹能夠解決第二個(gè)問(wèn)題的方案,即判斷有延遲,但是讀操作讀取的特定最新狀態(tài)已經(jīng)同步。

等GTID 方案

首先介紹一下 GTID,也就是全局事務(wù) ID,是一個(gè)事務(wù)在提交的時(shí)候生成的,是這個(gè)事務(wù)的唯一標(biāo)識(shí)。它由MySQL 實(shí)例的uuid和一個(gè)整數(shù)組成,該整數(shù)由該實(shí)例維護(hù),初始值是 1,每次該實(shí)例提交事務(wù)后都會(huì)加一。

MySQL 提供了一條基于 GTID 的命令,用于在從節(jié)點(diǎn)上執(zhí)行,等待從庫(kù)同步到了對(duì)應(yīng)的 GTID(binlog文件中會(huì)包含 GTID),或者超時(shí)返回。

 

MySQL 在執(zhí)行完事務(wù)后,會(huì)將該事務(wù)的 GTID 會(huì)給客戶端,然后客戶端可以使用該命令去要執(zhí)行讀操作的從庫(kù)中執(zhí)行,等待該 GTID,等待成功后,再執(zhí)行讀操作;如果等待超時(shí),則去主庫(kù)執(zhí)行讀操作,或者再換一個(gè)從庫(kù)執(zhí)行上述流程。

MariaDB 的 MaxScale 就是使用該方案,MaxScale 是 MariaDB 開發(fā)的一個(gè)數(shù)據(jù)庫(kù)智能代理服務(wù)(也支持 MySQL),允許根據(jù)數(shù)據(jù)庫(kù) SQL 語(yǔ)句將請(qǐng)求轉(zhuǎn)向目標(biāo)一個(gè)到多個(gè)服務(wù)器,可設(shè)定各種復(fù)雜程度的轉(zhuǎn)向規(guī)則。

 

MaxScale 在其 readwritesplit.hh 頭文件和 rwsplit_causal_reads.cc 文件中的 add_prefix_wait_gtid 函數(shù)中使用了上述方案。

 

舉個(gè)例子,原來(lái)要執(zhí)行讀操作的 SQL 和添加了前綴的 SQL 如下所示:

當(dāng) WAIT_FOR_EXECUTED_GTID_SET 執(zhí)行失敗后,原 SQL 就不會(huì)再執(zhí)行,而是將該 SQL 去主節(jié)點(diǎn)執(zhí)行。

 

責(zé)任編輯:武曉燕 來(lái)源: 程序員歷小冰
相關(guān)推薦

2017-09-01 10:48:33

分離CQRS性能

2022-02-22 11:54:05

跨域項(xiàng)目前后端

2018-10-16 16:45:05

數(shù)據(jù)庫(kù)讀寫分離

2010-05-17 11:19:44

MySQL proxy

2021-10-20 20:27:55

MySQL死鎖并發(fā)

2025-02-11 12:29:58

2023-10-30 18:35:47

MySQL主從延時(shí)

2011-08-08 10:29:12

MySQL

2022-10-13 14:11:29

瀏覽器域名端口

2024-12-05 09:06:58

2013-08-21 10:30:37

iOSNSBundle pa解決

2010-04-29 17:46:31

Oracle死鎖

2012-09-05 11:09:15

SELinux操作系統(tǒng)

2011-03-15 13:30:27

IBatis.netMySQL

2017-12-26 16:18:00

架構(gòu)服務(wù)化讀寫分離

2021-09-27 13:33:03

MySQL深分頁(yè)數(shù)據(jù)庫(kù)

2021-09-26 06:43:07

MySQL深分頁(yè)優(yōu)化

2021-11-09 10:20:15

MySQL深分頁(yè)數(shù)據(jù)庫(kù)

2011-05-06 16:28:44

共享打印機(jī)

2024-10-29 16:41:24

SpringBoot跨域Java
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)