高可用架構
主備一致
基本原理
M-S架構:客戶端的讀寫都直接訪問A庫,直到切換時把客戶端讀寫切換給B庫,A變成備庫
備庫設定為readonly狀態:防止切換過程出現雙寫,可以用readonly狀態判斷節點的角色
基本原理:主庫A和備庫B之間維持一個長連線,主庫內部有一個執行緒專門用於服務B的這個長連線
-
在備庫上透過change master命令可設定主庫A的IP,密碼,從哪個位置開始請求binlog
-
在備庫 B 上執行 start slave 命令,這時候備庫會啟動兩個執行緒,io_thread 和 sql_thread,其中 io_thread 負責與主庫建立連線
-
主庫 A 校驗完使用者名稱、密碼後,開始按照備庫 B 傳過來的位置,從本地讀取 binlog,發給 B
-
備庫 B 拿到 binlog 後,寫到本地檔案,稱為中轉日誌(relay log)
-
sql_thread 讀取中轉日誌,解析出日誌裡的命令,並執行
雙M架構:庫A和庫B總是互為主備關係,在切換時就不需要再修改主備關係
基本原理:
-
判斷備庫 B 現在的 seconds_behind_master,即主備延遲時間,如果小於某個值(比如 5 秒)繼續下一步,否則持續重試這一步;
之所以到等到該值小於某個值後再往下執行,是為了儘量縮短第三步中A和B庫都不可用的時間
-
把主庫 A 改成只讀狀態,即把 readonly 設定為 true;
-
判斷備庫 B 的 seconds_behind_master 的值,直到這個值變成 0 為止;
在這個等待該值變0的過程,就是等待備庫進行資料同步的過程,這個階段A和B庫都是readonly階段,都不能接受寫操作
-
把備庫 B 改成可讀寫狀態,也就是把 readonly 設定為 false;
-
把業務請求切到備庫 B。
可用性優先策略:不等待主備資料同步,直接把連線切換到備庫B並讓B庫可以讀寫,在statement和mixed格式下的binlog日誌可能會導致資料不一致問題,在row格式會報錯,儘量使用可靠性優先策略
雙M架構可能會導致迴圈複製問題:當節點A把binlog發給B,B在執行A-binlog時也會記錄下這些操作為B-binlog,而A又同時是B的備庫,所以它又會執行一遍B-binlog,即重複執行A-binlog
解決方法:一個備庫接收到binlog並在重放過程中,生成與原binlog的server id相同的binlog,而每個庫收到主庫的binlog會拋棄其中server id等於自己的部分
這樣上文的B-binlog裡是A執行過的操作會將server id記為A,A再收到B-binlog時就能拋棄掉這重複的部分
一主多從架構:一般用於讀寫分離,主庫負責所有的寫入和一部分讀請求,其他讀請求由從庫承擔
在該架構下的主庫切換:當主庫A當機時,A‘會成為新主庫,同時B、C、D庫也需要改接到新主庫A’
基於位點的主備切換:當A‘成為新主庫時,其他節點要設定為A’的從庫需要知道主庫對應的檔名和日誌偏移量(即同步點位)
取同步點位的方法是等待A’將relay log全部同步完成,執行show master status
得到當前A‘上最新的點位,再取原主庫A故障的時刻T,用工具解析A’的檔案得到T時刻的點位
但這個點位是不準確的,在從庫B上執行時可能會出現主鍵衝突等錯誤,有兩種常用的方法跳過錯誤:
- 主動跳過一個事務,
set global sql_slave_counter=1;
- 透過設定slave_skip_errors引數,直接設定跳過指定的錯誤:在主備切換時常出現的錯誤有1062,插入資料時唯一鍵衝突和1032刪除資料時找不到行
GTID
主備延遲
主備延遲:同一個事務,在備庫執行完成的時間和主庫執行完成的時間之間的差值,在網路正常的情況下主要表現在備庫消費relay log的速度比主庫生產binlog的速度要慢,導致主備延遲的主要來源:
- 在早期的部署中,備庫所在機器的效能比主庫所在的機器效能差
- 做對稱部署
- 備庫提供一些讀能力,在備庫上的查詢耗費大量CPU資源,影響同步速度
- 做一主多從
- 執行時間很長的大事務(因為要執行完事務再寫入binlog)如一次性用delete刪除大量資料、大表DDL
- MySQL5.6之前,sql_thread單執行緒更新資料,導致備庫應用日誌不夠快
- 做多執行緒的並行複製
在雙M架構下,如果備庫B的 seconds_behind_master還很大時,主庫A斷電下線,此時B庫還未完全重放relay log,因此無法切換A的連線到B庫上,系統會處於完全不可用的情況
所以MySQL 高可用系統的可用性,是依賴於主備延遲的。延遲的時間越小,在主庫故障的時候,服務恢復需要的時間就越短,可用性就越高
並行複製
在多執行緒模式下,sql_thread 充當 coordinator,負責讀取relay log並分發事務給worker具體執行,它在分發時要滿足以下兩個基本要求:
- 不能造成覆蓋更新,更新同一行的兩個事務必須分發到同一個worker中(可能會導致更新同一行的兩個事務在主庫和備庫上的執行順序不相同)
- 同一個事務不能拆開必須分發到同一個worker中(最終是一致的,但在過程中客戶端可能會訪問到中間結果)
讀寫分離
主要目標是為了分攤主庫的壓力,有兩種架構:客戶端直連主動做負載均衡(即前文的一主多從架構) 和 在MySQL和客戶端之間有一個proxy中間代理層,客戶端只連線proxy,由proxy根據請求型別和上下文決定請求的分發路由
客戶端直連:
- 減少proxy層轉發,查詢效能稍好
- 通常使用zookeeper負責管理後端元件的資訊
- 但客戶端需要調整資料庫的連線資訊等
proxy層轉發
- 對客戶端友好,客戶端不需要關注後端細節
- 由proxy維護連線以及後端資訊
- 但對proxy有高可用的要求,相對複雜
由於主從延遲無法100%避免,那在讀寫分離下就有可能會發生過期讀,即在從庫上讀到一個系統的過期現象,共有五種解決方案:
強制走主庫
將查詢請求做分類,對於需要拿到最新結果的請求,強制分發到主庫上;對於可以讀到舊資料的請求,才分發到從庫上
問題在於遇到“所有查詢都不能是過期讀”的情況,就必須要放棄讀寫分離,由主庫承擔所有
Sleep方案
假設大多數情況下,主備延遲在1s之內,所以在主庫更新後,讀從庫前先select sleep(1)
,讓從庫sleep一下,這樣就有很大機率拿到最新的資料
但並不精確:如果主備延遲只有0.5s,還是要等1s;如果主備延遲超過1s,依舊會發生過期讀
判斷主備無延遲方案
-
在從庫執行查詢請求前,先使用
show slave status
檢視seconds_behind_master的值,如果不等於0則等待該值等於0後執行查詢請求 -
Master_Log_File 和 Read_Master_Log_Pos,表示的是讀到的主庫的最新位點;Relay_Master_Log_File 和 Exec_Master_Log_Pos,表示的是備庫執行的最新位點
如果這兩組值完全相同,說明接收到的日誌已經同步完成
這種方案雖然比sleep方案更加精確,但仍不嚴謹,因為可能有一部分binlog日誌屬於主庫執行結束,但還未傳送給備庫的狀態,而備庫在未收到這部分日誌前可能會認為自己已經執行完relay log處於無主備延遲的階段(實際上並沒有)
配合semi-sync replication:引入半同步複製
- 在事務提交時,主庫把binlog發給從庫
- 從庫收到binlog以後,回覆給主庫一個ack,表示收到
- 主庫收到這個ack之後,才能給客戶端返回事務完成的確認
在這種模式下,就表示所有給客戶端傳送過確認的事務,都確保了備庫已經收到了這個日誌
但semi-sync + 位點判斷 的方案只針對一主一備的場景是嚴格成立的,在一主多從下主庫只要收到一個從庫的ack就會返回確認,但其他從庫並不一定都收到了這個binlog;且如果在業務高峰期位點持續變化,可能會導致從庫發生過度等待的問題(比如我查詢的是trx1的結果,但從庫因為還沒執行完trx2,就不能回覆trx1的結果)
等主庫位點方案
使用select master_pos_wait(file, pos, timeout);
會等待timeout秒,然後返回從命令開始執行到應用完file和pos表示的binlog位置時,執行了多少事務
- 如果等待事件結束,還沒有執行到file和pos表示的位點時,返回-1
- 如果剛開始執行時,發現已經執行過這個位點了,返回0
利用這個命令,可以使用等待主庫位點的方案解決半同步複製方案下過度等待的問題:
- trx1 事務更新完成後,馬上執行 show master status 得到當前主庫執行到的 File 和 Position;
- 選定一個從庫執行查詢語句;
- 在從庫上執行 select master_pos_wait(File, Position, 1);
- 如果返回值是 >=0 的正整數,則在這個從庫執行查詢語句;
- 否則,到主庫執行查詢語句