目錄
一、配置組複製模式
組複製可以以單主模式或多主模式運行,缺省採用單主模式。單主模式中只有一個可以讀寫的服務器,其它服務器只讀。多主模式中,所有服務器均可讀寫。無論部署模式如何,組複製都不處理客戶端故障轉移,而必須由應用程序本身、連接器或中間件(如MySQL router)處理此問題。
1. 單主模式
在此模式下,組中具有單一可讀寫的主服務器,通常是第一個引導組的服務器,所有其它成員都爲只讀。這種設置自動發生。當主服務器失敗時,會自動選擇新的主服務器,如圖1所示。
選擇哪個服務器作爲新主庫由group_replication_member_weight系統變量控制,該變量值最高的成員將被選爲新主庫。如果多個服務器具有相同的group_replication_member_weight值,則組複製將根據按字典順序排列的server_uuid,優先選擇第一個在線服務器作爲新主庫。新主庫將自動設置爲讀寫,其它服務器仍爲從庫,因此爲只讀。
新主庫只有在處理了來自舊主庫的所有事務後纔可寫,這避免了在新主庫上並行執行新老事務的問題。將客戶端應用程序重新路由到新主庫之前,等待新主服務器應用其複製相關的中繼日誌是一種很好的做法。
如果組成員的MySQL版本不同,選舉新主庫的過程可能會受到影響。例如,如果有成員不支持group_replication_member_weight,則根據較低主版本成員的server_uuid順序選擇主庫;如果運行不同MySQL版本的所有成員都支持group_replication_member_weight,則根據較低主版本成員的group_replication_member_weight選擇主庫。
主服務器可由下面查詢確認:
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | SECONDARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | PRIMARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | SECONDARY |
+--------------------------------------+-------------+-------------+
3 rows in set (0.00 sec)
2. 多主模式
多主模式中所有服務器角色等價,各服務器都能讀寫。客戶端故障轉移如圖2所示。
多主模式下部署組複製時,將進行以下檢查:
- 如果事務在SERIALIZABLE隔離級別下執行,則在與組同步時其提交失敗。
- 如果事務在具有級聯外鍵約束的表上執行,則在與組同步時事務無法提交。
通過將選項group_replication_enforce_update_everywhere_checks設置爲FALSE,可以禁用這些檢查。單主模式下此選項必須設置爲FALSE。
3. 聯機配置組複製模式
可以使用一組依賴於組操作協調器的函數在組複製運行時聯機配置組,這些函數由版本8.0.13及更高版本中的組複製插件提供。爲使協調器能夠在正在運行的組上進行配置,所有成員必須MySQL 8.0.13或更高版本。
配置整個組時,操作的分佈式性質意味着它們與組複製插件的許多進程交互,因此需要注意以下幾點:
- 可以在任何服務器發佈配置操作,所有操作都以協調的方式發送到所有組成員上執行。如果調用成員宕機,任何已在運行的配置過程繼續在其它成員上運行。
- 配置更改期間,任何成員都無法加入組,在協調配置更改期間嘗試加入組的任何成員將離開該組並取消其加入過程。
- 一次只能執行一個配置。正在執行配置更改的組不能接受任何其它組配置更改,因爲併發配置操作可能導致成員分歧。
- 不能在混合版本組上使用此配置功能。由於這些配置操作的分佈式特性,所有成員必須識別它們才能執行。因此,組中不能存在舊版本的服務器,否則操作被拒絕。
可以在任何成員上運行函數。操作運行時可以通過發出以下命令來檢查其進度:
select event_name, work_completed, work_estimated from performance_schema.events_stages_current where event_name like "%stage/group_rpl%";
(1)更改主服務器
使用group_replication_set_as_primary() 函數更改單主組中的主服務器,對於多主組此功能無效。只有主庫才允許寫入,因此如果該成員上正在運行異步通道複製,則在異步通道複製停止之前不允許切換。通過發出以下命令傳遞要成爲該組新主服務器成員的server_uuid:
mysql> select group_replication_set_as_primary('8eed0f5b-6f9b-11e9-94a9-005056a57a4e');
+--------------------------------------------------------------------------+
| group_replication_set_as_primary('8eed0f5b-6f9b-11e9-94a9-005056a57a4e') |
+--------------------------------------------------------------------------+
| Primary server switched to: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e |
+--------------------------------------------------------------------------+
1 row in set (0.02 sec)
(2)更改模式
group_replication_switch_to_multi_primary_mode()函數用於單主改多主:
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | PRIMARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | SECONDARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | SECONDARY |
+--------------------------------------+-------------+-------------+
3 rows in set (0.00 sec)
mysql> select group_replication_switch_to_multi_primary_mode();
+--------------------------------------------------+
| group_replication_switch_to_multi_primary_mode() |
+--------------------------------------------------+
| Mode switched to multi-primary successfully. |
+--------------------------------------------------+
1 row in set (0.01 sec)
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | PRIMARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | PRIMARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | PRIMARY |
+--------------------------------------+-------------+-------------+
3 rows in set (0.00 sec)
下面驗證一下多主模式下對組複製的限制。
- 串行化隔離級別下報錯。
mysql> show create table test.t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`a` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
mysql> set transaction_isolation='serializable';
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test.t1 select null;
ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.
mysql> set transaction_isolation='repeatable-read';
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test.t1 select null;
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
- 級聯更新報錯
mysql> create table stu(
-> sid int unsigned primary key auto_increment,
-> name varchar(20) not null);
stu(sid));
Query OK, 0 rows affected (0.02 sec)
mysql>
mysql> create table sc(
-> scid int unsigned primary key auto_increment,
-> sid int unsigned not null,
-> score varchar(20) default '0',
-> index (sid),
-> foreign key (sid) references stu(sid));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into stu (name) value ('zxf');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> insert into sc(sid,score) values ('1','98');
Query OK, 1 row affected (0.01 sec)
mysql> drop table sc;
Query OK, 0 rows affected (0.02 sec)
mysql> drop table stu;
Query OK, 0 rows affected (0.01 sec)
mysql> create table stu(
-> sid int unsigned primary key auto_increment,
-> name varchar(20) not null);
Query OK, 0 rows affected (0.02 sec)
mysql> create table sc(
-> scid int unsigned primary key auto_increment,
-> sid int unsigned not null,
-> score varchar(20) default '0',
-> index (sid),
-> foreign key (sid) references stu(sid) on delete cascade on update cascade);
Query OK, 0 rows affected (0.02 sec)
mysql> insert into stu (name) value ('zxf');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> insert into sc(sid,score) values ('1','98');
ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.
mysql>
group_replication_switch_to_single_primary_mode()函數用於多主改單主:
mysql> select group_replication_switch_to_single_primary_mode('8eed0f5b-6f9b-11e9-94a9-005056a57a4e');
+-----------------------------------------------------------------------------------------+
| group_replication_switch_to_single_primary_mode('8eed0f5b-6f9b-11e9-94a9-005056a57a4e') |
+-----------------------------------------------------------------------------------------+
| Mode switched to single-primary successfully. |
+-----------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
如果未傳入任何字符串,則新主節點的選擇由配置的選舉權重或server_uuid字典順序控制。
4. 配置併發寫實例數
group_replication_get_write_concurrency()函數用於在運行時檢查組的最大並行可寫實例數。缺省值爲10適用於LAN上運行的組,對於通過較慢網絡上(如WAN)運行的組,增加此數量可以微調組複製的性能。
mysql> select group_replication_get_write_concurrency();
+-------------------------------------------+
| group_replication_get_write_concurrency() |
+-------------------------------------------+
| 10 |
+-------------------------------------------+
1 row in set (0.00 sec)
group_replication_set_write_concurrency()函數用於在運行時設置組的最大並行可寫實例數,值域爲10-200。該函數是異步執行的,可以調用group_replication_get_write_concurrency確認設置生效。
mysql> select group_replication_set_write_concurrency(1);
ERROR 1123 (HY000): Can't initialize function 'group_replication_set_write_concurrency'; Argument must be between 10 and 200.
mysql> select group_replication_set_write_concurrency(201);
ERROR 1123 (HY000): Can't initialize function 'group_replication_set_write_concurrency'; Argument must be between 10 and 200.
mysql> select group_replication_set_write_concurrency(10);
+-----------------------------------------------------------------------------------+
| group_replication_set_write_concurrency(10) |
+-----------------------------------------------------------------------------------+
| UDF is asynchronous, check log or call group_replication_get_write_concurrency(). |
+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
5. 設置組的通信協議版本
從MySQL 8.0.16開始,組複製具有通信協議的概念。可以顯式管理組複製通信協議版本,並將其設置爲支持的最老的MySQL服務器版本。這使得組可以由不同MySQL服務器版本的成員組成,同時確保向後兼容性。該組的所有成員必須使用相同的通信協議版本,以便發送所有組成員都能理解的消息。
如果組的通信協議版本小於或等於X,則版本X的MySQL服務器加入到複製組並達到ONLINE狀態。新成員加入複製組時,該組的現有成員會檢查加入成員的通信協議版本。如果支持該版本,則將它加入該組並使用該組已宣佈的通信協議。如果不支持通信協議版本,則將其從組中移除。
只有新成員的通信協議版本與該組的通信協議版本兼容時,它們才能加入。加入該組的具有不同通信協議版本的成員必須單獨加入。例如:
- 一個MySQL Server 8.0.16實例可以成功加入使用通信協議版本5.7.24的組。
- 一個MySQL Server 5.7.24實例無法成功加入使用通信協議版本8.0.16的組。
- 兩個MySQL Server 8.0.16實例無法同時加入使用通信協議版本5.7.24的組。
- 兩個MySQL Server 8.0.16實例可以同時加入使用通信協議版本8.0.16的組。
group_replication_get_communication_protocol()函數用於檢查組使用的通信協議,該函數返回該組支持的最老的MySQL服務器版本。該組的所有現有成員都返回相同的通信協議版本。
mysql> select group_replication_get_communication_protocol();
+------------------------------------------------+
| group_replication_get_communication_protocol() |
+------------------------------------------------+
| 8.0.16 |
+------------------------------------------------+
1 row in set (0.00 sec)
group_replication_get_communication_protocol的返回值可能與傳遞給group_replication_set_communication_protocol 的版本號以及該組成員上正在使用的MySQL服務器版本不同。如果需要更改組的通信協議版本以便早期版本的成員可以加入,使用group_replication_set_communication_protocol()函數指定要允許的最老成員的MySQL服務器版本。這使得該組在可能的情況下回退到兼容的通信協議版本。使用此函數需要GROUP_REPLICATION_ADMIN權限,並且在發出語句時所有現有組成員必須處於在線狀態。
mysql> select group_replication_set_communication_protocol("5.7.25");
+-----------------------------------------------------------------------------------+
| group_replication_set_communication_protocol("5.7.25") |
+-----------------------------------------------------------------------------------+
| The operation group_replication_set_communication_protocol completed successfully |
+-----------------------------------------------------------------------------------+
1 row in set (0.01 sec)
mysql> select group_replication_get_communication_protocol();
+------------------------------------------------+
| group_replication_get_communication_protocol() |
+------------------------------------------------+
| 5.7.14 |
+------------------------------------------------+
1 row in set (0.00 sec)
如果將複製組的所有成員升級到新的MySQL服務器版本,則該組的通信協議版本不會自動升級以匹配。必須使用group_replication_set_communication_protocol()函數將通信協議版本設置爲新MySQL服務器版本。
mysql> select group_replication_set_communication_protocol("8.0.16");
+-----------------------------------------------------------------------------------+
| group_replication_set_communication_protocol("8.0.16") |
+-----------------------------------------------------------------------------------+
| The operation group_replication_set_communication_protocol completed successfully |
+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
group_replication_set_communication_protocol()函數作爲組操作實現,因此它將在組的所有成員上同時執行。組操作開始緩衝消息並等待傳遞已完成的任何傳出消息,然後更改通信協議版本併發送緩衝的消息。如果成員在更改通信協議版本後加入該組,則組成員將使用新協議版本。
二、保證數據一致性
1. 組複製數據一致性簡介
對分佈式系統的一個重要需求是它能夠提供一致性保證。就分佈式一致性而言,無論是在正常還是失敗修復的操作中,組複製始終是始終保持最終一致性。可以將影響數據一致性的事件分爲兩類:一是手動或由故障自動觸發的控制操作;二是數據流。
(1)控制操作
與一致性相關的組複製操作包括:添加或移除組成員、網絡故障保護和主庫故障轉移。添加或移除組成員的數據一致性保障已經在“分佈式恢復”中描述。
- 網絡故障寫保護
當一個組成員離開復制組後,該成員不能繼續接收更新事務,否則只能在本地提交,造成數據不一致。爲了提高安全性,執行STOP GROUP_REPLICATION時,在服務器上啓用超級只讀模式。這將導致服務器在離開組時默認只讀,無論它離開的原因是手動還是網絡故障。
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | PRIMARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | PRIMARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | PRIMARY |
+--------------------------------------+-------------+-------------+
3 rows in set (0.00 sec)
mysql> show variables like '%read_only%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_read_only | OFF |
| read_only | OFF |
| super_read_only | OFF |
| transaction_read_only | OFF |
+-----------------------+-------+
4 rows in set (0.00 sec)
mysql> stop group_replication;
Query OK, 0 rows affected (7.92 sec)
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | |
+--------------------------------------+-------------+-------------+
1 row in set (0.00 sec)
mysql> show variables like '%read_only%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_read_only | OFF |
| read_only | ON |
| super_read_only | ON |
| transaction_read_only | OFF |
+-----------------------+-------+
4 rows in set (0.01 sec)
mysql> start group_replication;
Query OK, 0 rows affected (3.47 sec)
mysql> show variables like '%read_only%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_read_only | OFF |
| read_only | OFF |
| super_read_only | OFF |
| transaction_read_only | OFF |
+-----------------------+-------+
4 rows in set (0.01 sec)
mysql> select member_id, member_host, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+-------------+
| member_id | member_host | member_role |
+--------------------------------------+-------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | PRIMARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | PRIMARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | PRIMARY |
+--------------------------------------+-------------+-------------+
3 rows in set (0.00 sec)
- 主庫故障轉移
單主模式中,當主庫發生故障,一個從庫被提升爲主庫時,對於積壓事務與新事務有兩種可選的處理方式:1)可以立即服務應用程序,無論複製積壓的數量有多大;2)在處理積壓事務之前限制訪問。
第一種方案中,系統將花費最少的時間在主庫故障之後通過選擇新主庫來保護穩定的組成員資格,然後在應用舊主庫積壓的事務時立即允許數據訪問。這種方式能夠保證寫入一致性,但可能讀取到過時的數據。使用第二種方法,系統將在主庫故障後保護穩定的組成員資格,並以與第一種方案相同的方式選擇新主庫。但在這種情況下,該組將等待新主庫應用所有積壓事務,之後才允許數據訪問。故障轉移所需的時間與積壓大小成正比,在均衡的複製組中,積壓應該很小。
MySQL 8.0.14之前採用可用性最大化策略(第一種方法),並且不可配。MySQL 8.0.14及更高版本中可以使用group_replication_consistency變量配置組成員在主庫故障轉移期間提供的事務一致性保證級別。
(2)數據流
數據流影響組一致性的方式基於讀取和寫入。單主模式中,通常將傳入的讀/寫事務進行拆分,寫入路由到主庫,讀取均勻分配給從庫。複製組對外應該表現爲單個實體,因此可以合理地期望主庫上的寫入在從庫上即時可用。儘管組複製是在實現Paxos算法的組通信系統(GCS)協議之上編寫的,但組複製的某些部分是異步的,這意味着數據異步應用於從庫,因此可能出現這樣的情況,客戶端C1在主庫上寫入“A = 2 WHERE A = 1”,立即連接到從庫但讀取到“A = 1”。這是MySQL 8.0.14之前唯一可用的一致性級別。
可以選擇在讀取或寫入時同步數據。如果在讀取時進行同步,則當前客戶端會話將等待一個給定點,即所有先前更新事務完成的時間點,然後才能開始執行。此方法僅影響當前會話,所有其它併發數據操作不受影響。如果在寫入時進行同步,則寫入會話將等待所有其它成員都寫入其數據。由於組複製遵循事務的總順序,這意味着需要等待其它成員執行隊列中所有先前的寫入及其本次寫入。這兩種可選方案都能確保前面例子中,即使立即連接到從庫,客戶端C1也將始終讀取“A = 2”。同步點的確定與系統工作負載直接相關。
寫時同步適用場景:
- 組的寫入比讀取少的多,希望對讀取進行負載均衡,又不對讀取哪個服務器進行額外限制以避免讀取舊數據。
- 主要是隻讀數據的組,希望讀寫事務一旦提交就應用到所有成員,以便後續讀取最新數據。
讀時同步適用場景:
- 組的寫入比讀取多的多,希望對讀取進行負載均衡,又不對讀取哪個服務器進行額外限制以避免讀取舊數據。
- 希望工作負載中的特定事務始終從組中讀取最新數據,以便每當更新敏感數據時強制讀取最新值。
可以簡單但不夠嚴謹地理解爲讀多寫時同步,寫多讀時同步,目的就是減少同步次數同時滿足數據一致性要求。後面會看到讀、寫兩個同步點以及兩者的組合,對應group_replication_consistency系統變量的可選值,用作選擇組複製一致性級別。
2. 防止主庫故障轉移造成的過時讀取
組複製羣集自動檢測故障並調整組成員視圖,即成員資格配置。如果組以單主模式部署,當成員資格更改時,將執行檢查以確定組中是否存在主庫。如果沒有,則在從庫成員列表中選擇一個作爲新主庫,這就是所謂的從庫提升。用戶期望一旦發生從庫提升,新主庫數據與舊主庫數據處於完全相同的狀態,在新主庫上不能讀取或寫入舊數據。換句話說,當能夠讀取和寫入新主庫時,其上沒有積壓的複製事務。
從MySQL 8.0.14開始,從庫提升後,用戶可以指定新主庫的行爲。新增的group_replication_consistency系統參數用於控制新主庫是採用之前版本的最終一致性,還是阻止讀取和寫入,直到完全應用積壓事務。如果在具有group_replication_consistency='BEFORE_ON_PRIMARY_FAILOVER'設置的新主庫上執行事務時,該新主庫正在處理積壓,則事務將被阻止,直到完全應用待處理的積壓事務。這可確保在主庫發生故障轉移時,無論是自動觸發還是手工觸發,客戶端始終會在新主庫上看到最新值,因此防止了以下異常:
- 對於只讀和讀寫事務,沒有過時讀取。這可以防止新主庫將過時讀取外部化到應用程序。
- 讀寫事務沒有虛假回滾,因爲與複製讀寫事務產生寫-寫衝突的事務仍處於待處理狀態。
- 讀寫事務沒有讀取偏差,例如:
-- 不會向t2插入從t1過時讀取的舊數據
insert into t2 select a from t1;
注意,該設置表明在可用性與一致性之間,用戶更重視數據一致性。畢竟和最終一致性相比,此設置可能使應用程序產生等待。這意味着客戶端必須能夠在應用積壓的情況下處理延遲。通常這種延遲應該很小,具體取決於積壓的大小。
當group_replication_consistency ='BEFORE_ON_PRIMARY_FAILOVER'時,並非所有讀取都被阻止。例如,提升發生後,允許下面不修改數據的非阻塞查詢:
- SHOW commands
- SET option
- DO
- EMPTY
- USE
- SELECTing from performance_schema database
- SELECTing from table PROCESSLIST on database infoschema
- SELECTing from sys database
- SELECT command that don’t use tables
- SELECT command that don’t execute user defined functions
- STOP GROUP_REPLICATION command
- SHUTDOWN command
- RESET PERSIST
爲了保證組不返回過時數據,組中所有成員都應該如下配置。從庫提升後阻塞新事務,直到新主庫應用所有積壓。
set persist group_replication_consistency= 'before_on_primary_failover';
3. 選擇適當的一致性級別
group_replication_consistency系統變量用於配置組複製的一致性級別,不同配置對組處理的只讀(RO)和讀寫(RW)事務產生不同的影響。按增加事務一致性保證的順序,該變量有EVENTUAL、BEFORE_ON_PRIMARY_FAILOVER、BEFORE、AFTER、BEFORE_AND_AFTER五個可選值,缺省爲EVENTUAL。從上一小節已經瞭解了BEFORE_ON_PRIMARY_FAILOVER的作用,下面介紹其它四個可選值。
(1)EVENTUAL——缺省值
讀、寫事務都直接執行,不等待之前複製積壓的事務,也不等待其它組成員應用這些事務。這是MySQL 8.0.14之前的組複製行爲。
- 流程圖
- 算法描述
- 在組成員M1上開始事務T1。
- T1執行到提交點,在該點事務數據已經發送到所有組成員,包括髮起T1的成員M1。
- 每個成員檢查T1是否和之前的事務存在衝突,如果是則回滾T1,否則T1在M1上提交,並且進入其它成員的事務隊列,排隊執行和提交。
- 在成員M3接收到T1數據前開始事務T2。在M3上,T2將在T1之前執行,因此T2可能讀取到過時數據。
- 示例
hdp3上鎖定表。
mysql> lock table t1 read;
Query OK, 0 rows affected (0.00 sec)
hdp2上執行插入成功,不會產生等待。
mysql> insert into t1 values (1);
Query OK, 1 row affected (0.01 sec)
hdp3上執行查詢,由於表被鎖定,插入事務處於等待狀態,因此查詢結果爲insert前的數據。表解鎖後,插入事務隨之執行和提交,查詢結果爲insert後的數據。
mysql> select * from t1;
Empty set (0.00 sec)
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+---+
| a |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
(2)BEFORE——讀時同步
在開始執行之前,事務將等待所有先前的事務完成。這可確保此事務將在最新的數據快照上執行,而不管在哪個成員上執行。此一致性級別涵蓋BEFORE_ON_PRIMARY_FAILOVER提供的一致性保證。
- 流程圖
- 算法描述
- 在組成員M1上使用EVENTUAL級別開始事務T1。
- T1執行到提交點,在該點事務數據已經發送到所有組成員,包括髮起T1的成員M1。
- 每個成員檢查T1是否和之前的事務存在衝突,如果是則回滾T1,否則T1在M1上提交,並且進入其它成員的事務隊列,排隊執行和提交。
- 在成員M3接收到T1數據前,使用BEFORE級別開始事務T2。T2將向所有羣成員發送消息,提供T2的全局順序。
- 當按順序接收和處理消息時,M3從消息流中獲取組複製應用程序的RECEIVED_TRANSACTION_SET,這是允許提交的遠程事務集合。無論這些事務是否實際已經提交,它們都包含在此集合中。這個集合提供了在T2之前存在的遠程事務。爲了保證成員上的一致性讀,需要跟蹤遠程事務。儘管提供T2全局順序的消息已經發送給所有組成員,但只有M3需要對其進行操作,其它成員丟棄此消息而不進行任何其它操作。
- 事務T2僅在提交組複製應用程序RECEIVED_TRANSACTION_SET中的所有事務後纔開始在M3上執行。這確保T2不會讀取和執行相對於其全局順序過時的數據,這裏的順序爲T1,T2。此等待僅發生在執行具有BEFORE一致性事務的服務器上,本例中是M3,所有其它成員不受此等待的影響。
- 示例
hdp3上,會話1中鎖定表。
mysql> lock table t1 read;
Query OK, 0 rows affected (0.00 sec)
hdp2上執行插入成功,不會產生等待。
mysql> insert into t1 values (1);
Query OK, 1 row affected (0.01 sec)
hdp3上,會話2中在BEFORE級別下執行查詢,由於表被鎖定,插入事務處於等待狀態,因此查詢等待。
mysql> set @@session.group_replication_consistency='BEFORE' ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
hdp3上,會話1中解鎖表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
表解鎖後,插入事務隨之執行和提交,等待的查詢得以執行,結果爲insert後的數據。因爲單從輸出結果中無法區分是立即返回結果,還是先掛起一段時間後再返回的結果,所以這裏沒貼出查詢結果。
(3)AFTER——寫時同步
讀寫事務將等待其更改已應用於其它成員,對只讀事務沒有影響。此模式確保在本地成員上提交事務時,後續事務會讀取最新值,而無論在哪個成員上執行。將此模式與主要只讀操作的組一起使用,保證應用的讀寫事務在提交後隨處可用。通過僅在讀寫事務上使用同步,減少了只讀事務上的同步開銷。此一致性級別涵蓋BEFORE_ON_PRIMARY_FAILOVER提供的一致性保證。
- 流程圖
- 算法描述
- 在組成員M1上使用AFTER級別開始事務T1。
- T1執行到提交點,在該點事務數據已經發送到所有組成員,包括髮起T1的成員M1。
- 每個成員檢查T1是否和之前的事務存在衝突,如果是則回滾T1,否則執行第4步。
- T1在其它成員上排隊執行。一旦事務進入準備階段,即數據在等待提交指令的存儲引擎上最終確定時,它將向所有成員發送ACK確認。
- 一旦所有成員收到來自所有成員的確認,它們都提交事務。
- 在成員M3上的T1事務處於準備和提交之間時,使用EVENTUAL級別開始事務T2。此時T1還沒有提交,所以T2將等待T1提交完成後再開始執行。這確保T1後的事務都能讀取到T1的最新數據。
- 示例
hdp3上,會話1中鎖定表。
mysql> lock table t1 read;
Query OK, 0 rows affected (0.00 sec)
hdp2上在AFTER級別執行插入。因爲表被鎖定,插入事務處於等待狀態。
mysql> set @@session.group_replication_consistency='AFTER' ;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values (1);
hdp3上,會話2中執行查詢。由於插入事務處於等待狀態,因此查詢等待。
mysql> select * from t1;
hdp3上,會話1中解鎖表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
表解鎖後,插入事務隨之執行和提交,其後等待的查詢得以執行,結果爲insert後的數據。基於和前面BEFORE示例中相同的原因,這裏沒貼出查詢結果。
(4)BEFORE_AND_AFTER——讀寫時都同步
此事務開始執行時等待:1)所有該事務先前的事務都執行完成;2)該事務的變更已應用於其它成員。這可確保:1)此事務將在最新的數據快照上執行;2)一旦此事務完成,所有後續事務都會讀取到包含其更改的數據庫狀態,無論它們在哪個成員上執行。此一致性級別涵蓋BEFORE_ON_PRIMARY_FAILOVER提供的一致性保證。
- 流程圖
BEFORE_AND_AFTER一致性級別的算法流程是將BEFORE和AFTER組合在一起。讀寫事務等待之前所有的事務完成,並且等待其在所有節點上的變更結束。只讀事務需要等待之前所有的事務完成。
- 示例
hdp2上,會話1中鎖定表,並執行更新。
mysql> lock table t1 write;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values (1);
Query OK, 1 row affected (0.01 sec)
hdp3上,會話1中鎖定表,並執行更新。
mysql> lock table t1 write;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values (2);
Query OK, 1 row affected (0.00 sec)
hdp2上,會話2中在BEFORE_AND_AFTER級別執行查詢。因爲同一服務器上的更新被鎖定,只讀查詢需要等待。
mysql> set @@session.group_replication_consistency='BEFORE_AND_AFTER' ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
hdp2上,會話1中解鎖表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
hdp2上,會話2中的查詢返回最新結果,距查詢開始已經等待了62.68秒。
mysql> select * from t1;
+---+
| a |
+---+
| 1 |
| 2 |
+---+
2 rows in set (1 min 2.68 sec)
hdp2上,會話2中執行更新。因爲hdp3上的更新被鎖定,讀寫事務需要等待。
mysql> insert into t1 values (3);
hdp3上,會話1中解鎖表。
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
表解鎖後,hdp2上會話2中執行的插入事務隨之執行和提交。
BEFORE可以用於讀取和寫入事務,AFTER只用於寫事務。不同可選值提供了靈活性的一致性級別設置。
- 場景1:讀多寫少,不讀取過期數據的情況下對讀取進行負載平衡。選擇AFTER。
- 場景2:寫多讀少,不讀取過期數據。選擇BEFORE。
- 場景3:希望工作負載中的特定事務始終從組中讀取最新數據。選擇BEFORE。
- 場景4:複製組主要爲只讀,希望讀寫事務一旦提交就應用於任何地方,以便後續讀取最新數據,並且不爲只讀事務產生同步開銷。選擇AFTER。
- 場景5:複製組主要爲只讀,希望讀寫事務始終從組中讀取最新數據,並在提交後隨處應用,以便後續讀取最新數據,並且不爲只讀事務產生同步開銷。選擇BEFORE_AND_AFTER。
4. 一致性級別範圍
選擇強制執行一致性級別的範圍非常重要,如果將它們設置在全局範圍內,一致性級別可能會對性能產生負面影響。可以在全局或會話級別設置group_replication_consistency系統變量:
-- 強制執行當前會話的一致性級別
set @@session.group_replication_consistency = 'before';
-- 強制執行所有會話的一致性級別
set @@global.group_replication_consistency = 'before';
在特定會話上設置一致性級別的可能場景有:
- 場景6:只有一種更新,如設置對文檔的訪問權限,希望更改訪問權限後,確保所有客戶端都能看到正確的權限。其它更新沒有強一致性要求。只需要在該事務上執行 set @@session.group_replication_consistency = 'after',其它事務使用缺省的EVENTUAL級別。
- 場景7:在場景6中描述的同一系統上,每天需要讀取最新數據進行一些分析處理。只需要在該特定事務上執行 set @@ session.group_replication_consistency = 'before'。
作爲原則,如果只有一些特性事務需要強一致性,就在會話級別設置group_replication_consistency。需要強調的一點是,所有事務在組複製中是完全排序的,因此即使發出以下命令僅設置當前會話的一致性級別爲'AFTER':
set @@session.group_replication_consistency = 'after';
此事務也將等待其更改應用於所有組成員,即等待執行隊列上的此事務和所有先前事務。在實踐中,AFTER一致性級別將等待所有先前事務和當前事務在其它成員執行完。只能在狀態爲ONLINE的組成員上設置BEFORE、AFTER和BEFORE_AND_AFTER的一致性級別,嘗試在其它狀態的成員上使用它們會導致會話錯誤。一致性級別不是EVENTUAL的事務等待的最長時間由wait_timeout系統變量指定,缺省爲8小時。如果超時,則拋出ER_GR_HOLD_WAIT_TIMEOUT錯誤。
5. 一致性級別的影響
可以根據對複製組其它成員的影響,對一致性級別進行分類。除了在事務流上排序之外,BEFORE一致性級別僅影響本地成員。也就是說,它不需要與其它成員協調,也不會對其它事務產生影響,或者說BEFORE僅影響使用它的事務。AFTER和BEFORE_AND_AFTER一致性級別對在其它成員上執行的併發事務具有副作用。這兩個一致性級別可以使其它成員事務陷入等待。假設一個事務T2在具有EVENTUAL級別的成員M2上啓動時,成員M1正在AFTER或BEFORE_AND_AFTER級別下執行一個事務T1,則T2必須等待T1在M2上提交後才能開始執行。對於其它成員也是如此,即使它們具有EVENTUAL一致性級別。就是說設置AFTER和BEFORE_AND_AFTER會影響所有ONLINE成員。
爲了進一步說明這一點,在具有M1、M2、M3三個成員的組試驗驗證。三個成員初始的group_replication_consistency均爲缺省值EVENTUAL。
(1)在成員M1上執行一個需要長時間提交的事務T1,這裏刪除一個具有2097152行數據的大表。
set @@session.group_replication_consistency = 'after';
begin;
delete from num;
commit;
(2)上面SQL最後的commit命令執行過程中,在M2上開啓一個事務T2。
select count(*) from num for update;
下面是兩次執行T2的輸出:
mysql> select count(*) from num for update;
+----------+
| count(*) |
+----------+
| 2097152 |
+----------+
1 row in set (1.66 sec)
mysql> select count(*) from num for update;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (30.59 sec)
第一次執行時,T1還沒有傳遞到M2時,T2可以正常查詢出刪除前的表記錄數。第二次執行時,T2開始等待,因爲此時T1已經在M2的事務隊列中排隊,並且排在T2之前。即使T2的一致性級別爲EVENTUAL,它也必須等待T1先完成提交。當等待了30秒後,T1提交完畢,T2開始執行,此時返回最新的表記錄數0。
三、其它配置
1. 調整恢復
新成員加入複製組時,它會連接到一個合適的捐贈者(donor),從那裏獲取缺失的歷史數據,直到它變爲在線狀態爲止。此過程就是“MySQL 8 複製(七)——組複製基本原理”中詳細討論的分佈式恢復。這裏側重如何設置分佈式恢複相關的系統變量。
捐贈者是從組中當前在線成員中隨機選擇的,這樣當多個成員進入組時,很大可能不會選擇同一服務器作爲捐贈者。如果新成員與捐贈者的連接失敗,會自動嘗試連接到另一個新的候選捐贈者。達到連接重試限制後,恢復過程將終止並顯示錯誤。組複製提供了強大的錯誤檢測機制,能夠在整個恢復過程中應對失敗。例如,當出現以下問題時,恢復都能檢測到錯誤並嘗試切換到新的捐贈者:
- 加入組的服務器已經包含的數據與恢復期間來自所選捐贈者的數據存在衝突。
- 贈者包含新增成員已經清除(purge)GTID的數據。
- 恢復的接收線程或應用線程失敗。
(1)設置重連次數
如果出現一些持續性故障甚至是瞬態故障,恢復將自動重試連接到相同或新的捐贈者。恢復數據傳輸依賴於二進制日誌和現有的MySQL異步複製框架,因此一些瞬態錯誤可能會導致接收線程或應用線程錯誤。在這種情況下,捐贈者切換進程具有重試功能,重試次數通過group_replication_recovery_retry_count插件變量設置,缺省值爲10。該變量指定服務器連接到每個合適的捐贈者的全局嘗試次數。
mysql> show variables like 'group_replication_recovery_retry_count';
+----------------------------------------+-------+
| Variable_name | Value |
+----------------------------------------+-------+
| group_replication_recovery_retry_count | 10 |
+----------------------------------------+-------+
1 row in set (0.01 sec)
mysql> set group_replication_recovery_retry_count=10;
ERROR 1229 (HY000): Variable 'group_replication_recovery_retry_count' is a GLOBAL variable and should be set with SET GLOBAL
mysql> set global group_replication_recovery_retry_count=10;
Query OK, 0 rows affected (0.00 sec)
(2)設置休眠時間
group_replication_recovery_reconnect_interval插件變量定義恢復進程在捐贈者連接嘗試之間應休眠的時間,默認設置爲60秒,可以動態更改。
mysql> show variables like 'group_replication_recovery_reconnect_interval';
+-----------------------------------------------+-------+
| Variable_name | Value |
+-----------------------------------------------+-------+
| group_replication_recovery_reconnect_interval | 60 |
+-----------------------------------------------+-------+
1 row in set (0.02 sec)
mysql> set group_replication_recovery_reconnect_interval=60;
ERROR 1229 (HY000): Variable 'group_replication_recovery_reconnect_interval' is a GLOBAL variable and should be set with SET GLOBAL
mysql> set global group_replication_recovery_reconnect_interval=60;
Query OK, 0 rows affected (0.00 sec)
並不是每次嘗試連接捐贈者後都休眠。只有當加入組的服務器嘗試連接到該組中所有合適的捐贈者並且沒有剩餘時,恢復進程纔會休眠由group_replication_recovery_reconnect_interval變量配置的秒數。
2. 網絡分區
事務複製、組成員身份更改,以及一些使組保持一致的內部消息傳遞,都需要組成員達成共識。這要求大多數組成員就特定決定達成一致。當大多數組成員丟失時,組複製無法正常進行,因爲無法保證多數或法定票數。例如,在一個5臺服務器的複製組中,如果其中3臺異常宕機,則大無法達到法定票數。事實上,剩下的兩個服務器無法判斷其它三臺服務器是否已崩潰,或者網絡分區是否已將這兩臺服務器單獨隔離,因此無法自動重新配置該組。
另一方面,如果服務器自願退出組,它們會指示組應該重新配置自己,這意味着離開的服務器告訴其它成員它要退出。這種情況下其它成員可以正確地重新配置組,保持成員的數據一致性並重新計算法定票數。在上述5個服務器離開3個的場景中,如果3個離開的服務器一個接一個地通知組它們要離開,那麼成員資格能夠從5調整到2,同時確保法定票數。法定票數喪失本身是不良計劃的結果。無論故障是連續發生,一次性發生,還是零星發生,通常容忍 f 個故障機所需的服務器數量 n 爲:n = 2 * f + 1。
下面演示一個網絡分區的例子。如下所示一個三個成員的單主模式複製組,成員均爲在線狀態:
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | ONLINE | SECONDARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | ONLINE | SECONDARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | ONLINE | PRIMARY |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
現在用腳本同時殺掉hdp3、hdp4上的MySQL實例,腳本內容如下:
[mysql@hdp2~]$more kill_mysqld.sh
#!/bin/bash
ssh hdp3 "ps -ef | grep mysqld | grep -v grep | awk '{print \$2}' | xargs kill -9"
ssh hdp4 "ps -ef | grep mysqld | grep -v grep | awk '{print \$2}' | xargs kill -9"
在hdp2上再次查看成員狀態:
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | UNREACHABLE | SECONDARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | UNREACHABLE | SECONDARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | ONLINE | PRIMARY |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)
可以看到hdp2仍爲在線狀態,但hdp3、hdp4處於不可到達狀態。而且,系統無法重新配置自己以改變成員資格,因爲已經無法達到法定票數2。此時hdp2雖然在線,但無法執行任何事務,沒有外部干預就無法繼續提供服務。在這種特殊情況下,需要重置組成員資格以允許組複製繼續進行。此時有兩種選擇,一是重啓整個組複製:
-- hdp2上執行
stop group_replication;
set global group_replication_bootstrap_group=on;
start group_replication;
set global group_replication_bootstrap_group=off;
此方法實際上是重新初始化新的一個複製組,該複製組中只有hdp2一個成員(引導成員)。
第二種方法是使用group_replication_force_members變量強制指定組成員。組複製可以通過強制執行特定配置來重置組成員身份列表。本例中hdp2是唯一在線服務器,因此可以選擇強制hdp2的成員資格配置。必須強調,使用group_replication_force_members應被視爲最後的補救措施,必須非常小心地使用,並且只能用於失敗服務器大於等於法定票數的場景。如果誤用,可能會創建一個人工的裂腦情景或完全阻止整個系統。
首先要檢查hdp2的組通信標識符,在hdp2上執行下面的查詢獲取此信息。
mysql> select @@group_replication_local_address;
+-----------------------------------+
| @@group_replication_local_address |
+-----------------------------------+
| 172.16.1.125:33061 |
+-----------------------------------+
1 row in set (0.00 sec)
然後設置group_replication_force_members變量值爲上面的查詢結果,強制組成員爲hdp2:
mysql> set global group_replication_force_members="172.16.1.125:33061";
這會通過強制執行不同的配置來取消阻止該組。檢查hdp2上的replication_group_members以在此更改後驗證組成員身份。
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | ONLINE | PRIMARY |
+--------------------------------------+-------------+--------------+-------------+
1 row in set (0.00 sec)
強制執行新的成員資格配置時,必須保證所有強制退出該組的服務器確實已停止。在上面描述的場景中,如果hdp3、hdp4不可達(如斷網)但是MySQL實例可用,則它們可能已經形成了自己的網絡分區(它們是3箇中的2個,因此佔大多數)。這種情況下強制使用hdp2的組成員列表可能會產生人爲的裂腦情況。因此,在強制執行新的成員資格配置前,要確保排除的服務器實例已關閉,然後再繼續執行。 使用group_replication_force_members系統變量成功強制新的組成員身份並取消阻止該組後,應該清除該系統變量。group_replication_force_members必須爲空才能發出START GROUP_REPLICATION語句。
mysql> show variables like 'group_replication_force_members';
+---------------------------------+--------------------+
| Variable_name | Value |
+---------------------------------+--------------------+
| group_replication_force_members | 172.16.1.125:33061 |
+---------------------------------+--------------------+
1 row in set (0.01 sec)
mysql> stop group_replication;
Query OK, 0 rows affected (13.13 sec)
mysql> start group_replication;
ERROR 3092 (HY000): The server is not configured properly to be an active member of the group. Please see more details on error log.
mysql> set group_replication_force_members="";
ERROR 1229 (HY000): Variable 'group_replication_force_members' is a GLOBAL variable and should be set with SET GLOBAL
mysql> set global group_replication_force_members="";
Query OK, 0 rows affected (0.01 sec)
mysql> set global group_replication_bootstrap_group=on;
Query OK, 0 rows affected (0.00 sec)
mysql> start group_replication;
Query OK, 0 rows affected (3.04 sec)
mysql> set global group_replication_bootstrap_group=off;
Query OK, 0 rows affected (0.00 sec)
無論使用哪種方法恢復了組複製,都可以在hdp3、hdp4重新可用後,將它們重新添加到新的複製組。
啓動實例:
mysqld_safe --defaults-file=/etc/my.cnf &
啓動組複製:
start group_replication;
之後驗證組成員身份已經恢復到最初的狀態:
mysql> select member_id, member_host, member_state, member_role from performance_schema.replication_group_members;
+--------------------------------------+-------------+--------------+-------------+
| member_id | member_host | member_state | member_role |
+--------------------------------------+-------------+--------------+-------------+
| 5c93a708-a393-11e9-8343-005056a5497f | hdp4 | ONLINE | SECONDARY |
| 5f045152-a393-11e9-8020-005056a50f77 | hdp3 | ONLINE | SECONDARY |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | hdp2 | ONLINE | PRIMARY |
+--------------------------------------+-------------+--------------+-------------+
3 rows in set (0.00 sec)