MySQL實戰-3

 

目錄

主備一致性原理

高可用原理

主從複製策略

主從機制

讀寫分離相關的問題

健康檢查的方案


 

主備一致性原理

mysql的主備切換流程

上圖就是主備切換的流程,主節點原來是A,現在切換成B
在狀態1中,雖然B沒有被直接訪問,但仍然要把備庫B設置成只讀的

  1. 有些時候一些運營類查詢會放到備庫上查詢,設置只讀可以防止誤操作
  2. 防止切換有bug,如切換中出現雙寫造成主備不一致
  3. 用readonly狀態,來判斷節點的角色

下圖是一個update語句,從A節點同步到B節點的完整流程

主庫接收到客戶端的更新請求後,執行內部事務的更新邏輯,同時寫binlog
備庫B跟主庫A之間維持了一個長連接,主庫A內部有一個線程專用用於服務備庫B的這個長連接,一個事務日誌同步的完整過程如下

  1. 在備庫B上通過change master 命令,設置主庫A的ip,端口,用戶名/密碼,以及要從哪個位置開始請求binlog,這個位置包含文件名和日誌偏移量
  2. 在備庫B上執行start slave命令,這時候備庫會啓動兩個線程,就是圖中io_thread和sql_thread,其中io_thread負責與主庫建立連接
  3. 主庫A校驗玩用戶名/密碼後,開始按照備庫B傳過來的位置,從本地讀取binlog,發給B
  4. 備庫B拿到binlog後,寫到本地文件,稱爲中專日誌relay log
  5. sql_thread讀取中轉日誌,解析出日誌裏的命令,並執行

binlog的三種格式

statement,記錄的是sql語句的原文
mysql> show binlog events in 'master.000001';

row,記錄的是記錄更新後的值(不需要記錄上下文信息)
如果一個delete刪除了1W行數據,statement只需要記錄一個語句就可以,row會記錄每一條刪除語句,需要更多的空間

mixed模式混合了row和statement的優點

數據恢復的時候,根據insert,update,delete,在row模式下直接做反向操作就可以了

用mysqlbinlog 工具來解析binlog

雙主的循環複製問題

解決辦法
1.規定兩個庫的server id必須不同,如果相同,則他們直接不能設定爲主備關係
2.一個備庫接到binlog並在重放的過程中,生成與原binlog的server id相同的新的binlog
3.每個庫在受到從自己的主庫發過來的日誌後,先判斷server id,如果跟自己的相同,表示這個日誌是自己生成的,就直接丟棄這個日誌,所以死循環在這裏就斷開了

 

高可用原理

同步延遲的概念

  1. 主庫A執行完一個事務,寫入binlog,把這個時刻記做T1
  2. 之後傳給備庫B,把備庫B接受完這個binlog的時刻記做T2
  3. 備庫B執行完這個事務,把這個時刻記做T3

主備延遲,就是同一個事務,在備庫執行完的時間和主庫執行完的時間的差值,也就是T3-T1
在備庫執行
show slave status 命令,返回
second_behind_master,用於表示當前備庫延遲了多少秒
如果主備時間不一致,會通過執行SELECT UNIX_TIMESTAMP()函數來獲取當前主庫的系統時間
網絡正常的時候,日誌從主庫傳給備庫的時間很短,即T2-T1的值非常小
網絡正常下,主備延遲主要是備庫接收完binlog和執行完這個事務之間的時間差

主備延遲的原因

  1. 備庫所在的機器性能比主庫所在的機器性能差
  2. 備庫的壓力大,如執行了一些運營查詢,多增加幾個從庫,或將binlog輸出到Hadoop系統讓外部系統提供查詢
  3. 大事務如delete大量數據,大表的DDL
  4. 備庫的並行複製能力
     

主備切換策略
可靠性優先策略,如下圖

  1. 判斷備庫B現在的seconds_behind_master,如果小於某個值(比如5秒)繼續下一步,否則持續重試這一步
  2. 把主庫A改成只讀狀態,即把readonly設置爲true
  3. 判斷備庫B的second_behind_master的值,直到這個值變爲0爲止
  4. 把備庫B改成可讀狀態,也就是把readonly設置爲false
  5. 把業務請求切到備庫B

這個切換流程一般是由專門的HA系統來完成的

可用性優先策略
如果把上面的4,5步調整到最開始執行,也就是不等主備同步,直接把連接切到備庫B,讓備庫B可以讀寫,那麼系統幾乎就沒有不可用時間了
但無論是用mix模式,還是row模式,都可能會造成數據不一致

在滿足數據可靠性的前提下,MySQL高可用系統的可用性,是依賴主備延遲的,延遲的時間越小,在主庫故障的時候,服務恢復的時間越端,可用性越高

 

主從複製策略

備庫如果執行的不如主庫快,時間一長就會延遲很多
解決辦法是採用並行複製機制
把只有一個線程的sql_thread,拆成多個線程

coordinator就是原來的sql_thread,只負責讀取中轉日誌和分發事務,真正更新日誌的是worker線程
work線程數量最好在8-16之間(32核物理機)

並行複製策略
按表分發策略
如果兩個事務更新不同的表,它就可以並行,因爲數據存儲在表裏所以按表分發,可以保證兩個worker不會更新同一行

每個work線程有個hash表,hask表的key是 庫名.表名,value是隊列中有多少事務在修改這個表
如果新來的事務T中涉及修改表t1,跟worker_1隊列衝突,也跟worker_2衝突,執行策略如下

  1. 如果跟所有的worker都不衝突,coordinator線程把這個事務分配給最空閒的worker
  2. 如果跟多餘一個worker衝突,coordinator線程就進入等待狀態,直到和這個事務存在衝突關係的worker只剩下1個
  3. 如果只跟一個worker衝突,coordinator線程就會把這個事務分配給這個存在衝突關係的worker

大多數情況下會表現很好,但如果所有事務都都涉及某個表會被分配到一個work,就變成單線程複製了

按行分發策略
事務hash表中還需要考慮唯一鍵,即key應該是 庫名+表名+索引a的名字+a的值
假設下面這個事務

coordinator在解析這個語句的binlog時,這個事務的hash表就有三項

  1. key=hash_func(db1+t1+ primary + 2),value=2是因爲修改前後的行id值不變,出現了兩次
  2. key=hash_func(db1+t1+"a"+2),value=1,會影響這個表a=2的行
  3. key=hash_func(db1+t1+"a"+1),value=1,會影響這個表a=1的行

按行分發會消耗更多的計算資源,而且有一些下限制條件

  1. 主庫的binlog格式必須是row
  2. 必須有主鍵
  3. 不能有外鍵

如果一個事務需要執行10W行,那麼coordinator會等待其他worker執行完,然後退化爲單線程模式,等執行完後再恢復成並行模式

mysql5.6採用並行複製策略,以庫做維度
MariaDB採用組提交併行復制策略

  1. 在一組裏面一起提交的事務,有一個相同的commit_id,下一組就是commit_id+1
  2. commit_id直接寫到binlog裏面
  3. 傳到備庫應用的時候,相同commit_id的事務分發到多個worker執行
  4. 這一組全部執行完後,coordinator再去取下一批

下面這三組事務是在主庫執行的,trx1,trx2,trx3提交的時候,trx4,trx5和trx6是在執行的,第一組事務提交完成的時候,下一組事務很快進入commit狀態

MariaDB並行複製策略,要等第一組事務完全執行完,第二組事務才能開始執行,這樣吞吐量會下降,尤其是大事務會拖後腿

mysql5.7的複製策略是優化了MariaDB複製策略
再回顧一下完整的兩階段提交策略


mysql5.7並行複製的策略是

  1. 同時處於prepare狀態的事務,在備庫執行時是可以並行的
  2. 處於prepare狀態的事務,與處於commit狀態的事務之間,在備庫執行時也可以並行

binlog組提交的時候,有兩個參數

  1. binlog_group_commit_sync_delay參數,多少微妙後才調用fsync
  2. binlog_group_commit_sync_no_delay_count參數, 積累多少次後才調用fsync

這兩個參數故意拉長binlog從write到fsync的時間,以此減少binlog的寫盤次數,這兩個參數可以故意讓主庫提交的慢些,也可以讓備庫執行的快一些,主從延遲的時候可以適當調整這兩個參數

mysql5.7.22支持了行模式的並行複製策略

 

主從機制

下圖是基本的一主多從的結構
虛線是主備關係,A和A'互爲主備,從庫B,C,D指向的是主庫A
一主多從的設置,一般用於讀寫分離


下圖是主庫發生故障,主備切換後的結果
一主多從切換完之後,A'就變成新的主庫,從庫B,C,D也要改連接到A'
正是多了從庫B,C,D重新指向的這個過程,所以主備切換的複雜性也增加了

把節點B設置成節點A'的從庫時,需要執行一條change master命令

CHANGE MASTER TO 
MASTER_HOST=$host_name 
MASTER_PORT=$port 
MASTER_USER=$user_name 
MASTER_PASSWORD=$password 
MASTER_LOG_FILE=$master_log_name 
MASTER_LOG_POS=$master_log_pos  

最後兩個參數 MASTER_LOG_FILE和MASTER_LOG_POS表示,要從主庫的master_log_name文件的master_log_pos這個文職的日誌繼續同步
這個位置是一個大致的位置並不精確
一種獲取方式如下

  1. 等待新主庫A’把中轉日誌relay log全部同步完
  2. 在A'上執行show master status命令,得到當前A'上最新的File和Position
  3. 取原主庫A故障時刻T
  4. 用mysqlbinglog工具解析A'的File,得到T時刻的位點

在切換任務時,要先跳過這些錯誤

  1. 主動跳過一個事務
  2. 通過slave_skip_error參數,直接設置要跳過的錯誤

GTID 是Global Transaction Identifier,也就是全局事務ID
一個事務在提交的時候生成的,格式是
GTID=server_uuid:gno
基於GTID的切換,也需要找位點,這個工作是在實例A'內部自動完成的,對HA來說是很友好的
在實例B上執行start slave取binlog的邏輯如下

  1. 實例B指定主庫A',基於主備協議建立連接
  2. 實例B把set_b發給主庫A'
  3. 實例A'算出set_a和set_b的差集,也就是鄋存在於set_a,但不存在於set_b的GTID集合,判斷A'本地是否包含了這個差集需要的所有binlog事務,如果不包含直接返回錯誤,如果確認全部包含,A'把自己的binlog文件裏面找出第一個不在set_b的事務發給B
  4. 之後就從這個事務開始,往後讀文件,按順序取binlog發給B去執行
     

 

讀寫分離相關的問題

讀寫分離可以用 客戶端直連 或者 proxy方式,他們的對比

  1. 客戶端少了一層轉發性能更好,但需要感知後端架構,一般會搭配zookeeper使用
  2. 使客戶端無須關心後端細節,但對團隊要求比較高,而且proxy也有高可用架構

目前的趨勢是向proxy架構方向發展
無論採用哪種架構,都會碰到讀寫延遲,客戶端再一個更新事務後馬上發起查詢,如果查詢選擇的是從庫就有可能讀到剛更新之前的狀態,這種現象被稱爲 過期讀
目前的解決方案包括

  1. 強制走主庫方案
  2. sleep方案
  3. 判斷主備無延遲方案
  4. 配合semi-sync方案
  5. 等主庫位點方案
  6. 等GTID方案


強制走主庫
對於可以讀到舊數據的請求,繼續讀從庫,對於必須拿最新結果的請求就走主庫
但如果是金融業務,所有的請求必須是最新的,就等於是放棄讀寫分離方案了

sleep方案
主庫更新後,讀從庫之前先sleep 1秒
這種方案簡單,但是時間很難確定,不知道1秒是長了還是短了
另外可以配合AJAX提升用戶體驗

判斷主備無延遲方案
show slave status的結果裏
seconds_behind_master參數的值,可以用來衡量主備延遲時間的長短
對於每次查詢先判斷 seconds_behind_master如果不等於0就得先等這個參數爲0再查詢
這個參數的單位是秒不夠精確
還有其他兩種方式

對比位點
master_log_file和read_master_log_pos 表示讀到的主庫的最新位點
relay_master_log_file和exec_master_log_pos,表示備庫執行的最新位點
如果第一組兩個參數的值,第二組兩個參數的值一樣,比較接收到的日誌已經同步完成

對比GTID集合
retrieved_gtid_set 是備庫收到所有日誌的GTID集合
executed_gtid_set  是備庫所有已經執行完成的GTID集合

判斷主備延遲的方案尤其是對比位點會很精確,但如果一個事務剛在主庫commit,還沒發送給從庫,那麼判斷主備延遲就不管用了,這樣仍會讀到過期數據

配合semi-sync
這個是半同步複製,機制如下

  1. 事務提交的時候,主庫把binlog發給從庫
  2. 從庫收到binlog後,發給主庫一個ack表示成功
  3. 主庫收到這個ack後,才能給客戶端返回事務完成確認

如果主庫掉電的時候有些binlog還來不及發給從庫,就有可能導致數據丟失,如果是semi-sync方案就不會
半同步複製方案的缺點是
如果查詢落在響應ack的從庫是能讀到最新數據
如果落在其他從庫,則仍可能讀到過期數據
如果是業務高峯,主庫位點或GTID更新很快,位點等值判斷會一直不成立,可能出現從庫一直無法響應,過度等待的問題


等主庫位點方案
使用下面命令

select master_pos_wait(file, pos[, timeout]);

執行方式如下

  1. 事務更新完成後馬上執行show master status得到當前主庫執行的file和position
  2. 選擇一個從庫執行查詢語句
  3. 在從庫上執行 select master_pos_wait(file,position,1)
  4. 如果返回的是>=0的正整數,則在這個從庫執行查詢
  5. 如果小於0則表示超時,則在主庫執行

最壞的結果是所有查詢都超時1秒然後落在主庫上執行

GTID方案
跟等主庫位點方案類似,也有一個查詢

select wait_for_executed_gtid_set(gtid_set, 1);

執行流程如下

  1. 事務更新完後,從返回包直接獲取這個事務的GTID
  2. 選擇一個從庫執行查詢語句
  3. 在從庫上執行select wait_for_executed_gtid_set(gtid,1)
  4. 如果返回值是0,則在這個從庫執行查詢
  5. 否則到主庫上執行查詢

實際使用中,是幾個方案混合用的
先對客戶端做分類,哪些請求可以接受過期讀,哪些不可以
對於不能接受過期的請求,再使用GTID或者等位點的方案
如果極端情況所有GTID查詢後都失敗,再查詢主庫,這樣主庫壓力仍會非常大,需要做限流處理

 

健康檢查的方案

select 1 方案
考慮到下面這個問題,將innodb_thread_concurrrency設置爲3,這個是併發線程的上限
此時三個事務都在卡住了,第四個任務做普通的查詢會被卡住,但select 1 就不會有問題

連接可能有很多,但併發查詢必須設置一個上限,比如128,這個是參數 innodb_thread_concurrency來控制的
進入等待隊列後,併發線程會減1,也就是等行鎖不算在128個併發線程裏面

查表判斷
增加一個健康檢查表

mysql> select * from mysql.health_check; 

使用這個方案,可以檢測出併發線程多多導致的數據庫不可用情況
但存儲空間滿了的話,這種普通查詢是檢查不出來的

更新判斷
把上面那個查詢判斷改成查詢

mysql> update mysql.health_check set t_modified=now();

如果是主庫的雙M設計,這個表就不對了,必須增加server_id做主鍵

mysql> CREATE TABLE `health_check` (
  `id` int(11) NOT NULL,
  `t_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

/* 檢測命令 */
insert into mysql.health_check(id, t_modified) values (@@server_id, now()) on duplicate key update t_modified=now();

以上還是有 判斷慢的問題
如果IO利用率是100%表示系統系統已經滿負荷了,簡單的update需要的資源少就能拿到IO資源,於是返回系統正常
但其他的請求可能就不行了
而且這種情況可能需要好幾輪才能檢測出來

內部統計方案
mysql5.6之後提供了performance_schema庫
在 file_sunnmary_by_event_name 表裏統計了每次IO請求的時間

圖中這一行表示redo log的寫入時間,第一列EVENT_NAME表示統計的類型
然後是IO類型統計(sun,min,avg等),再是讀,寫操作統計
與binlog對應的是event_name="wait/io/file/sql/binlog" 這一行

打開redo log監控

mysql> update setup_instruments set ENABLED='YES', Timed='YES' where name like '%wait/io/file/innodb/innodb_log_file%';

下面語句用來做監控

mysql> select event_name,MAX_TIMER_WAIT  FROM performance_schema.file_summary_by_event_name where event_name in ('wait/io/file/innodb/innodb_log_file','wait/io/file/sql/binlog') and MAX_TIMER_WAIT>200*1000000000;

發現異常後,清空信息

mysql> truncate table performance_schema.file_summary_by_event_name;

如果後面的監控中,再次出現這個異常,就可以加入監控累積值了
select 1 這個方案最簡單,雖然不是很準,但使用的很多
每個改進方案,都會增加額外損耗,所以需要根據實際情況做權衡
推薦使用update系統表,再加上performance_schema的信息

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章