MySQL:從MySQL看主從架構(gòu)高可用性實(shí)現(xiàn)
從進(jìn)入互聯(lián)網(wǎng)時(shí)代開始,我們從單機(jī)走向集群再到當(dāng)前的微服務(wù)架構(gòu),我們已經(jīng)很少再使用單機(jī)架構(gòu)來實(shí)現(xiàn)業(yè)務(wù)邏輯,即使沒有使用微服務(wù),但是主備、主從等集群已經(jīng)屬于是業(yè)務(wù)側(cè)必備能力。
但是,無論是主備還是主從架構(gòu),實(shí)際上就是為了系統(tǒng)的高可用性實(shí)現(xiàn)的一個(gè)策略,防止主機(jī)因?yàn)槟承┕收蠈?dǎo)致異常下線,這時(shí)候備份或者從實(shí)例就會(huì)通過選擇或者其他策略成為主服務(wù)實(shí)例,對外繼續(xù)提供服務(wù)。
在MySQL的正常情況下,只要主庫執(zhí)行更新生成的所有binlog全部被正確的傳到備庫并且被正確執(zhí)行,備庫就能和主庫數(shù)據(jù)一致,實(shí)現(xiàn)最終一致性。但是最終一致性并不能滿足線上的性能需求,還需要保證集群的可用性。
1 主備延遲
1.1 主備延遲
在發(fā)生主備延遲時(shí),與數(shù)據(jù)同步的時(shí)間點(diǎn)主要包括:
- 主庫 A 執(zhí)行完成一個(gè)事務(wù),寫入 binlog,我們把這個(gè)時(shí)刻記為 T1;
- 之后傳給備庫 B,我們把備庫 B 接收完這個(gè) binlog 的時(shí)刻記為 T2;
- 備庫 B 執(zhí)行完成這個(gè)事務(wù),我們把這個(gè)時(shí)刻記為 T3。
主備延遲,就是同一個(gè)事務(wù),在備庫執(zhí)行完成的時(shí)間和主庫執(zhí)行完成的時(shí)間之間的差值,就是T3-T1。
在備庫執(zhí)行show slave status會(huì)得到seconds_behind_master,表示備庫延遲的時(shí)間,計(jì)算方法為:
- 每個(gè)事務(wù)的binlog都有一個(gè)時(shí)間字段,用于記錄主庫寫入時(shí)間;
- 備庫取出當(dāng)前正在執(zhí)行的事務(wù)的時(shí)間字段的值,計(jì)算與當(dāng)前系統(tǒng)時(shí)間差值,就是該值,單位為秒;
如果主備庫機(jī)器的系統(tǒng)時(shí)間設(shè)置不一致,不會(huì)導(dǎo)致主備延遲的值不準(zhǔn)。因?yàn)?,備庫連接到主庫的時(shí)候,會(huì)通過執(zhí)行 SELECT UNIX_TIMESTAMP() 函數(shù)來獲得當(dāng)前主庫的系統(tǒng)時(shí)間。如果這時(shí)候發(fā)現(xiàn)主庫的系統(tǒng)時(shí)間與自己不一致,備庫在執(zhí)行 seconds_behind_master 計(jì)算的時(shí)候會(huì)自動(dòng)扣掉這個(gè)差值。
但是:如果備庫已經(jīng)連接主庫后,修改主庫的系統(tǒng)時(shí)間,備庫同步的時(shí)候就不會(huì)再做時(shí)間的自動(dòng)修正了,因此,時(shí)間修正只有第一次建連的時(shí)候才會(huì)執(zhí)行。
在網(wǎng)絡(luò)正常的時(shí)候,日志從主庫傳給備庫所需的時(shí)間是很短的,即 T2-T1 的值是非常小的。也就是說,網(wǎng)絡(luò)正常情況下,主備延遲的主要來源是備庫接收完 binlog 和執(zhí)行完這個(gè)事務(wù)之間的時(shí)間差。所以說,主備延遲最直接的表現(xiàn)是,備庫消費(fèi)中轉(zhuǎn)日志(relay log)的速度,比主庫生產(chǎn) binlog 的速度要慢。
1.2 主備延遲的來源
1.2.1 主備機(jī)性能有差距
備庫所在機(jī)器性能比主庫的機(jī)器性能差,此時(shí)一般將備庫設(shè)置為“非雙1”模式【犧牲備庫的一點(diǎn)可靠性,減少寫盤次數(shù),增強(qiáng)IO能力】,更新過程中觸發(fā)大量讀操作,可能會(huì)導(dǎo)致主備延遲。
現(xiàn)在這種情況比較少,因?yàn)楝F(xiàn)在都是主從部署,可能隨時(shí)發(fā)生主從切換,因此一般都是對稱部署。
1.2.2 備庫壓力大
一般出現(xiàn)的原因是讀寫分離場景,備庫對外提供讀能力,查詢耗費(fèi)大量CPU資源,影響了同步速度,造成主備延遲。
此時(shí)的處理措施是:
- 一主多從,用從庫分擔(dān)壓力;
- 通過binlog輸出到外部系統(tǒng),比如Hadoop系統(tǒng),提供統(tǒng)計(jì)類查詢能力;
從庫和備庫在概念上其實(shí)差不多。在我們這個(gè)專欄里,為了方便描述,我把會(huì)在 HA 過程中被選成新主庫的,稱為備庫,其他的稱為從庫。
1.2.3 大事務(wù)
主庫必須等事務(wù)執(zhí)行完成后才能寫入binlog,再傳給備庫,造成主備延遲。
比如說大量數(shù)據(jù)的刪除就會(huì)造成大事務(wù),一般是要求分批執(zhí)行。之所以刪除會(huì)造成大事務(wù),是因?yàn)闊o論是否有索引,存儲引擎都是一條條數(shù)據(jù)查詢并加鎖,返回給執(zhí)行引擎,執(zhí)行引擎標(biāo)記數(shù)據(jù)刪除。所有的數(shù)據(jù)都處理完成后,才會(huì)提交事務(wù)釋放鎖。
另一種就是大表DDL。
1.3 主備延遲的排查思路
1)查數(shù)據(jù)庫在干什么
pager cat - | grep -v Sleep | sort -rn -k 12 | head -n 20
show full processlist;
select * from information_schema.processlist
where 1=1 order by TIME desc limit 10;
2)查看sql_thread在干什么
slave上查看狀態(tài):
show slave status\G;
查看relay_master_log_file以及exec_master_log_pos
master上解析binglog日志:
mysqlbinlog -v --base64-output=decode-rows --start-position=exec_master_log_pos relay_master_log_file
如果發(fā)現(xiàn)卡在操作某表上:
1))檢查表結(jié)構(gòu)
- 沒有索引:stop slave 可能會(huì)卡主,建議關(guān)閉mysql,啟動(dòng)后先加索引,然后start slave
- 有索引:只能等,大事務(wù)需要做拆分,不要操作太多數(shù)據(jù)
2))大事務(wù):M上session回話使用statement格式,使用語句級別的復(fù)制
3)查看MySQL狀態(tài)
- 機(jī)器性能(CPU、IO等):從庫配置適當(dāng)高一點(diǎn),使用新硬件PCI-E或SSD設(shè)備
- 表結(jié)構(gòu): 設(shè)計(jì)要合理,必須有主鍵,主鍵要短小,為查詢字段建索引
- 業(yè)務(wù)程序:適當(dāng)使用緩存,減少數(shù)據(jù)庫壓力
分析MySQL進(jìn)程并結(jié)合源碼:
perf top `pidof mysqld`
4)參數(shù)臨時(shí)優(yōu)化
- 主庫開啟group commit
- 從庫開啟writeset
- 從庫設(shè)置sync_binlog=0 && innodb_flush_log_at_trx_commit=2
5)檢查鎖情況
show engine innodb status\G;
2 主備切換策略
2.1 可靠性優(yōu)先策略
在雙M結(jié)構(gòu)下,主備切換的流程如圖:
圖片
- 判斷備庫 B 現(xiàn)在的 seconds_behind_master(SBM),如果小于某個(gè)值(比如 5 秒)繼續(xù)下一步,否則持續(xù)重試這一步;這里主從延遲時(shí)間短,說明當(dāng)前沒有大事務(wù),延遲比較低,減少因?yàn)檠舆t造成數(shù)據(jù)不可靠的幾率;
- 把主庫 A 改成只讀狀態(tài),即把 readonly 設(shè)置為 true;
- 判斷備庫 B 的 seconds_behind_master 的值,直到這個(gè)值變成 0 為止;
- 把備庫 B 改成可讀寫狀態(tài),也就是把 readonly 設(shè)置為 false;
- 把業(yè)務(wù)請求切到備庫 B。
這個(gè)切換流程,一般是由專門的 HA 系統(tǒng)來完成的,我們暫時(shí)稱之為可靠性優(yōu)先流程。
圖片
這個(gè)切換流程中是有不可用時(shí)間的。因?yàn)樵诓襟E 2 之后,主庫 A 和備庫 B 都處于 readonly 狀態(tài),也就是說這時(shí)系統(tǒng)處于不可寫狀態(tài),直到步驟 5 完成后才能恢復(fù)。
在這個(gè)不可用狀態(tài)中,比較耗費(fèi)時(shí)間的是步驟 3,可能需要耗費(fèi)好幾秒的時(shí)間。這也是為什么需要在步驟 1 先做判斷,確保 seconds_behind_master 的值足夠小。
2.2 可用性優(yōu)先策略
如果是直接將第4和第5步提前,保證了系統(tǒng)幾乎么有不可用時(shí)間,但是可能造成數(shù)據(jù)不一致。
其實(shí)這就是CAP中的C和A,MySQL主庫在寫完binlog后就給客戶端響應(yīng)了,沒等binlog同步到一個(gè)或多個(gè)備庫,這種策略是在C和A之間選擇了A,犧牲了C,如果主庫宕機(jī)了,但binlog的最后一個(gè)或幾個(gè)事務(wù)沒同步到備庫,那備庫成為主庫后,數(shù)據(jù)就丟了。其它的NoSQL很多是給用戶提供了選擇,比如Mongo,用戶可以設(shè)置日志同步到幾個(gè)Slave后再給客戶端響應(yīng),同步的Slave越多,C越強(qiáng),A越弱,比如同步到X個(gè)Slave后再給客戶端響應(yīng),那即使任何X個(gè)節(jié)點(diǎn)宕機(jī),集群中仍然有1個(gè)節(jié)點(diǎn)有最新日志,它會(huì)成為主節(jié)點(diǎn),數(shù)據(jù)沒丟,集群還可以工作。
在滿足數(shù)據(jù)可靠性的前提下,MySQL 高可用系統(tǒng)的可用性,是依賴于主備延遲的。延遲的時(shí)間越小,在主庫故障的時(shí)候,服務(wù)恢復(fù)需要的時(shí)間就越短,可用性就越高。
2.3 常見切換技術(shù)
semi-sync在網(wǎng)絡(luò)故障超時(shí)的情況下會(huì)退化成async,這個(gè)時(shí)候如果剛好主庫掉電了,有些binlog還沒有傳給從庫,從庫無法判斷數(shù)據(jù)跟主庫是否一致,如果強(qiáng)行切換可能會(huì)導(dǎo)致丟數(shù)據(jù),在金融業(yè)務(wù)場景下只能"人工智能"來做切換,服務(wù)中斷時(shí)間長。AliSQL采用雙通道復(fù)制更容易判斷主備數(shù)據(jù)是否一致,如果一致可以自動(dòng)切換,如果不一致才需要人工恢復(fù)數(shù)據(jù)。