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