MySQL InnoDB Cluster – how to manage a split-brain situation

潇湘隐者發表於2024-08-13

本文是翻譯MySQL InnoDB Cluster – how to manage a split-brain situation[1]這篇文章,如有翻譯不妥或不對的地方,敬請諒解與指正。請尊重原創和翻譯勞動成果,轉載的時候請註明出處。謝謝!

每次我展示MySQL InnoDB Cluster時,在建立叢集的演示中,很多人都不明白為什麼當我叢集中已有2個成員時,我的叢集還不能容忍任何故障。 事實上,當您建立MySQL InnoDB叢集時,只要您新增了第二個例項,您就可以看到狀態資訊:

    "status": "OK_NO_TOLERANCE",      
"statusText": "Cluster is NOT tolerant to any failures.",

仲裁(Quorum)

[譯者註釋] 這裡Quorum翻譯成仲裁,直譯是法定人數。個人認為翻譯成仲裁更好一些。

這是為什麼呢?這是因為,要成為網路主分割槽(網路主分割槽指包含服務的網路分割槽,在預設的單主模式中指具有主節點的網路分割槽)的一部分,您的網路分割槽必須達到大多數節點(法定人數)。在MySQL InnoDB叢集(和許多其他叢集解決方案一樣)中,要達到法定人數,分割槽中的成員數量必須大於50%。

[譯者註釋]:這裡所謂的網路主分割槽(primary partition),其實是指叢集中的節點可能由於網路故障導致變成了2個或多個隔離區域。是一個網路拓撲概念。

因此,當我們已有2個節點時,如果兩個伺服器之間出現網路問題,叢集將分裂為2個分割槽。每個分割槽將擁有總成員數量的 50%(2 箇中的1個)。那麼50% > 50% 嗎?不!在MySQL InnoDB叢集的環境下,沒有一個網路分割槽會達到法定人數,也沒有一個網路分割槽允許查詢。這就是原因。

事實上,第一臺伺服器會發現它再也無法與第二臺伺服器通訊了……但為什麼呢?是第二臺伺服器當機了嗎?是不是網路介面出了問題?我們不知道,所以我們無法決定。

讓我們看一下這個由3名成員組成的叢集 (3/3 = 100%):

如果我們看一下cluster.status()命令的輸出資訊,我們可以看到,有3個節點時可以容忍一個節點出現故障:

"status": "OK",      
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",

現在讓我們想象一下,我們遇到一個網路問題,它會隔離其中一個成員/節點:

我們可以在cluster.status()命令的輸出資訊中看到節點丟失: 只要一個分割槽仍然具有法定人數(2/3 = 66%,大於 50%),我們的叢集就仍然能夠提供交易服務。

    "mysql6:3306": {
"address": "mysql6:3306",
"mode": "n/a",
"readReplicas": {},
"role": "HA",
"status": "(MISSING)"
}

這裡我想介紹一個非常重要的概念,因為這並不總是顯而易見的。InnoDB Cluster 和 Group Replication 中的叢集概念是不同的。實際上,InnoDB Cluster 依賴於DBA使用 MySQL Shell建立的後設資料。這些後設資料描述了叢集的設定方式。Group Replication 以不同的方式看待叢集。它以上次檢查時的狀態以及現在的狀態來看待叢集……並更新該檢視。這通常被稱為世界觀(view of the world)。 因此,在上面的示例中,InnoDB 叢集看到 3 個節點:2 個線上,1 個丟失。對於組複製,在短時間內,網路分割槽中節點處於UNREACHABLE狀態,幾秒鐘後,在被多數人從組中逐出後(因此只有在仍有多數人的情況下),該節點不再是叢集的一部分。組大小現在是 2/2(2/2 而不是 2/3)。此資訊可以透過performance_schema.replication_group_members獲取。

如果我們的網路問題更加嚴重,並將我們的叢集分成 3 個,如下圖所示,那麼叢集將處於“離線”狀態,因為這 3 個分割槽均未達到法定多數,即 1/3 = 33% (<50%):

在這種情況下,MySQL 服務將無法正常工作,直到人工修復該問題。

解決問題

當叢集中不再有網路主分割槽時(如上例所示),DBA 需要恢復服務。和往常一樣,MySQL 錯誤日誌中已經有一些資訊:

2019-04-10T13:34:09.051391Z 0 [Warning] [MY-011493] [Repl] Plugin group_replication 
reported: 'Member with address mysql4:3306 has become unreachable.'
2019-04-10T13:34:09.065598Z 0 [Warning] [MY-011493] [Repl] Plugin group_replication
reported: 'Member with address mysql5:3306 has become unreachable.'
2019-04-10T13:34:09.065615Z 0 [ERROR] [MY-011495] [Repl] Plugin group_replication
reported: 'This server is not able to reach a majority of members in
the group. This server will now block all updates. The server will
remain blocked until contact with the majority is restored. It is
possible to use group_replication_force_members to force a new group
membership.

從訊息中我們可以看到這正是我們在這裡解釋的情況/場景。我們可以從命令cluster.status()的輸出資訊看到叢集被“阻止”了:

"status": "NO_QUORUM",      
"statusText": "Cluster has no quorum as visible from 'mysql4:3306'
and cannot process write transactions.
2 members are not active"
,

我們有兩個解決方案來解決這個問題:

  1. 使用SQL和組複製變數
  2. 使用 MySQL Shell 的 adminAPI

使用SQL和組複製變數進行修復

手冊[2]中解釋了這個過程(組複製:網路分割槽)。 在DBA想要用來恢復服務的節點上,如果只剩下一個節點,我們可以使用全域性變數group_replication_force_members,並使用您可以在group_replication_local_address找到的伺服器的GCS地址(如果有多個伺服器線上但未達到大多數,則應將所有伺服器新增到此變數中):

set global group_replication_force_members=@@group_replication_local_address;

請注意,最佳做法是關閉其他節點,以避免在強制仲裁過程中再次出現任何型別的衝突。

叢集將再次可用。我們可以在錯誤日誌中看到情況已解決:

2019-04-10T14:41:15.232078Z 0 [Warning] [MY-011498] [Repl] Plugin group_replication 
reported:
'The member has resumed contact with a majority of the members in the group.
Regular operation is restored and transactions are unblocked.'

當節點上線後不要忘記刪除變數group_replication_force_members的值

set global group_replication_force_members='';

當網路問題解決後,節點將嘗試重新連線,但由於我們強制成員身份/資格,這些節點將被拒絕。您需要透過以下方式將其重新加入群組:

  • 重新啟動 mysqld服務
  • 重新啟動組複製(stop group_replication; start group_replication)
  • 使用 MySQL Shell ( cluster.rejoinInstance())

使用 MySQL Shell的adminAPI

另一個選項是使用MySQL Shell的adminAPI。這當然是更好的選擇!使用 AdminAPI,您甚至不需要知道用於 GCS 的埠即可恢復仲裁。 在下面的示例中,我們將使用名為mysql4的伺服器來重新啟用我們的叢集:

cluster.forceQuorumUsingPartitionOf('clusteradmin@mysql4') 

並且當網路問題解決後,Shell 還可以用於重新加入其他例項(在本例中為mysql6):

cluster.rejoinInstance('clusteradmin@mysql6') 

結論

如果您遇到任何原因在 MySQL InnoDB 群集上失去仲裁的情況,請不要驚慌。選擇要使用的節點(或仍可相互通訊的節點列表),如果可以的話,請關閉或停止其他節點上的mysqld服務。然後 MySQL Shell再次幫助你,你可以使用 adminAPI 強制仲裁併用一個命令中重新啟用您的群集!

Bonus

如果您想知道您的 MySQL 伺服器是否屬於網路主分割槽(佔多數的分割槽),您可以執行以下命令:

mysql> SELECT IF( MEMBER_STATE='ONLINE' AND ((
SELECT COUNT(*) FROM performance_schema.replication_group_members
WHERE MEMBER_STATE NOT IN ('ONLINE', 'RECOVERING')) >=
((SELECT COUNT(*)
FROM performance_schema.replication_group_members)/2) = 0), 'YES', 'NO' )
` in primary partition`
FROM performance_schema.replication_group_members
JOIN performance_schema.replication_group_member_stats
USING(member_id) where member_id=@@global.server_uuid;
+----------------------+
| in primary partition |
+----------------------+
| NO |
+----------------------+

或者使用addition_to_sys_GR.sql這個SQL來確認。

USE sys;

DELIMITER $$

CREATE FUNCTION my_id() RETURNS TEXT(36) DETERMINISTIC NO SQL RETURN (SELECT @@global.server_uuid as my_id);$$

-- new function, contribution from Bruce DeFrang
CREATE FUNCTION gr_member_in_primary_partition()
RETURNS VARCHAR(3)
DETERMINISTIC
BEGIN
RETURN (SELECT IF( MEMBER_STATE='ONLINE' AND ((SELECT COUNT(*) FROM
performance_schema.replication_group_members WHERE MEMBER_STATE NOT IN ('ONLINE', 'RECOVERING')) >=
((SELECT COUNT(*) FROM performance_schema.replication_group_members)/2) = 0),
'YES', 'NO' ) FROM performance_schema.replication_group_members JOIN
performance_schema.replication_group_member_stats USING(member_id) where member_id=my_id());
END$$

CREATE VIEW gr_member_routing_candidate_status AS SELECT
sys.gr_member_in_primary_partition() as viable_candidate,
IF( (SELECT (SELECT GROUP_CONCAT(variable_value) FROM
performance_schema.global_variables WHERE variable_name IN ('read_only',
'super_read_only')) != 'OFF,OFF'), 'YES', 'NO') as read_only,
Count_Transactions_Remote_In_Applier_Queue as transactions_behind, Count_Transactions_in_queue as 'transactions_to_cert'
from performance_schema.replication_group_member_stats where member_id=my_id();$$

DELIMITER ;


SQL>select gr_member_in_primary_partition();
+----------------------------------+
| gr_member_in_primary_partition() |
+----------------------------------+
| YES |
+----------------------------------+
1 row in set (0.0288 sec)
參考資料
[1]

1: https://lefred.be/content/mysql-innodb-cluster-how-to-manage-a-split-brain-situation/

[2]

2: https://dev.mysql.com/doc/refman/8.0/en/group-replication-network-partitioning.html

相關文章