MySQL 主從模式采用 GTID 的實(shí)踐
你好,我是悟空。
本文主要內(nèi)容如下:
一、背景
為了保證高可用,之前在測(cè)試環(huán)境部署了一套 MySQL 雙主模式,當(dāng)一個(gè)主庫(kù)服務(wù)出現(xiàn)異常,可以將流量切到另外一個(gè)主庫(kù),兩個(gè)主庫(kù)之間相互同步數(shù)據(jù)。
雙主模式
雙主模式的原理圖如下:
但是經(jīng)常出現(xiàn)數(shù)據(jù)沖突的問題,于是我們又把??雙主模式?
?改為了??主從讀寫分離模式?
?。主庫(kù)作為讀寫庫(kù),再加上一個(gè)從庫(kù)用來(lái)做 I/O 密集型的任務(wù)(如大量的數(shù)據(jù)統(tǒng)計(jì)操作)。如下圖所示:
另外從庫(kù)復(fù)制的模式采用??位點(diǎn)?
?的方式:指定 binlog 文件和 binlog 位置,這樣從庫(kù)就知道了復(fù)制的起始位置。(下文會(huì)講解這種方式)
雖然改為了主從模式,但依舊遇到了些問題:
- 問題 1:從庫(kù) B 復(fù)制數(shù)據(jù)時(shí),出現(xiàn)了主鍵沖突問題,導(dǎo)致同步失敗,從庫(kù)停止復(fù)制。猜測(cè)因主庫(kù)配置的 binlog 日志的格式為
mixed
,從庫(kù)同步時(shí)出現(xiàn)不一致的情況。 - 問題 2:從庫(kù) B 停止復(fù)制后,導(dǎo)致很多數(shù)據(jù)未同步到從庫(kù),出現(xiàn)主從大量數(shù)據(jù)不一致的情況。
- 問題 3:從庫(kù) B 想要恢復(fù)復(fù)制,必須先解決同步失敗的問題才能恢復(fù)。排查難度較大,耗時(shí)。
- 問題 4:從庫(kù) B 恢復(fù)時(shí),必須知道同步位點(diǎn),也就是從哪個(gè) binlog 文件和 binlog 位置斷開復(fù)制的,且即使找到了位點(diǎn),也不是精確的。
- 問題 5:從庫(kù) B 因同步異常導(dǎo)致停止復(fù)制到恢復(fù)復(fù)制這段期間,主庫(kù) A 自動(dòng)清理了幾天前的 binlog 日志,而這些日志從庫(kù) B 還未來(lái)得及同步,進(jìn)而導(dǎo)致再次同步失敗。
- 問題 6:主從存在同步延遲。
這篇我們來(lái)探討下問題 4 和問題 6。
其中問題 4 是一個(gè)比較頭疼的問題,我們一般是通過查看從庫(kù) B 當(dāng)前的同步狀態(tài)拿到同步位點(diǎn),然后設(shè)置同步位點(diǎn)后。但是重新啟動(dòng)同步的時(shí)候又會(huì)出現(xiàn)同步異常,比如從庫(kù) B 可能會(huì)出現(xiàn) Duplicate entry ‘id_of_R’ for key ‘PRIMARY’ 錯(cuò)誤,提示出現(xiàn)了主鍵沖突,然后停止同步。
為了減少位點(diǎn)同步引入的復(fù)雜度,我們切換成了 GTID 模式。
對(duì)于問題 6,本篇也僅限于探討如何觀察延遲,對(duì)于如何減少延遲不在本篇探討范圍之內(nèi)。
接下來(lái)我們來(lái)展開看下位點(diǎn)同步的痛點(diǎn)。
二、位點(diǎn)同步的痛點(diǎn)
2.1 通過位點(diǎn)同步的原理圖
為了更清晰地理解主從采用位點(diǎn)同步的原理,這里有一個(gè)原理圖:
1、主庫(kù)會(huì)生成多個(gè) binlog 日志文件。
2、從庫(kù)的I/O 線程請(qǐng)求指定文件和指定位置的 binlog 日志文件(位點(diǎn))。
3、主庫(kù) dump 線程獲取指定位點(diǎn)的 binlog 日志。
4、主庫(kù)按照從庫(kù)發(fā)送給來(lái)的位點(diǎn)信息讀取 binlog,然后推送 binlog 給從庫(kù)。
5、從庫(kù)將得到的 binlog 寫到本地的 relay log (中繼日志) 文件中。
6、從庫(kù)的 SQL 線程讀取和解析 relay log 文件。
7、從庫(kù)的 SQL 線程重放 relay log 中的命令。
當(dāng)我們使用位點(diǎn)同步的方式時(shí),兩種場(chǎng)景下的操作步驟比較復(fù)雜。
2.2 痛點(diǎn)
痛點(diǎn)1:首次開啟主從復(fù)制的步驟復(fù)雜
- 第一次開啟主從同步時(shí),要求從庫(kù)和主庫(kù)是一致的。
- 找到主庫(kù)的 binlog 位點(diǎn)。
- 設(shè)置從庫(kù)的 binlog 位點(diǎn)。
- 開啟從庫(kù)的復(fù)制線程。
痛點(diǎn)2:恢復(fù)主從復(fù)制的步驟復(fù)雜
- 找到從庫(kù)復(fù)制線程停止時(shí)的位點(diǎn)。
- 解決復(fù)制異常的事務(wù)。無(wú)法解決時(shí)就需要手動(dòng)跳過指定類型的錯(cuò)誤,比如通過設(shè)置slave_skip_errors=1032,1062。當(dāng)然這個(gè)前提條件是跳過這類錯(cuò)誤是無(wú)損的。(1062 錯(cuò)誤是插入數(shù)據(jù)時(shí)唯一鍵沖突;1032 錯(cuò)誤是刪除數(shù)據(jù)時(shí)找不到行)
不論是首次開啟同步時(shí)需要找位點(diǎn)和設(shè)置位點(diǎn),還是恢復(fù)主從復(fù)制時(shí),設(shè)置位點(diǎn)和忽略錯(cuò)誤,這些步驟都顯得過于復(fù)雜,而且容易出錯(cuò)。所以 MySQL 5.6 版本引入了 GTID,徹底解決了這個(gè)困難。
三、GTID 方案
3.1 GTID 是什么?
GTID 的全稱是 Global Transaction Identifier,全局事務(wù) ID,當(dāng)一個(gè)事務(wù)提交時(shí),就會(huì)生成一個(gè) GTID,相當(dāng)于事務(wù)的唯一標(biāo)識(shí)。
GTID 長(zhǎng)這樣:
結(jié)構(gòu):
server_uuid 是一個(gè)實(shí)例第一次啟動(dòng)時(shí)自動(dòng)生成的,是一個(gè)全局唯一的值;
gno 是一個(gè)整數(shù),初始值是 1,每次提交事務(wù)的時(shí)候分配給這個(gè)事務(wù),并加 1。
每個(gè) MySQL 實(shí)例都維護(hù)了一個(gè) GTID 集合,用來(lái)對(duì)應(yīng)“這個(gè)實(shí)例執(zhí)行過的所有事務(wù)”。
3.2 GTID 的優(yōu)勢(shì)
- 更簡(jiǎn)單的實(shí)現(xiàn) failover,不用以前那樣在需要找位點(diǎn)(log_file 和 log_pos)。
- 更簡(jiǎn)單的搭建主從復(fù)制。
- 比傳統(tǒng)的復(fù)制更加安全。
- GTID是連續(xù)的沒有空洞的,保證數(shù)據(jù)的一致性,零丟失。
3.3 如何啟用 GTID
修改主庫(kù)和從庫(kù)的配置文件:
從庫(kù)配置同步的參數(shù):
其中 master_auto_position 標(biāo)識(shí)主從關(guān)系使用的 GTID 協(xié)議。
相比之前的配置,MASTER_LOG_FILE 和 MASTER_LOG_POS 參數(shù)已經(jīng)不需要了。
3.4 GTID 同步方案
GTID 同步的原理圖。
GTID 方案:主庫(kù)計(jì)算主庫(kù) GTID 集合和從庫(kù) GTID 的集合的差集,主庫(kù)推送差集 binlog 給從庫(kù)。
當(dāng)從庫(kù)設(shè)置完同步參數(shù)后,主庫(kù) A 的GTID 集合記為集合 x,從庫(kù) B 的 GTID 集合記為 y。從庫(kù)同步的邏輯如下:
- 從庫(kù) B 指定主庫(kù) A,基于主備協(xié)議簡(jiǎn)歷連接。
- 從庫(kù) B 把集合 y 發(fā)給主庫(kù) A。
- 主庫(kù) A 計(jì)算出集合 x 和集合 y 的差集,也就是集合 x 中存在,集合 y 中不存在的 GTID 集合。比如集合 x 是 1~100,集合 y 是 1~90,那么這個(gè)差集就是 91~100。這里會(huì)判斷集合 x 是不是包含有集合 y 的所有 GTID,如果不是則說(shuō)明主庫(kù) A 刪除了從庫(kù) B 需要的 binlog,主庫(kù) A 直接返回錯(cuò)誤。
- 主庫(kù) A 從自己的 binlog 文件里面,找到第一個(gè)不在集合 y 中的事務(wù) GTID,也就是找到了 91。
- 主庫(kù) A 從 GTID = 91 的事務(wù)開始,往后讀 binlog 文件,按順序取 binlog,然后發(fā)給 B。
- 從庫(kù) B 的 I/O 線程讀取 binlog 文件生成 relay log,SQL 線程解析 relay log,然后執(zhí)行 SQL 語(yǔ)句。
GTID 同步方案和位點(diǎn)同步的方案區(qū)別是:
- 位點(diǎn)同步方案是通過人工在從庫(kù)上指定哪個(gè)位點(diǎn),主庫(kù)就發(fā)哪個(gè)位點(diǎn),不做日志的完整性判斷。
- 而 GTID 方案是通過主庫(kù)來(lái)自動(dòng)計(jì)算位點(diǎn)的,不需要人工去設(shè)置位點(diǎn),對(duì)運(yùn)維人員友好。
四、如何判斷主從庫(kù)是否有延遲
上面提到的問題 6 是主從讀寫分離后,從庫(kù)復(fù)制存在延遲,接下來(lái)我們來(lái)探討下如何觀察主從延遲多少的問題。
方案一:判斷從庫(kù)的同步狀態(tài)參數(shù) seconds_behind_master 是否為 0。(不準(zhǔn)確)
方案二:對(duì)比位點(diǎn)確保主備無(wú)延遲。
方案三:對(duì)比 GTID 集合確保主備無(wú)延遲。
方案一:查看 seconds_behind_master
可以在從庫(kù)上執(zhí)行 slow slave status 命令來(lái)看執(zhí)行結(jié)果里面的 ??seconds_behind_master?
? 參數(shù)的值,如下圖所示,Seconds_Behind_Master 等于 0
Seconds_Behind_Master 的單位是秒,所以精度不準(zhǔn)確。
所以為了保證查詢的數(shù)據(jù)是和主庫(kù)一致的,就需要先判斷 seconds_behind_master 是否已經(jīng)等于 0,如果不等于 0,就必須等到這個(gè)參數(shù)變?yōu)?0 才能執(zhí)行查詢請(qǐng)求。
方案二:對(duì)比位點(diǎn)
可以通過查看從庫(kù)當(dāng)前的同步位點(diǎn)來(lái)確認(rèn)從庫(kù)同步是否有延遲。下圖是在從庫(kù)上執(zhí)行 ??show slave status \G?
?命令后的結(jié)果:
Master_Log_File? 和 Read_Master_Log_Pos 這兩個(gè)參數(shù)合起來(lái)表示的是讀到的主庫(kù)的最新位點(diǎn),第一參數(shù)是代表讀取到了哪個(gè)文件,第二個(gè)是讀取到的文件的位置。
Relay_Master_Log_File? 和 Exec_Master_Log_Pos,這兩個(gè)參數(shù)合起來(lái)表示的是從庫(kù)執(zhí)行的最新位點(diǎn)。
如果紅色框起來(lái)的兩個(gè)參數(shù):Master_Log_File? 和 Relay_Master_Log_File 相等,則說(shuō)明從庫(kù)讀到的最新文件和主庫(kù)上生成的文件相同,這里都是 mysql-bin.000934。
如果藍(lán)色框起來(lái)的兩個(gè)參數(shù) Read_Master_Log_Pos? 和 Exec_Master_Log_Pos 相等,則說(shuō)明從庫(kù)讀到的日志文件的位置和從庫(kù)上執(zhí)行日志文件的位置相同,這里都是 59521082。
當(dāng)上面兩組參數(shù)都相等時(shí),則說(shuō)明沒有延遲。
方案三:對(duì)比 GTID 集合
方案三是對(duì)比 GTID 集合。首先我們?cè)趶膸?kù)上執(zhí)行 show slave status \G來(lái)查看 GTID 集合。
如下圖所示:
Master_UUID 表示當(dāng)前連接的主庫(kù)的 ID。
Auto_Position: 1 表示主備使用了 GTID 協(xié)議。
Retrieved_Gtid_Set 表示從庫(kù)收到的所有日志的 GTID 集合。
Executed_Gtid_Set 表示從庫(kù)已經(jīng)執(zhí)行完成的 GTID 集合。
如果 Executed_Gtid_Set 集合是包含 Retrieved_Gtid_Set,則表示從庫(kù)接收到的日志已經(jīng)同步完成。
比如上圖中 Retrieved_Gtid_Set 值為
前面一段是主庫(kù) id,后面一段 1-87383 是 GTID 范圍。而Executed_Gtid_Set 的值有兩個(gè)集合
Executed_Gtid_Set 的第二個(gè)集合和第一個(gè)集合完全一致,第一個(gè)集合 id 和 集合范圍是上次同步另外一個(gè)主庫(kù)的記錄。這里說(shuō)明從庫(kù)已經(jīng)和當(dāng)前主庫(kù)同步完成了。
方案二對(duì)比位點(diǎn)和方案三的 GTID 比對(duì)都要比方案一的seconds_behind_master 更準(zhǔn)確。但是還是沒有達(dá)到精確的程度,需要配合半同步復(fù)制(semi-sync replication)才能達(dá)到。
小結(jié):本篇通過 GTID 的方式更好地實(shí)現(xiàn)了主從節(jié)點(diǎn)的同步,以及如何觀察主從同步的延遲。
參考資料:
www.passjava.cn
https://time.geekbang.org/column/article/77636
高性能 MySQL 第四版
千金良方:MySQL性能優(yōu)化金字塔法則
關(guān)于我
8 年互聯(lián)網(wǎng)開發(fā)經(jīng)驗(yàn),擅長(zhǎng)微服務(wù)、分布式、架構(gòu)設(shè)計(jì)。目前在一家大型上市公司從事基礎(chǔ)架構(gòu)和性能優(yōu)化工作。
InfoQ 簽約作者、藍(lán)橋簽約作者、阿里云專家博主、51CTO 紅人。