之前設計的切換邏輯
1 查詢slve的延遲情況,超過N秒延遲則等待或者返回失敗,確保業務影響時間最短
2 登陸proxy節點,disable當前hproxy,使得後續通過proxy的業務連接失敗
3 登陸proxy節點,shutdown當前通過proxy連接的會話(如果sql能快速完成,這步其實可用不做,可以做個時間閾值檢測,當N秒以後還有業務層連接則kill)
4 記錄當前主庫的binlog和pos點
5 等待雙主間的數據完全一致,即從庫執行到步驟4記錄的pos點後,可以利用MASTER_POS_WAIT(log_name,log_pos[,timeout])內置函數實現
6 執行切換,即enable M-M中的備用節點
7 返回切換成功
故障現象
切換以後,show slave status出現大量的主鍵衝突報錯
原因分析
通過分析,發現通過haproxy來殺死會話實際上並不靠譜,當連接建立以後,即使通過haproxy殺死會話或者殺死mysql client所在的客戶端進程,實際上沒有效果,依舊會往下繼續執行成功,如果此時主庫insert了記錄爲10的自增id沒有完成
當新的主庫連接進來後,當時的慢的sql還沒跑完,即自增id爲10的記錄還沒同步過來
這時新主庫再次insert一條記錄,產生的自增id依舊是10
慢SQL這時在主庫執行完成,傳到新主庫後主鍵衝突,報錯
場景模擬
1 通過proxy登陸mysql,執行以下命令
mysql> select * from test_ha_switch;
Empty set (0.00 sec)
mysql> insert into test_ha_switch(dd) values (sleep(100));
此時SQL處於運行中
2 登陸proxy,通過socat殺死會話
echo "shutdown sessions server ha-proxy/10.9.188.208" | socat stdio /opt/udb/instance/haproxy/7378229c-7ada-4f90-b1ec-96d704979426/stats
3 此時再回去看步驟1中的會話,發現已經是處於ERROR 2013 (HY000): Lost connection to MySQL server during query
mysql> insert into test_ha_switch(dd) values (sleep(100));
ERROR 2013 (HY000): Lost connection to MySQL server during query
4 再次查詢該表,發現記錄依舊寫進去了
mysql> select * from test_ha_switch;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 1536352
Current database: jiang
+----+------+
| id | dd |
+----+------+
| 1 | 0 |
+----+------+
1 row in set (0.01 sec)
另外如果沒有proxy,通過殺死客戶端所在的進程的方式也一樣很好模擬
# mysql -S /opt/udb/instance/mysql-5.6/5770e236-fef6-4d61-bc61-beb6d69f7dbe/mysqld.sock -uucloudbackup jiang -e "show processlist;"
+---------+--------------+-------------------+-------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+---------+--------------+-------------------+-------+---------+------+-------+------------------+
| 1536517 | ucloudbackup | 10.9.125.20:62374 | jiang | Sleep | 7 | | NULL |
| 1536566 | ucloudbackup | localhost | jiang | Query | 0 | init | show processlist |
+---------+--------------+-------------------+-------+---------+------+-------+------------------+
# nohup mysql -S /opt/udb/instance/mysql-5.6/5770e236-fef6-4d61-bc61-beb6d69f7dbe/mysqld.sock -uucloudbackup jiang -e "insert into test_ha_switch(dd) select dd from test_ha_switch;" &
[1] 28752
# nohup: ignoring input and appending output to `nohup.out'
# ps -ef|grep test_ha_switch
root 28752 17668 0 18:10 pts/0 00:00:00 mysql -S /opt/udb/instance/mysql-5.6/5770e236-fef6-4d61-bc61-beb6d69f7dbe/mysqld.sock -uucloudbackup -px xxxxxxxx jiang -e insert into test_ha_switch(dd) select dd from test_ha_switch;
root 29117 17668 0 18:10 pts/0 00:00:00 grep test_ha_switch
# kill -9 28752
[1]+ Killed nohup mysql -S /opt/udb/instance/mysql-5.6/5770e236-fef6-4d61-bc61-beb6d69f7dbe/mysqld.sock -uucloudbackup jiang -e "insert into test_ha_switch(dd) select dd from test_ha_switch;"
# mysql -S /opt/udb/instance/mysql-5.6/5770e236-fef6-4d61-bc61-beb6d69f7dbe/mysqld.sock -uucloudbackup -pJfKS9FFXvk jiang -e "show processlist;"
+---------+--------------+-------------------+-------+---------+------+--------------+--------------------------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+---------+--------------+-------------------+-------+---------+------+--------------+--------------------------------------------------------------+
| 1536517 | ucloudbackup | 10.9.125.20:62374 | jiang | Sleep | 29 | | NULL |
| 1536570 | ucloudbackup | localhost | jiang | Query | 16 | Sending data | insert into test_ha_switch(dd) select dd from test_ha_switch |
| 1536575 | ucloudbackup | localhost | jiang | Query | 0 | init | show processlist |
+---------+--------------+-------------------+-------+---------+------+--------------+--------------------------------------------------------------+
優化方法
優化方法其實也比較簡單,就是在切換邏輯的3和4之間,通過直接登陸主節點的方式(這時已經無法通過proxy登陸db了),如果當前還有業務連接,則通過mysql kill語法kill掉當前的業務連接即可;
值得一提的是,kill線程時要確保正常退出,可能會存在這種情況,查看的時候還有兩個連接,你kill之前第一個連接已經斷開了,那麼你再去kill這個線程程序可能就會報錯退出,從而第二個線程實際上都沒有kill掉