前言
在上一篇MySQL主從復制(一)——實戰文章中,我們簡單的提了一下它的實現原理,隨后即開始了其相關的實現實戰內容。在本篇文章中,我們主要詳細了解數據庫主從復制的實現原理以及其在同步過程中存在的時延問題。
1. 主從復制的形式
- 一主一從
- 主主復制
- 一主多從:擴展系統讀取的性能,因為讀是在從庫讀取的;
- 多主一從:MySQL5.7開始支持;
- 聯級復制
2. 主從復制實現原理
簡單來講,其過程為
- 主庫的任何數據更改都會被記錄到
二進制日志(binlog)
中; - 從庫生成兩個線程,一個
I/O線程
,一個SQL線程
; - I/O線程去請求主庫 的binlog,并將得到的binlog日志寫到
relay log(中繼日志)
文件中; - 主庫會生成一個
log dump 線程
,用來給從庫 I/O線程傳binlog; - SQL 線程,會讀取relay log文件中的日志,從Exec_Master_Log_Pos位置開始執行讀取到的更新事件,將更新內容寫入到slave的db,來實現主從的操作一致,而最終數據一致;
我們可以發現從庫同步主庫數據的過程是串行化的,也就是說主庫上并行的操作,在從庫上會串行執行,由于從庫從主庫拷貝日志以及串行執行 SQL 的特點,在高并發場景下,從庫的數據一定會比主庫慢一些,是有延時的。所以經常出現,剛寫入主庫的數據可能是讀不到的,要過幾十毫秒,甚至幾百毫秒才能讀取到。這就是我們接下來要談到的主從同步時延問題
。
3.主從同步時延問題的產生
時延問題可能會導致我們新增的數據無法讀取到,我們針對業務的不同情況來決定我們是否允許有時延,若不允許,則需要我們進行相應的配置去解決。
3.1 時延情況觀察
我們在配置好的從庫上執行show slave status\G;
可以看到如下參數:
-
Master_Log_File
: SLAVE中的I/O線程當前正在讀取的主服務器二進制日志文件的名稱; -
Read_Master_Log_Pos
: 在當前的主服務器二進制日志中,SLAVE中的I/O線程已經讀取的位置; -
Relay_Log_File
: SQL線程當前正在讀取和執行的中繼日志文件的名稱; -
Relay_Log_Pos
: 在當前的中繼日志中,SQL線程已讀取和執行的位置; -
Relay_Master_Log_File
: 由SQL線程執行的包含多數近期事件的主服務器二進制日志文件的名稱; -
Slave_IO_Running
: I/O線程是否被啟動并成功地連接到主服務器上; -
Slave_SQL_Running
: SQL線程是否被啟動; -
Seconds_Behind_Master
: 從屬服務器SQL線程和從屬服務器I/O線程之間的時間差距,單位以秒計。
從以上的參數中,我們可以觀察得知主從數據庫的時延情況:
- 從庫同步延遲情況出現的
Seconds_Behind_Master
不為0,這個數值可能會很大; -
Relay_Master_Log_File
和Master_Log_File
顯示bin-log的編號相差很大,說明bin-log在從庫上沒有及時同步,所以近期執行的bin-log和當前IO線程所讀的bin-log相差很大; - MySQL的從庫數據目錄下存在大量mysql-relay-log日志,該日志同步完成之后就會被系統自動刪除,存在大量日志,說明主從同步延遲很高。
3.2 主從同步延時問題的產生
1)MySQL數據庫主從同步延遲原理:主庫針對寫操作,順序寫binlog,從庫單線程去主庫順序讀”寫操作的binlog”,從庫取到binlog在本地原樣執行(隨機寫),來保證主從數據邏輯上一致。mysql的主從復制都是單線程的操作,主庫對所有DDL和DML產生binlog,binlog是順序寫,所以效率很高,slave的Slave_IO_Running線程到主庫取日志,效率比較高,下一步,問題來了,slave的Slave_SQL_Running線程將主庫的DDL和DML操作在slave實施。DML和DDL的IO操作是隨即的,不是順序的,成本高很多,還可能可slave上的其他查詢產生lock爭用,由于Slave_SQL_Running也是單線程的,所以一個DDL卡主了,需要執行10分鐘,那么所有之后的DDL會等待這個DDL執行完才會繼續執行,這就導致了延時。有朋友會問:“主庫上那個相同的DDL也需要執行10分,為什么slave會延時?”,答案是master可以并發,Slave_SQL_Running線程卻不可以。
2)MySQL數據庫主從同步延遲是怎么產生的?當主庫的TPS并發較高時,產生的DDL數量超過slave一個sql線程所能承受的范圍,那么延時就產生了,當然還有就是可能與slave的大型query語句產生了鎖等待。首要原因:數據庫在業務上讀寫壓力太大,CPU計算負荷大,網卡負荷大,硬盤隨機IO太高次要原因:讀寫binlog帶來的性能影響,網絡傳輸延遲。
4. 主從同步時延問題解決
在講解時延問題解決時,我們先大概了解MySql主從復制存在的問題:
-
主庫宕機后,數據可能丟失
; -
從庫只有一個sql thread,在主庫寫壓力大的時候,復制可能存在時延
;
4.1 MySql提供的幾種主從復制機制:
(1)異步復制
:MYSQL 默認的復制方式,就是主庫寫入binlog日志后即可成功返回客戶端,無須等待binlog日志傳遞給從庫的過程。但這樣一旦主庫發生宕機,就有可能出現數據丟失的情況。
(2)半同步復制(mysql semi-sync)
,解決數據丟失的問題,其原理如下:
- 事務在主庫寫完binlog后需要從庫返回一個已接受,才返回給客戶端。簡單的講就是,主庫寫入 binlog 日志之后,就會將強制此時立即將數據同步到從庫,從庫將日志寫入自己本地的 relay log 之后,接著會返回一個 ack 給主庫,主庫接收到至少一個從庫的 ack 之后才會認為寫操作完成了
- 5.5集成到mysql,以插件的形式存在,需要單獨安裝;
- 確保事務提交后binlog至少傳輸到一個從庫 ;
- 不保證從庫應用完這個事務的binlog;
- 性能有一定的降低,響應時間會更長;
- 網絡異常或從庫宕機,卡主主庫,直到超時或從庫恢復;
(3)并行復制
,解決從庫復制延遲的問題,指的是從庫開啟多個線程,并行讀取 relay log 中不同庫的日志,然后并行重放不同庫的日志,這是庫級別的并行。其特點如下:
- 5.6中新增;
- 并行是指從庫多線程apply binlog ;
- 庫級別并行應用binlog,同一個庫數據更改還是串行的(5.7版并行復制基于事務組)設置set global slave_parallel_workers=10;設置sql線程數為10;
4.2 時延問題解決
分庫,將一個主庫拆分為多個主庫,每個主庫的寫并發就減少了幾倍,此時主從延遲可以忽略不計。
打開 MySQL 支持的并行復制,多個庫并行復制。如果說某個庫的寫入并發就是特別高,單庫寫并發達到了 2000/s,并行復制還是沒意義。
針對主從延遲,本人的經驗如下:業務量不大的
:主庫能處理業務就全放在主庫吧,從庫只做災備,備份,對實時性要求不高的統計報表類工作;已經出現延遲的
:一般來說,就慢慢等吧,試圖通過重啟db之類的操作是無法解決的,還會因為大事務回滾再重做導致花的時間更長;延遲N天無法解決的
:那就重做slave。為什么會延遲N天,難道僅僅是因為從庫單線程嗎?我感覺大部分都是主庫上采用mixed的binlog_format,由于某種限制,無法基于statement,只好row模式復制。那么如果當前sql是全表掃描,傳到slave上執行時就是茫茫多次的全表掃描了。一般來說在slave上show proceslist看查看當前的system user正在執行什么,那就是問題SQL。如果pos點一直不動,也可以去主庫對應的binlog上查看下執行的是什么東西;出現延遲時,查看下當前slave的cpu和磁盤狀況
:一般來說如果從庫沒有其他業務,單線程的原因,cpu跑滿一個核已經是極限了。磁盤io滿的話,確認下是否有其他進程或mysql線程影響了它(比如從庫正在dump或者超大的sql在執行),也可以嘗試調整下slave上關于io的幾個參數;從庫raid卡,務必設置成write back的寫策略
;批量的dml操作
:批量的dml操作如果不做處理,一般必然會出現延遲,建議業務低峰期執行,并將批量操作做下調整,一次dml 10000行,sleep一會,再dml 10000行。具體的行數和sleep需要自己根據業務確定,能保證從庫不延遲就好;如果還是經常性的短時間延遲,那就嘗試加大從庫的硬件配置,比如上sata SSD,pcie等
延遲的監控到位
,可通過pt-heart-beat來準確監控延遲值,及時發現查看。5.5以后版本的,可以考慮
采用半同步復制
,能解決少量延遲引起的問題,不過對tps性能損耗較大
;升級到mysql 5.7吧,
多線程復制
即并行復制
,幾乎完美解決單線程復制引起的從庫延遲。
5. 總結
總的來說,我們可以根據業務場景的不同,合理的選擇是否主從復制后再結合讀寫分離,因為讀寫分離,是必須要面對時延這一問題,我們可以根據我們需求來確定到底是不是真的需要讀寫分離,因為我們也同樣可以利用redis緩存,或者其它緩存來緩解我們數據庫的壓力,而將從庫僅作為我們的備份,這樣我們就不需要去保證從庫無時延(即數據與主庫實時同步),所以具體的應用需要符合我們自己的業務場景。