MySQL高可用-MGR運維常見問題和注意事項

MySQL 的高可用可以選擇 MGR 方案,部署 MGR 很簡單,可以參考《MySQL高可用-使用Docker部署MGR》,但在運行過程中,如果出現(xiàn)問題,如何快速解決?需要持續(xù)學習和實踐。
下面我以實際用到的和我搜集到的一些資料來說說 MGR 在運維過程中的常見問題和注意事項。
部署前注意事項
硬件和環(huán)境要求
1、網絡要求。
- 低延遲網絡:MGR 對網絡延遲敏感,建議延遲 < 5ms 。因為 MGR 基于 Paxos 變種的共識算法,每提交一次事務至少跨網兩次(prepare>ack>commit)。
- 穩(wěn)定帶寬:確保有足夠的帶寬支持數據同步,建議 ≥ 1Gbps 。全量 recovery、大事務或 DDL 時會產生突發(fā)流量,帶寬不足會導致 flow-control 觸發(fā),集群整體降速
2、服務器配置。
- CPU:建議多核 CPU,至少4核以上
- 內存:充足內存,建議 ≥ 16GB
- 存儲:使用 SSD 存儲,確保足夠 IOPS,考慮使用 RAID 10 而非 RAID 5/6,以獲得更好的寫性能
- 時鐘同步:所有節(jié)點必須時鐘同步(NTP)。MGR 的 GTID、view_change 事件都帶時間戳,時間漂移會導致 “member expel” 誤判。誤判會導致節(jié)點是正常的,但會被踢出去。
- 分離數據和日志:將二進制日志和數據文件放在不同的物理存儲上,參數大致如下:
datadir = /data/mysql
log_bin = /binlog/mysql-bin
binlog_cache_size = 1M
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1操作系統(tǒng)優(yōu)化
1、文件描述符限制。
# 文件描述符限制
echo "mysql soft nofile 65536" >> /etc/security/limits.conf
echo "mysql hard nofile 65536" >> /etc/security/limits.confMGR + InnoDB 打開的文件數 = 表數量 × 分區(qū) × 3(ibd、frm、ibtmp)+ binlog + relay log。文件描述限制如果比較小,操作系統(tǒng)層面的文件描述符(fd)耗盡,后果是 mysqld 再想去 open() 任何文件(包括新的 ibd、binlog、relay-log、tmp-file、socket 等)時都會得到 EMFILE,導致各種莫名奇妙的錯誤。
可以使用下面語句進行檢查:
ulimit -n # 當前會話
cat /proc/$(pidof mysqld)/limits | grep files我在 CentOS 服務器上執(zhí)行 ulimit -n 得到的結果是 1024 ,但在 MySQL 容器中的結果是 1048676 。
2、內核參數調優(yōu)。
# 內核參數調優(yōu)
echo "net.core.rmem_max = 134217728" >> /etc/sysctl.conf
echo "net.core.wmem_max = 134217728" >> /etc/sysctl.conf
sysctl -pLinux 默認的 socket 緩沖區(qū)只有 128 KB,跨機房或云環(huán)境突發(fā)流量會觸發(fā) TCP 丟包重傳,將該參數調大可降低重傳率,提高吞吐。
可以通過 sysctl net.core.rmem_max net.core.wmem_max 進行檢查。
3、內存優(yōu)化。
# 內存優(yōu)化
SET GLOBAL innodb_buffer_pool_size = 32*1024*1024*1024;
# 注釋掉 /etc/fstab 里的 swap 行
sed -i '/swap/s/^/#/' /etc/fstab- 確保
innodb_buffer_pool_size設置合理,通常為服務器內存的 50-75% - 確保系統(tǒng)不會使用交換空間,否則可能導致嚴重的性能下降。
監(jiān)控
查看集成成員
SELECT
MEMBER_ID AS node_uuid,
MEMBER_HOST AS host,
MEMBER_PORT AS port,
MEMBER_STATE AS state, -- ONLINE / RECOVERING / ERROR / OFFLINE
MEMBER_ROLE AS role -- PRIMARY / SECONDARY
FROM performance_schema.replication_group_members
ORDER BY MEMBER_HOST;- state ≠ ONLINE:立刻報警。
- role = PRIMARY:表示為主庫。
復制延遲/事務堆積(只看當前節(jié)點)
SELECT
MEMBER_ID AS my_uuid,
COUNT_TRANSACTIONS_IN_QUEUE AS queue_txn, -- >0 表示延遲
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE AS relay_txn, -- 未應用完的 relay log 事務
TRANSACTIONS_COMMITTED_ALL_MEMBERS AS cluster_lsn, -- 全局已提交位點
LAST_CONFLICT_FREE_TRANSACTION AS last_no_conflict_txn
FROM performance_schema.replication_group_member_stats
WHERE MEMBER_ID = @@server_uuid;根據經驗進行判斷:
- queue_txn > 1000 或持續(xù)增長:延遲告警。
- relay_txn > 1000 表示回放慢:需檢查 applier 線程、IO 能力。
queue_txn 是其它節(jié)點已經提交、通過 Paxos 共識后廣播給本節(jié)點,但本節(jié)點還沒開始的事務數量。
正常情況下,事務來了立即被 applier 線程消耗,queue_txn 會瞬間降到 0。queue_txn 比較大說明有堵塞。
回放慢意思是當前節(jié)點的 applier 線程來不及重放遠程事務,導致數據滯后。
applier 線程是什么呢 ?
在 MGR 里,可以把 applier 線程理解為真正把遠程事務寫進本地 InnoDB 的工人。檢查 applier 線程、IO 能力其實就是確認:
- 工人夠不夠。
- 工人有沒有被磁盤 IO 卡住。
- 工人有沒有報錯/死鎖。
先看有幾個工人(applier 線程)。
SELECT THREAD_ID, NAME, PROCESSLIST_STATE
FROM performance_schema.threads
WHERE NAME LIKE '%group_rpl%applier%';如果 PROCESSLIST_STATE 長期是 Waiting for disk space 或 Waiting for table flush,說明被 IO 堵住。
看工人是不是在慢吞吞地寫。
SELECT
EVENT_NAME,
SUM_TIMER_WAIT/1e9 AS total_seconds,
MAX_TIMER_WAIT/1e9 AS max_seconds
FROM performance_schema.events_waits_summary_by_thread_by_event_name
JOIN performance_schema.threads USING(THREAD_ID)
WHERE NAME LIKE '%applier%'
AND EVENT_NAME LIKE '%io%file%';total_seconds 很大說明 applier 線程在磁盤 IO 上耗掉了大量時間。
看工人有沒有報錯。
SELECT
WORKER_ID,
LAST_ERROR_NUMBER,
LAST_ERROR_MESSAGE,
SERVICE_STATE -- 顯示線程是 ON / OFF
FROM performance_schema.replication_applier_status_by_worker
WHERE LAST_ERROR_NUMBER <> 0;只要這條 SQL 返回了任何一行,就說明至少有一個 applier 線程曾經或正在報錯,需要立即處理。
常見故障及排查
腦裂問題
正常情況下同一時間只能有一個節(jié)點是主節(jié)點,由于網絡分區(qū)、參數配置等原因導致出現(xiàn)多個主節(jié)點,這就是腦裂。
癥狀識別
-- 檢查是否存在多個PRIMARY
SELECT MEMBER_HOST, MEMBER_STATE, MEMBER_ROLE
FROM performance_schema.replication_group_members
WHERE MEMBER_ROLE = 'PRIMARY';在一次網絡分區(qū)后,節(jié)點可能會分成兩派:
- 多數派:仍然能夠湊齊 >50 % 組內成員的那一邊。例如 5 節(jié)點集群,有 3 臺還是 ONLINE 狀態(tài),這一邊就是多數派。只有多數派才能繼續(xù)對外提供寫服務,并且它們的投票結果才會被 MGR 認可。
- 少數派:剩下的 ≤50 % 節(jié)點。這一側因為湊不夠“法定人數”,自動變?yōu)橹蛔x或離線,不再接受寫請求。
解決方案
1、立即隔離寫流量,把應用 VIP、proxy、DNS 指向全部下線,避免繼續(xù)寫。
2、快速定位合法主節(jié)點,找到擁有最新 GTID 集合且處于多數派的節(jié)點:
SELECT @@global.gtid_executed;- 在多數派中所有 ONLINE 節(jié)點上比較,選出 GTID 最大的那一個
- 如果兩邊 GTID 相同,就保留原 PRIMARY;如果不同,以多數派里最新的為準。
3、多數派中的節(jié)點什么都不用做,不需要重啟,不需要 bootstrap,保持 ONLINE 即可。
4、處理少數派節(jié)點,對節(jié)點進行下面操作:
STOP GROUP_REPLICATION;
RESET SLAVE ALL; -- 清掉舊的通道
SET GLOBAL gtid_purged = ''; -- 如果確定這些節(jié)點落后很多
START GROUP_REPLICATION; -- 讓它重新加入并自動追趕- SET GLOBAL gtid_purged = '' 需要非常謹慎使用。這會清除節(jié)點的 GTID 執(zhí)行歷史,只有在確定節(jié)點數據已嚴重落后且準備重新同步全部數據時才應使用。在大多數情況下,不建議這樣做,因為這可能導致數據丟失。更安全的做法是保留 GTID 歷史,讓節(jié)點基于現(xiàn)有數據追趕,或者如果數據差異太大,先備份后重建節(jié)點。
5、等所有節(jié)點回到 ONLINE 后,執(zhí)行下面語句:
SELECT MEMBER_ID, MEMBER_STATE, MEMBER_ROLE
FROM performance_schema.replication_group_members;確保只有 1 個 PRIMARY,且所有節(jié)點 GTID 完全一致。
節(jié)點無法加入集群
常見原因
- GTID 不一致
- 網絡連接問題
- 版本不兼容
- 配置錯誤
排查步驟
1、檢查 GTID 狀態(tài)。
SHOW GLOBAL VARIABLES LIKE 'gtid%';
SELECT @@GLOBAL.GTID_EXECUTED;2、檢查網絡。
# 在故障節(jié)點上,對任一 ONLINE 節(jié)點測試
telnet ONLINE_IP 33061 # group_replication_local_address 的端口-- 在問題節(jié)點上測試
SELECT * FROM performance_schema.replication_connection_status;- CHANNEL_NAME:
group_replication_recovery(分布式恢復通道)和group_replication_applier(正常應用通道) - SERVICE_STATE:ON = 連接正常;OFF = 連接斷開;CONNECTING = 正在握手/重連。
- LAST_ERROR_NUMBER / LAST_ERROR_MESSAGE:最近一次的 MySQL 錯誤號與文字描述。非 0 表示有問題。
- RECEIVED_TRANSACTION_SET:當前節(jié)點已經從集群收到的 GTID 集合,可與
@@global.gtid_executed對比判斷落后多少。 - COUNT_RECEIVED_HEARTBEATS:心跳計數,持續(xù)增加說明網絡通;長時間不動可能丟包。
- LAST_HEARTBEAT_TIMESTAMP:最后心跳時間,離當前時間越近越健康。
3、檢查錯誤日志。
SHOW GLOBAL VARIABLES LIKE 'log_error';解決方案
修復問題也需要分場景:
1、問題節(jié)點 GTID 落后,直接 START GROUP_REPLICATION; 即可自動追平,無需 RESET。
2、問題節(jié)點 GTID 超前,不執(zhí)行 RESET MASTER ,而是把多出來的事務導出、在主庫重放、再讓節(jié)點重新加入。
3、問題節(jié)點 GTID 分叉,備份本節(jié)點、做差異校驗、選擇保留哪份數據。
4、GTID 為空,用克隆插件或全量備份 + CHANGE REPLICATION SOURCE 重建數據,再啟動 MGR,不要手工 SET GTID_PURGED 。
怎么判斷 GTID 是否落后還是超前?
1、在任意一個正常的 ONLINE 節(jié)點查看集群最新 GTID。
SELECT @@GLOBAL.GTID_EXECUTED;
-- 結果:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-52、在故障節(jié)點查看 GTID。
SELECT @@GLOBAL.GTID_EXECUTED;
-- 結果:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-3 表示落后
-- 結果:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-7 表示超前






















