1. 程式人生 > >MySQL組復制(2):配置單主模型的組復制

MySQL組復制(2):配置單主模型的組復制

AC 文件 便在 繼續 rep 解釋 種子 nsis HR

MySQL的組復制可以配置為單主模型多主模型兩種工作模式,它們都能保證MySQL的高可用。以下是兩種工作模式的特性簡介:

  • 單主模型:從復制組中眾多個MySQL節點中自動選舉一個master節點,只有master節點可以寫,其他節點自動設置為read only。當master節點故障時,會自動選舉一個新的master節點,選舉成功後,它將設置為可寫,其他slave將指向這個新的master。
  • 多主模型:復制組中的任何一個節點都可以寫,因此沒有master和slave的概念,只要突然故障的節點數量不太多,這個多主模型就能繼續可用。

雖然多主模型的特性很誘人,但缺點是要配置和維護這種模式,必須要深入理解組復制的理論,更重要的是,多主模型限制較多,其一致性、安全性還需要多做測試。

而使用單主模型的組復制就簡單的太多了,唯一需要知道的就是它會自動選舉master節點這個特性,因為它的維護一切都是自動進行的,甚至對於管理人員來說,完全可以不用去了解組復制的理論。

雖然單主模型比多主模型的性能要差,但它沒有數據不一致的危險,加上限制少,配置簡單,基本上沒有額外的學習成本,所以多數情況下都是配置單主模型的組復制,即使是PXC和MariaDB也如此。

1.單主模型組復制的理論基礎

雖說組復制的單主模型很簡單,但有必要了解一點和單主模型有關的理論,盡管不了解也沒什麽問題,畢竟一切都是自動的。

如下圖,master節點為s1,其余為slave節點。

技術分享圖片

組復制一切正常時,所有的寫操作都路由到s1節點上,所有的讀操作都路由到s2、s3、s4或s5上。當s1節點故障後,組復制自動選舉新的master節點。假如選舉s2為新master成功後,s3、s4和s5將指向s2,寫操作將路由到s2節點上。

至於如何改變客戶端的路由目標,這不是組復制應該考慮的事情,而是客戶端應用程序應該考慮的事情。實際上,更好的方式是使用中間件來做數據庫的路由,比如MySQL Router、ProxySQL、amoeba、cobar、mycat。

1.1 如何加入新節點

上面一直說,單主模型是自動選舉主節點的,那麽如何選舉?

首先,在第一個MySQL節點s1啟動時,一般會將其設置為組的引導節點,所謂引導就是在啟動組復制功能時去創建一個復制組。當然,這並非強制要求,也可以設置第二個啟動節點作為組的引導節點。因為組內沒有其他節點,所以這第一個節點會直接選為master節點。

然後,如果有第二個節點要加入組時,新節點需要征得組的同意,因為目前只有一個節點,所以只需s1節點同意即可。新節點在加入組時,首先會聯系s1,與s1建立異步復制

的通道,並從s1節點處獲取s2上目前缺失的數據,等到s1和s2節點上的數據同步後,s2節點就會真正成為組中的新成員。當然,實際過程要比這裏復雜一些,本文不會過多討論。

如果還有新節點(比如s3節點)繼續加入組,s3將從s1或s2中選一個,並與之建立異步復制的通道,然後獲取缺失的數據,同步結束後,如果s1和s2都同意s3加入,那麽s3將會組中的新成員。其余節點加入組也依次類推。

有兩點需要註意:

  1. 新節點加入組時,如何選擇聯系對象?

    上面說加入第二個節點s2時會聯系s1,加入s3時會聯系s1、s2中的任意一個。實際上,新節點加入組時聯系的對象,稱為donor,意為數據供應者。新節點會和選中的donor建立異步復制通道,並從donor處獲取缺失的數據。

    在配置組復制時,需要指定種子節點列表。當新節點加入組時,只會聯系種子節點,也即是說,只有種子節點列表中的節點才有機會成為donor,沒有在種子節點列表中的節點不會被新節點選中。但建議,將組中所有節點都加入到種子列表中

    當聯系第一個donor失敗後,會向後聯系第二個donor,再失敗將聯系第三個donor,如果所有種子節點都聯系失敗,在等待一段時間後再次從頭開始聯系第一個donor。依此類推,直到加組失敗報錯。

  2. 新節點加入組時,需要征得哪些節點的同意?

    實際上,新節點加組涉及到組的決策:是否允許它加組。在組復制中,所有的決策都需要組中大多數節點達成一致,也即是達到法定票數。所謂大多數節點,指的是N/2+1(N是組中目前節點總數),例如目前組中有5個節點,則需要3個節點才能達到大多數的要求。

1.2 如何選舉新的master

當主節點s1故障後,組復制的故障探測機制就能發現這個問題並報告給組中其他成員,組中各成員根據收集到的其他成員信息,會比較各成員的權重值(由變量group_replication_member_weigth控制),權重值最高的優先成為新的Master。如果有多個節點具有相同的最高權重值,會按字典順序比較它們的server_uuid值,最小的(升序排序,最小值在最前面)優先成為新的master。

但需要註意,變量group_replication_member_weigth是從MySQL 5.7.20開始提供的,在MySQL 5.7.17到5.7.19之間沒有該變量。此時將根據它們的server_uuid值進行排序選舉。具體的規則可自行測試。

1.3 最多允許多少個節點故障

MySQL組復制使用Paxos分布式算法來提供節點間的分布式協調。正因如此,它要求組中大多數節點在線才能達到法定票數,從而對一個決策做出一致的決定。

大多數指的是N/2+1(N是組中目前節點總數),例如目前組中有5個節點,則需要3個節點才能達到大多數的要求。所以,允許出現故障的節點數量如下圖:

技術分享圖片

1.4 單主模型組復制的要求

見:使用MySQL組復制的限制和局限性

1.5 更多組復制的理論

若想了解更多組復制的理論以及組復制中每一個過程的細節,請參考我另一篇文章(暫未寫),或者閱讀我對MySQL官方手冊關於組復制的翻譯。

2.配置單主模型

本文配置3個節點的單主模型組復制。配置很簡單,基本上就是在常規復制選項的基礎上多了幾個選項、多了幾步操作。

拓撲圖如下:

技術分享圖片

具體環境細節如下:

節點名稱 MySQL版本 客戶端接口(eth0) 組內通信接口(eth0) 數據狀態
s1 MySQL 5.7.22 192.168.100.21 192.168.100.21 全新實例
s2 MySQL 5.7.22 192.168.100.22 192.168.100.21 全新實例
s3 MySQL 5.7.22 192.168.100.23 192.168.100.21 全新實例

發現了每個節點都給了兩個接口嗎?我這裏配置它們都使用同一個接口eth0。其中:

  1. 客戶端接口是mysqld向外提供數據庫服務的,對應端口是3306,例如php程序連接MySQL執行一個查詢語句時就使用該地址。
  2. 組內節點通信接口用於組內各節點消息傳遞,組內兩兩節點建立一條消息傳遞的TCP連接。所以,3個節點需要建立的組內通信連接為:s1<-->s2、s1<-->s3、s2<-->s3

請確保這3個節點的主機名不同,且能正確解析為客戶端接口的地址(別搞錯地址了),因為在連接donor進行數據恢復的時候,是通過主機名進行解析的。所以,所有節點都要先配置好不同的主機名,並修改/etc/hosts文件。對於克隆出來的實驗主機,這一步驟很關鍵。以centos 7為例:

# s1上:
hostnamectl set-hostname --static s1.longshuai.com
hostnamectl -H [email protected] set-hostname s2.longshuai.com
hostnamectl -H [email protected] set-hostname s3.longshuai.com

# 寫/etc/hosts
# s1上:
cat >>/etc/hosts<<eof
    192.168.100.21 s1.longshuai.com
    192.168.100.22 s2.longshuai.com
    192.168.100.23 s3.longshuai.com
eof
scp /etc/hosts 192.168.100.22:/etc
scp /etc/hosts 192.168.100.23:/etc

2.1 配置組內第一個節點s1

1.先提供配置文件。

[mysqld]
datadir=/data
socket=/data/mysql.sock

server-id=100                      # 必須
gtid_mode=on                       # 必須
enforce_gtid_consistency=on        # 必須
log-bin=/data/master-bin           # 必須
binlog_format=row                  # 必須
binlog_checksum=none               # 必須
master_info_repository=TABLE       # 必須
relay_log_info_repository=TABLE    # 必須
relay_log=/data/relay-log          # 必須,如果不給,將采用默認值
log_slave_updates=ON               # 必須
sync-binlog=1                      # 建議
log-error=/data/error.log
pid-file=/data/mysqld.pid

transaction_write_set_extraction=XXHASH64         # 必須
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"  # 必須
loose-group_replication_start_on_boot=off        # 建議設置為OFF
loose-group_replication_local_address="192.168.100.21:20001"   # 必須,下一行也必須
loose-group_replication_group_seeds="192.168.100.21:20001,192.168.100.22:20002"

想要使用組復制,要求還是挺多的。分析一下上面的配置選項:

  • (1).因為組復制基於GTID,所以必須開啟gtid_modeenforce_gtid_consistency
  • (2).組復制必須開啟二進制日誌,且必須設置為行格式的二進制日誌,這樣才能從日誌記錄中收集信息且保證數據一致性。所以設置log_binbinlog_format
  • (3).由於MySQL對復制事件校驗的設計缺陷,組復制不能對他們校驗,所以設置binlog_checksum=none
  • (4).組復制要將master和relay log的元數據寫入到mysql.slave_master_infomysql.slave_relay_log_info中。
  • (5).組中的每個節點都保留了完整的數據副本,它是share-nothing的模式。所以所有節點上都必須開啟log_slave_updates,這樣新節點隨便選哪個作為donor都可以進行異步復制。
  • (6).sync_binlog是為了保證每次事務提交都立刻將binlog刷盤,保證出現故障也不丟失日誌。
  • (7).最後的5行是組復制插件的配置。以loose_開頭表示即使啟動組復制插件,MySQL也繼續正常允許下去。這個前綴是可選的。
  • (8).倒數第5行表示寫集合以XXHASH64的算法進行hash。所謂寫集,是對事務中所修改的行進行的唯一標識,在後續檢測並發事務之間是否修改同一行沖突時使用。它基於主鍵生成,所以使用組復制,表中必須要有主鍵。
  • (9).倒數第4行表示這個復制組的名稱。它必須是一個有效的UUID值。嫌可以直接和上面一樣全寫字母a。在Linux下,可以使用uuidgen工具來生成UUID值。

    [root@xuexi ~]# uuidgen
    09c38ef2-7d81-463e-bdb4-9459b2c0e49b
  • (10).倒數第3行表示組復制功能不隨MySQL實例啟動而啟動。雖然,可以將組復制插件和啟動組復制功能的選項寫在配置文件裏,但強烈建議不要如此,而是每次手動去配置。
  • (11).倒數第2行表示本機上用於組內各節點之間通信的地址和端口
  • (12).最後一行,設置本組的種子節點。種子節點的意義在前文已經解釋過了。

現在配置文件已經提供。可以啟動mysql實例了。

[root@xuexi ~]# systemctl start mysqld

2.創建復制用戶。

連上s1節點。創建用於復制的用戶。我這裏創建的用戶為repl,密碼為P@ssword1!

mysql> create user repl@'192.168.100.%' identified by 'P@ssword1!';
mysql> grant replication slave on *.* to repl@'192.168.100.%';

3.配置節點加組時的通道。這是組復制的一個關鍵。

在新節點加入組時,首先要選擇donor。新節點和donor之間的異步復制就是通過一個名為group_replication_recovery的通道(通道名固定,不可使用自定義通道)進行數據恢復的,經過數據恢復後,新節點填充了它缺失的那部分數據,這樣就和組內其他節點的數據保持了同步。

這個恢復過程比較復雜,它是一種分布式恢復。本文不介紹這個,在組復制理論詳解中,我將對此做詳細的說明。

執行change master to語句設置恢復通道。

mysql> change master to 
            master_user='repl',
            master_password='P@ssword1!'
            for channel 'group_replication_recovery';

這裏的用戶名、密碼和通道在組復制中有一個專門的術語:通道憑據(channel credentials)。通道憑據是連接donor的關鍵。

當執行完上面的語句後,就生成了一個該通道的relay log文件(註意稱呼:該通道的relay log,後面還有另一個通道的relay log)。如下,其中前綴"relay-log"是配置文件中"relay_log"選項配置的值。

[root@xuexi ~]# ls -1 /data/*group*
/data/relay-log-group_replication_recovery.000001
/data/relay-log-group_replication_recovery.index

group_replication_recovery通道的relay log用於新節點加入組時,當新節點聯系上donor後,會從donor處以異步復制的方式將其binlog復制到這個通道的relay log中,新節點將從這個recovery通道的relay log中恢復數據。

前面配置文件中已經指定了master和relay log的元數據信息要記錄到表中,所以這裏可以先查看下關於relay log的元數據。

mysql> select * from mysql.slave_relay_log_info\G
*************************** 1. row ***************************
  Number_of_lines: 7
   Relay_log_name: /data/relay-log-group_replication_recovery.000001
    Relay_log_pos: 4
  Master_log_name: 
   Master_log_pos: 0
        Sql_delay: 0
Number_of_workers: 0
               Id: 1
     Channel_name: group_replication_recovery

如果要查看連接master的元數據信息,則查詢mysql.slave_master_info表。不過現在沒必要查,因為啥都還沒做呢。

4.安裝組復制插件,並啟動組復制功能。

一切就緒後,可以開啟mysql實例的組復制功能了。

mysql> install plugin group_replication soname 'group_replication.so';

然後開啟組復制功能。

mysql> set @@global.group_replication_bootstrap_group=on;
mysql> start group_replication;
mysql> set @@global.group_replication_bootstrap_group=off;

這裏的過程很重要,需要引起註意。在開啟組復制之前,設置全局變量group_replication_bootstrap_group為on,這表示稍後啟動的組復制功能將引導組,也就是創建組並配置組,這些都是自動的。配置引導變量為ON後,再開啟組復制插件功能,也就是啟動組復制。最後將引導變量設回OFF,之所以要設置回OFF,是為了避免下次重啟組復制插件功能時再次引導創建一個組,這樣會存在兩個名稱相同實際卻不相同的組。

這幾個過程不適合放進配置文件中,強烈建議手動執行它們的。否則下次重啟mysql實例時,會自動重新引導創建一個組。同理,除了第一個節點,其他節點啟動組復制功能時,不應該引導組,所以只需執行其中的start語句,千萬不能開啟group_replication_bootstrap_group變量引導組。

這裏的幾個過程,應該形成一個習慣,在啟動第一個節點時,這3條語句同時執行,在啟動其他節點時,只執行start語句。

當啟動組復制功能後,將生成另一個通道group_replication_applier的相關文件。

[root@xuexi ~]# ls -1 /data/*group*
/data/relay-log-group_replication_applier.000001
/data/relay-log-group_replication_applier.000002
/data/relay-log-group_replication_applier.index
/data/relay-log-group_replication_recovery.000001
/data/relay-log-group_replication_recovery.index

是否還記得剛才用於恢復的通道group_replication_recovery?這個applier通道是幹什麽的?在組復制中,沒有常規復制的兩個復制線程:io線程和sql線程,取而代之的是receiver、certifier和applier。這裏簡單介紹一下它們的作用:

  • receiver的作用類似於io線程,用於接收組內個節點之間傳播的消息和事務。也用於接收外界新發起的事務。
  • applier的作用類似於sql線程,用於應用relay log中的記錄。不過,組復制的relay log不再是relay log,而是這裏的組復制relay log:relay-log-group_replication_applier.00000N
  • certifier的作用在receiver接收到消息後,驗證是否有並發事務存在沖突問題。沖突檢測通過後,這條消息就會寫入到組復制的relay log中,等待applier去應用。

5.驗證組中節點並測試插入不滿足組復制要求的數據。

至此,這個節點的組復制已經配置完成了。現在需要查看這個節點是否成功加入到組中,成功加入組的標誌是被設置為"ONLINE"。只要沒有設置為ONLINE,就表示組中的這個節點是故障的。

查看的方式是通過查詢performance_schema架構下的replication_group_members表。在這個架構下,有幾張對於維護組復制來說非常重要的表,這裏的replication_group_members是其中一張。關於其他的表,我會在有需要的地方或者其他文章中解釋。

mysql> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+---------------------+-------------+--------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST         | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+---------------------+-------------+--------------+
| group_replication_applier | a659234f-6aea-11e8-a361-000c29ed4cf4 | xuexi.longshuai.com |        3306 | ONLINE       |
+---------------------------+--------------------------------------+---------------------+-------------+--------------+

如果不方便觀看,換一種顯示方式:

mysql> select * from performance_schema.replication_group_members\G
*************************** 1. row ***************************
CHANNEL_NAME: group_replication_applier
   MEMBER_ID: a659234f-6aea-11e8-a361-000c29ed4cf4
 MEMBER_HOST: xuexi.longshuai.com
 MEMBER_PORT: 3306
MEMBER_STATE: ONLINE

請註意這裏的每一行,包括member_host,它是對外連接的地址,所以應該設置它的DNS解析為提供MySQL數據庫服務的接口地址。這很重要,如果你不想去修改DNS解析,可以在啟動組復制之前,設置report_host變量為對外的IP地址,或者將其寫入到配置文件中。

現在,組中的這個節點已經是ONLINE了,表示可以對外提供組復制服務了。

稍後,將向組中加入第二個節點s2和第三個節點s3,但在加入新節點之前,先向s1節點寫入一些數據,順便測試一下開啟組復制後,必須使用InnoDB、表中必須有主鍵的限制。

下面創建4個表:t1和t4是InnoDB表,t3和t4具有主鍵。

create table t1(id int);
create table t2(id int)engine=myisam;
create table t3(id int primary key)engine=myisam;
create table t4(id int primary key);

雖說組復制對這些有限制,但是創建時是不會報錯的。

向這4張表中插入數據:

insert into t1 values(1);
insert into t2 values(1);
insert into t3 values(1);
insert into t4 values(1);

會發現只有t4能插入成功,t1、t2、t3都插入失敗,報錯信息如下:

ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.

意思是該表不遵從外部插件(即組復制插件)的要求。

最後,查看下二進制日誌中的事件。為了排版,我將顯示結果中的日誌名稱列去掉了。

mysql> SHOW BINLOG EVENTS in 'master-bin.000004';
+------+----------------+-----------+-------------+-------------------------------------------------------------------+
| Pos  | Event_type     | Server_id | End_log_pos | Info                                                              |
+------+----------------+-----------+-------------+-------------------------------------------------------------------+
|    4 | Format_desc    |       100 |         123 | Server ver: 5.7.22-log, Binlog ver: 4                             |
|  123 | Previous_gtids |       100 |         150 |                                                                   |
|  150 | Gtid           |       100 |         211 | SET @@SESSION.GTID_NEXT= 'a659234f-6aea-11e8-a361-000c29ed4cf4:1' |
|  211 | Query          |       100 |         399 | CREATE USER 'repl'@'192.168.100.%' IDENTIFIED WITH 'password'     |
|  399 | Gtid           |       100 |         460 | SET @@SESSION.GTID_NEXT= 'a659234f-6aea-11e8-a361-000c29ed4cf4:2' |
|  460 | Query          |       100 |         599 | GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.100.%'          |
|  599 | Gtid           |       100 |         660 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1' |
|  660 | Query          |       100 |         719 | BEGIN                                                             |
|  719 | View_change    |       100 |         858 | view_id=15294216022242634:1                                       |
|  858 | Query          |       100 |         923 | COMMIT                                                            |
|  923 | Gtid           |       100 |         984 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:2' |
|  984 | Query          |       100 |        1083 | create database gr_test                                           |
| 1083 | Gtid           |       100 |        1144 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3' |
| 1144 | Query          |       100 |        1243 | use `gr_test`; create table t1(id int)                            |
| 1243 | Gtid           |       100 |        1304 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:4' |
| 1304 | Query          |       100 |        1416 | use `gr_test`; create table t2(id int)engine=myisam               |
| 1416 | Gtid           |       100 |        1477 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:5' |
| 1477 | Query          |       100 |        1601 | use `gr_test`; create table t3(id int primary key)engine=myisam   |
| 1601 | Gtid           |       100 |        1662 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:6' |
| 1662 | Query          |       100 |        1773 | use `gr_test`; create table t4(id int primary key)                |
| 1773 | Gtid           |       100 |        1834 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:7' |
| 1834 | Query          |       100 |        1905 | BEGIN                                                             |
| 1905 | Table_map      |       100 |        1949 | table_id: 117 (gr_test.t4)                                        |
| 1949 | Write_rows     |       100 |        1985 | table_id: 117 flags: STMT_END_F                                   |
| 1985 | Xid            |       100 |        2012 | COMMIT /* xid=63 */                                               |
+------+----------------+-----------+-------------+-------------------------------------------------------------------+

除了正常的事務對應的事件,需要關註的三行是:

BEGIN
View_change <----> view_id=15294216022242634:1
COMMIT

組復制中,每個決策都需要組中大多數節點達成一致,包括新節點加組、離組的決定。實際上,組復制中內置了一個組成員服務,這個服務負責組成員的配置以及動態捕獲組中成員列表,這個成員列表成為成員視圖。每個視圖都有一個view id,view id的第一部分是創建組時隨機生成的,只要組不停止,這部分就不改變,第二部分是從1開始的單調遞增的數值。每當有成員加組、離組時,都會觸發這個服務對組成員進行重新配置,每次組成員的重新配置,view id的第二部分都會單調遞增地加1,表示這是新的成員視圖,新的組成員視圖需要得到組中大多數節點的同意,所以這個消息要在組中進行傳播。如何傳播?就是通過將視圖更改的事件作為一個事務寫進binlog中,然後在組中到處復制,這樣每個節點都可收到視圖變化的消息,並對此做出回應,同意之後再commit這個事務。如果足夠細心,會發現這個事務的提交和下面插入數據的提交(COMMIT /* xid=63 */)方式不一樣。如果不理解也沒關系,這個理論並不影響組復制的使用。

再仔細一看,還可以發現MySQL中的DDL語句是沒有事務的。所以,絕不允許不同節點上對同一個對象並發執行"DDL+DML"和"DDL+DDL",沖突檢測機制會探測到這樣的沖突。

2.2 向組中添加新節點

當組中已有第一個節點後,需要做的是向組中添加新的節點。這裏以添加s2和s3為例。

2.2.1 添加新節點前要做什麽

前面多次提到,新節點在加入組的時候,會先選擇一個donor,並通過異步復制的方式從這個donor處獲取缺失的數據,以便在成功加入組的時候它的數據和組中已有的節點是完全同步的,這樣才能向外界客戶端提供查詢。

這裏的重點在於異步復制,既然是復制,它就需要復制binlog,並通過應用binlog中的記錄來寫數據。如果在加入組之前,組中的數據量已經非常大,那麽這個異步復制的過程會很慢,而且還會影響donor的性能,畢竟它要傳輸大量數據出去。

本來加入新節點的目的就是對組復制進行擴展,提高它的均衡能力,現在因為異步復制慢,反而導致性能稍有下降,新節點短期內還無法上線向外提供服務。這有點背離原本的目標。

再者,如果組中的節點purge過日誌,那麽新節點將無法從donor上獲取完整的數據。這時新節點上的恢復過程會讓它重新選擇下一個donor。但很可能還是會失敗,因為Purge如果是加組之後執行的,它已經復制到了組中的所有節點上,所有節點都會purge那段日誌。

所以,在新節點加入組之前,應該先通過備份恢復的方式,從組中某節點上備份目前的數據到新節點上,然後再讓新節點去加組,這樣加組的過程將非常快,且能保證不會因為purge的原因而加組失敗。至於如何備份恢復,參見我的另一篇文章:將slave恢復到master指定的坐標。

我這裏做實驗的環境,所有節點都是剛安裝好的全新實例,數據量小,也沒purge過日誌,所以直接加入到組中就可以。

2.2.2 添加第二個節點

仍然先是提供配置文件。配置文件和第一個節點基本相同,除了幾個需要保持唯一性的選項。

配置文件內容如下:

[mysqld]
datadir=/data
socket=/data/mysql.sock

server-id=110                      # 必須,每個節點都不能相同
gtid_mode=on                       # 必須
enforce_gtid_consistency=on        # 必須
log-bin=/data/master-bin           # 必須
binlog_format=row                  # 必須
binlog_checksum=none               # 必須
master_info_repository=TABLE       # 必須
relay_log_info_repository=TABLE    # 必須
relay_log=/data/relay-log          # 必須,如果不給,將采用默認值
log_slave_updates=ON               # 必須
sync-binlog=1                      # 建議
log-error=/data/error.log
pid-file=/data/mysqld.pid

transaction_write_set_extraction=XXHASH64         # 必須
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"  # 必須
loose-group_replication_start_on_boot=off        # 建議設置為OFF
loose-group_replication_local_address="192.168.100.22:20002"   # 必須,下一行也必須
loose-group_replication_group_seeds="192.168.100.21:20001,192.168.100.22:20002"

這裏和s1的配置文件相比,只修改了server-idgroup_replication_local_address

然後執行change master to,選擇一個donor(此刻只有s1能選),並和donor建立通道連接。

mysql> change master to 
            master_user='repl',
            master_password='P@ssword1!'
            for channel 'group_replication_recovery';

這時,就已經選擇好donor,並和donor建立通道連接了。如果去s1上查看,可以看到這個通道的連接。下面的查詢結果中,第二行就是和s2建立的連接,通道為group_replication_recovery

mysql> select * from mysql.slave_master_info\G
*************************** 1. row ***************************
       Number_of_lines: 25
       Master_log_name: 
        Master_log_pos: 4
                  Host: <NULL>
             User_name: 
         User_password: 
                  Port: 0
         Connect_retry: 60
           Enabled_ssl: 0
                Ssl_ca: 
            Ssl_capath: 
              Ssl_cert: 
            Ssl_cipher: 
               Ssl_key: 
Ssl_verify_server_cert: 0
             Heartbeat: 30
                  Bind: 
    Ignored_server_ids: 0
                  Uuid: 
           Retry_count: 86400
               Ssl_crl: 
           Ssl_crlpath: 
 Enabled_auto_position: 1
          Channel_name: group_replication_applier
           Tls_version: 
*************************** 2. row ***************************
       Number_of_lines: 25
       Master_log_name: 
        Master_log_pos: 4
                  Host: 
             User_name: repl
         User_password: P@ssword1!
                  Port: 3306
         Connect_retry: 60
           Enabled_ssl: 0
                Ssl_ca: 
            Ssl_capath: 
              Ssl_cert: 
            Ssl_cipher: 
               Ssl_key: 
Ssl_verify_server_cert: 0
             Heartbeat: 0
                  Bind: 
    Ignored_server_ids: 0
                  Uuid: 
           Retry_count: 86400
               Ssl_crl: 
           Ssl_crlpath: 
 Enabled_auto_position: 0
          Channel_name: group_replication_recovery
           Tls_version: 
2 rows in set (0.00 sec)

然後回到s2節點上,安裝組復制插件,並開啟組復制功能。

mysql> install plugin group_replication soname 'group_replication.so';
mysql> start group_replication;

組復制啟動成功後,查看是否處於online狀態。(請無視我這裏的Member_host字段,這是我設置了report_host變量的結果)

mysql> select * from performance_schema.replication_group_members\G 
*************************** 1. row ***************************
CHANNEL_NAME: group_replication_applier
   MEMBER_ID: a5165443-6aec-11e8-a8f6-000c29827955
 MEMBER_HOST: 192.168.100.22
 MEMBER_PORT: 3306
MEMBER_STATE: ONLINE
*************************** 2. row ***************************
CHANNEL_NAME: group_replication_applier
   MEMBER_ID: a659234f-6aea-11e8-a361-000c29ed4cf4
 MEMBER_HOST: xuexi.longshuai.com
 MEMBER_PORT: 3306
MEMBER_STATE: ONLINE

再查看數據是否已經同步到s2節點。其實顯示了ONLINE,就一定已經同步。

mysql> show tables from gr_test;
+-------------------+
| Tables_in_gr_test |
+-------------------+
| t1                |
| t2                |
| t3                |
| t4                |
+-------------------+
4 rows in set (0.00 sec)

mysql> select * from gr_test.t4;
+----+
| id |
+----+
|  1 |
+----+

查看binlog事件。會發現內容已經復制,且view id又發生了一次變化。

mysql> show binlog events in 'master-bin.000002';
+------+----------------+-----------+-------------+-------------------------------------------------------------------+
| Pos  | Event_type     | Server_id | End_log_pos | Info                                                              |
+------+----------------+-----------+-------------+-------------------------------------------------------------------+
|    4 | Format_desc    |       110 |         123 | Server ver: 5.7.22-log, Binlog ver: 4                             |
|  123 | Previous_gtids |       110 |         150 |                                                                   |
|  150 | Gtid           |       100 |         211 | SET @@SESSION.GTID_NEXT= 'a659234f-6aea-11e8-a361-000c29ed4cf4:1' |
|  211 | Query          |       100 |         399 | CREATE USER 'repl'@'192.168.100.%' IDENTIFIED WITH 'password'     |
|  399 | Gtid           |       100 |         460 | SET @@SESSION.GTID_NEXT= 'a659234f-6aea-11e8-a361-000c29ed4cf4:2' |
|  460 | Query          |       100 |         599 | GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.100.%'          |
|  599 | Gtid           |       100 |         660 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1' |
|  660 | Query          |       100 |         719 | BEGIN                                                             |
|  719 | View_change    |       100 |         858 | view_id=15294216022242634:1                                       |
|  858 | Query          |       100 |         923 | COMMIT                                                            |
|  923 | Gtid           |       100 |         984 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:2' |
|  984 | Query          |       100 |        1083 | create database gr_test                                           |
| 1083 | Gtid           |       100 |        1144 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3' |
| 1144 | Query          |       100 |        1243 | use `gr_test`; create table t1(id int)                            |
| 1243 | Gtid           |       100 |        1304 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:4' |
| 1304 | Query          |       100 |        1416 | use `gr_test`; create table t2(id int)engine=myisam               |
| 1416 | Gtid           |       100 |        1477 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:5' |
| 1477 | Query          |       100 |        1601 | use `gr_test`; create table t3(id int primary key)engine=myisam   |
| 1601 | Gtid           |       100 |        1662 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:6' |
| 1662 | Query          |       100 |        1773 | use `gr_test`; create table t4(id int primary key)                |
| 1773 | Gtid           |       100 |        1834 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:7' |
| 1834 | Query          |       100 |        1893 | BEGIN                                                             |
| 1893 | Table_map      |       100 |        1937 | table_id: 112 (gr_test.t4)                                        |
| 1937 | Write_rows     |       100 |        1973 | table_id: 112 flags: STMT_END_F                                   |
| 1973 | Xid            |       100 |        2000 | COMMIT /* xid=31 */                                               |
| 2000 | Gtid           |       100 |        2061 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:8' |
| 2061 | Query          |       100 |        2120 | BEGIN                                                             |
| 2120 | View_change    |       100 |        2299 | view_id=15294216022242634:2                                       |
| 2299 | Query          |       100 |        2364 | COMMIT                                                            |
+------+----------------+-----------+-------------+-------------------------------------------------------------------+

2.2.3 添加第三個節點

和加入s2節點幾乎一致。所以這裏做個步驟的簡單總結:

1.配置主機名和DNS解析

見2.配置單主模型。

2.提供配置文件,並啟動MySQL實例

datadir=/data
socket=/data/mysql.sock

server-id=120                      
gtid_mode=on                       
enforce_gtid_consistency=on        
log-bin=/data/master-bin           
binlog_format=row                  
binlog_checksum=none               
master_info_repository=TABLE       
relay_log_info_repository=TABLE    
relay_log=/data/relay-log          
log_slave_updates=ON               
sync-binlog=1                      
log-error=/data/error.log
pid-file=/data/mysqld.pid

transaction_write_set_extraction=XXHASH64         
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"  
loose-group_replication_start_on_boot=off        
loose-group_replication_local_address="192.168.100.23:20003"
loose-group_replication_group_seeds="192.168.100.21:20001,192.168.100.22:20002"

3.連上新實例,設置恢復通道的憑據。

change master to 
            master_user='repl',
            master_password='P@ssword1!'
            for channel 'group_replication_recovery';

4.安裝組復制插件,並啟動組復制功能。

install plugin group_replication soname 'group_replication.so';
start group_replication;

5.查看新節點是否已經處於ONLINE。

select * from performance_schema.replication_group_members\G

3.加組失敗:新節點一直處於recoveing?

當加入一個新節點時,一切配置都正確,但是新節點死活就是不同步數據,隨便執行一個語句都卡半天,查看performance_schema.replication_group_members表時,還發現這個新節點一直處於recovering裝態。

這時,請查看新節點的錯誤日誌。以下是我截取出來的一行。

[root@xuexi ~]# tail /data/error.log 
2018-06-19T17:41:22.314085Z 10 [ERROR] Plugin group_replication reported: 'There was an error when connecting to the donor server. Please check that group_replication_recovery channel credentials and all MEMBER_HOST column values of performance_schema.replication_group_members table are correct and DNS resolvable.'

很顯然,連接donor的時候出錯,讓我們檢測通道憑據,並且查看member_host字段的主機名是否正確解析。一切正確配置的情況下,通道憑據是沒錯的,錯就錯在member_host的主機名。

當和donor建立通道連接時,首先會通過member_host字段的主機名去解析donor的地址。這個主機名默認采取的是操作系統默認的主機名,而非ip地址。所以,必須設置DNS解析,或者/etc/hosts文件,將member_host對應的主機名解析為donor的ip地址。

我這裏之所以顯示錯誤,是因為我在測試環境下,所有節點的主機名都相同:xuexi.longshuai.com。所以新節點會將這個主機名解析到本機。

4.組復制維護:停止、重啟組復制功能

操作組復制的語句只有兩個。

start group_replication;
stop group_replication;

但是,和組復制相關的變量卻有好幾個。

當要停止組中的某個成員中的組復制功能時,需要在那個節點上執行stop group_replication語句。但一定要註意,在執行這個語句之前,必須要保證這個節點不會向外提供MySQL服務,否則有可能會有新數據寫入(例如主節點停止時),或者讀取到過期數據。

所以,要安全地重啟整個組,最佳方法是先停止所有非主節點的MySQL實例(不僅是停止組復制功能),然後停止主節點的MySQL實例,再先重啟主節點,在這個節點上引導組,並啟動它的組復制功能。最後再將各slave節點加入組。

如果只是想停止某單個節點,如果這個節點是主節點,那麽停止整個MySQL實例,如果是slave節點,那麽只需停止它的組復制功能即可。當它們需要再次加組時,只需執行start group_replication語句。

那麽,如何知道哪個節點是主節點?

5.查找復制組中的主節點

只有單主模型的組復制才需要查找主節點,多主模型沒有master/slave的概念,所以無需查找。

mysql> SELECT VARIABLE_VALUE FROM performance_schema.global_status 
       WHERE VARIABLE_NAME='group_replication_primary_member';
+--------------------------------------+
| VARIABLE_VALUE                       |
+--------------------------------------+
| a659234f-6aea-11e8-a361-000c29ed4cf4 |
+--------------------------------------+
1 row in set (0,00 sec)

或者:

mysql> SHOW STATUS LIKE 'group_replication_primary_member';

這樣查找只是獲取了主節點的uuid,可以表連接的方式獲取主節點主機名。

select b.member_host the_master,a.variable_value master_uuid
    from performance_schema.global_status a
    join performance_schema.replication_group_members b
    on a.variable_value = b.member_id
    where variable_name='group_replication_primary_member';
+------------------+--------------------------------------+
| the_master       | master_uuid                          |
+------------------+--------------------------------------+
| s1.longshuai.com | a659234f-6aea-11e8-a361-000c29ed4cf4 |
+------------------+--------------------------------------+

6.測試:組復制的自動選舉和容錯

目前,組中有3個節點:s1、s2和s3,其中s1是主節點。

現在將主節點直接關機或者斷掉網卡。

# s1上:
shell> ifconfig eth0 down

然後查看s2上的錯誤日誌。可以看到選舉新主節點的過程。

[Warning] group_replication reported: 'Member with address s1.longshuai.com:3306 has become unreachable.'
[Note] group_replication reported: '[GCS] Removing members that have failed while processing new view.'
[Warning] group_replication reported: 'Members removed from the group: s1.longshuai.com:3306'
[Note] group_replication reported: 'Primary server with address s1.longshuai.com:3306 left the group. Electing new Primary.'
[Note] group_replication reported: 'A new primary with address s2.longshuai.com:3306 was elected, enabling conflict detection until the new primary applies all relay logs.'
[Note] group_replication reported: 'This server is working as primary member.'
[Note] group_replication reported: 'Group membership changed to s2.longshuai.com:3306, s3.longshuai.com:3306 on view 15294358712349771:4.'

這裏將s2選為新的主節點,且告知成員視圖中目前組中成員變為s2和s3。

可以測試下,是否能向新的主節點s2中插入數據。

# s2上:
mysql> insert into gr_test.t4 values(333);

如果再將s3停掉呢?還能繼續寫入數據嗎?

# 在s3上:
shell> ifconfig eth0 down

回到s2,插入數據看看:

# s2上:
mysql> insert into gr_test.t4 values(3333); 

發現無法插入,一直阻塞。

查看下s2的錯誤日誌:

[Warning] group_replication reported: 'Member with address s3.longshuai.com:3306 has become unreachable.'
[ERROR] 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.'

已經說明了,s3移除後,組中的成員無法達到大多數的要求,所以將復制組給阻塞了。如果想要修復組,可以強制生成一個新的組成員視圖。

如果這時候,將s1和s3的網卡啟動,s1和s3還會加入到組中嗎?以下是s2上的錯誤日誌:

[Warning] group_replication reported: 'Member with address s3.longshuai.com:3306 is reachable again.'
[Warning] 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.'

發現s3加入了,但s1未加入。為什麽?因為s1節點上只是停掉了網卡,mysql實例以及組復制功能還在運行,而且它的角色還保持為主節點。這時候,s1和s2、s3已經出現了所謂的"網絡分裂",對於s2和s3來說,s1被隔離,對於s1來說,s2和s3被隔離。當s1的網卡恢復後,它仍然保留著自己的主節點運行,但因為它達不到大多數的要求,所以s1是被阻塞的,如果網卡長時間沒有恢復,則s1會被標記為ERROR。

這種情況下的s1,要讓它重新加入到組中,應該重啟組復制,更安全的方法是重啟mysql實例,因為組可能還沒有標記為ERROR,這個組暫時還存在,它與s2、s3所屬的組同名,可能會導致腦裂問題。

MySQL組復制(2):配置單主模型的組復制