MySQL高可用之組複製技術(2):配置單主模型的組複製

MySQL組複製系列文章:php

  1. MySQL組複製大綱
  2. MySQL組複製(1):組複製技術簡介
  3. MySQL組複製(2):配置單主模型的組複製
  4. MySQL組複製(3):配置多主模型的組複製
  5. MySQL組複製(4):組複製理論透徹分析

MySQL的組複製能夠配置爲單主模型多主模型兩種工做模式,它們都能保證MySQL的高可用。如下是兩種工做模式的特性簡介:html

  • 單主模型:從複製組中衆多個MySQL節點中自動選舉一個master節點,只有master節點能夠寫,其餘節點自動設置爲read only。當master節點故障時,會自動選舉一個新的master節點,選舉成功後,它將設置爲可寫,其餘slave將指向這個新的master。
  • 多主模型:複製組中的任何一個節點均可以寫,所以沒有master和slave的概念,只要忽然故障的節點數量不太多,這個多主模型就能繼續可用。

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

而使用單主模型的組複製就簡單的太多了,惟一須要知道的就是它會自動選舉master節點這個特性,由於它的維護一切都是自動進行的,甚至對於管理人員來講,徹底能夠不用去了解組複製的理論。算法

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

1.單主模型組複製的理論基礎

雖然說組複製的單主模型很簡單,但有必要了解一點和單主模型有關的理論,儘管不瞭解也沒什麼問題,畢竟一切都是自動的。shell

以下圖,master節點爲s1,其他爲slave節點。數據庫

組複製一切正常時,全部的寫操做都路由到s1節點上,全部的讀操做都路由到s二、s三、s4或s5上。當s1節點故障後,組複製自動選舉新的master節點。假如選舉s2爲新master成功後,s三、s4和s5將指向s2,寫操做將路由到s2節點上。bootstrap

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

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時會聯繫s一、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組複製理論透徹分析,或者閱讀我對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.22 全新實例
s3 MySQL 5.7.22 192.168.100.23 192.168.100.23 全新實例

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

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

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

# s1上:
hostnamectl set-hostname --static s1.longshuai.com
hostnamectl -H root@192.168.100.22 set-hostname s2.longshuai.com
hostnamectl -H root@192.168.100.23 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_member_weigth = 40   # 非必需,mysql 5.7.20纔開始支持該選項
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).最後的6行是組複製插件的配置。以loose_開頭表示即便啓動組複製插件,MySQL也繼續正常容許下去。這個前綴是可選的。
  • (8).倒數第6行表示寫集合以XXHASH64的算法進行hash。所謂寫集,是對事務中所修改的行進行的惟一標識,在後續檢測併發事務之間是否修改同一行衝突時使用。它基於主鍵生成,因此使用組複製,表中必需要有主鍵。
  • (9).倒數第5行表示這個複製組的名稱。它必須是一個有效的UUID值。嫌能夠直接和上面同樣全寫字母a。在Linux下,可使用uuidgen工具來生成UUID值。
[root@xuexi ~]# uuidgen
09c38ef2-7d81-463e-bdb4-9459b2c0e49b
  • (10).倒數第4行表示組複製功能不隨MySQL實例啓動而啓動。雖然,能夠將組複製插件和啓動組複製功能的選項寫在配置文件裏,但強烈建議不要如此,而是每次手動去配置。
  • (11).倒數第3行表示該節點在組中的權重爲40。權重越高,自動選舉爲primary節點的優先級就越高。
  • (12).倒數第2行表示本機上用於組內各節點之間通訊的地址和端口
  • (13).最後一行,設置本組的種子節點。種子節點的意義在前文已經解釋過了。

如今配置文件已經提供。能夠啓動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的通道(通道名固定,不可以使用自定義通道)進行數據恢復的,通過數據恢復後,新節點填充了它缺失的那部分數據,這樣就和組內其餘節點的數據保持了同步。

這個恢復過程比較複雜,它是一種分佈式恢復。本文不介紹這個,在MySQL組複製理論透徹分析中,我對此作了詳細的說明。

執行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線程,在組複製中,再也不稱之爲io_thread和sql_thread,取而代之的是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去應用。

並非說組複製中沒有io線程和sql線程,而是稱呼改變了,receiver和applier實際上就是io_therad和sql_thread。

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能插入成功,t一、t二、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不是事件,不會寫入到binlog中,因此不會複製到其它節點上,換句話說,某節點Purge後,那麼它的binlog和其它節點的binlog是不一致的)。

因此,在新節點加入組以前,應該先經過備份恢復的方式,從組中某節點上備份目前的數據到新節點上,而後再讓新節點去加組,這樣加組的過程將很是快,且能保證不會由於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_member_weigth = 20   # 非必需,mysql 5.7.20纔開始支持該選項
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_member_weigth = 30
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.加組失敗

3.1 新節點一直處於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。因此新節點會將這個主機名解析到本機。

3.2 新節點包含了額外的gtid事務?

若是新節點中包含了額外的數據,例如,新節點上多了一個用戶,建立這個用戶是會產生gtid事務的,當這個節點要加入到組時會報錯。如下是error.log中的內容:

2018-06-24T12:56:29.300453Z 0 [ERROR] Plugin group_replication reported: 'This member has more executed transactions than those present in the gro
up. Local transactions: 48f1d8aa-7798-11e8-bf9a-000c29296408:1-2 > Group transactions: 481024ff-7798-11e8-89da-000c29ff1054:1-4,
bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1-6'
2018-06-24T12:56:29.300536Z 0 [ERROR] Plugin group_replication reported: 'The member contains transactions not present in the group. The member wi
ll now exit the group.'

錯誤已經指明瞭是新節點的事務和組中的事務不匹配,有多餘的事務,從而致使加組失敗。

若是你已經明確這些多餘的事務形成的數據不會影響到組中的節點,正如多了一個可能永遠也用不上的用戶,或者多了幾個和組複製徹底無關的數據庫。

這時,能夠將這些可有可無的gtid給刪掉,可是想刪除這些gtid還真沒那麼容易。purge日誌不行,停掉MySQL後刪日誌文件也不行,把binlog關掉再打開也不行。它們都會把之前的事務記錄到Previous_gtid中。真正行之有效的方法是將全局變量executed_gtid設置爲空。方法爲:

mysql> reset master;

而後,再去加組。

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.測試:組複製的自動選舉和容錯

組複製中,有兩種節點離組的狀況:自願離組、非自願離組。

  1. 自願離組:執行stop group_replication;語句。
    • (1).執行該語句表示該節點自願離組,它會觸發視圖自動配置,並將該視圖更改操做複製到組內全部節點,直到大多數節點都贊成新的視圖配置,該節點纔會離組。
    • (2).節點自願離組時,不會丟失法定票數。因此不管多少個節點自願離組,都不會出現"達不到大多數"的要求而阻塞組。
    • (3).舉個例子,5個節點的組,自願退出一個節點A後,這個組的大小爲4。這個組認爲節點A歷來都沒有出現過。
  2. 非自願離組:除了上面自願離組的狀況,全部離組的狀況都是非自願離組。好比節點宕機,斷網等等。
    • (1).節點非自願離組時,故障探測機制會檢測到這個問題,因而向組中報告這個問題。而後會觸發組視圖成員自動配置,須要大多數節點贊成新視圖。
    • (2).非自願離組時,組的大小不會改變,不管多少個節點的組,節點非自願退出後,組大小仍是5,只不過這些離組的節點被標記爲非ONLINE。但注意,組的視圖配置會改變,由於離組的節點狀態須要標記爲非ONLINE。
    • (3).非自願離組時,會丟失法定票數。因此,當非自願離組節點數量過多時,致使組中剩餘節點數量達不到大多數的要求,組就會被阻塞。
    • (4).舉個例子,5節點的組,非自願退出1個節點A後,這個組的大小仍是5,可是節點A在新的視圖中被標記爲unreachable或其餘狀態。當繼續非自願退出2個節點後,組中只剩下2個ONLINE節點,這時達不到大多數的要求,組就會被阻塞。

目前,組中有3個節點:s一、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和s二、s3已經出現了所謂的"網絡分裂",對於s2和s3來講,s1被隔離,對於s1來講,s2和s3被隔離。當s1的網卡恢復後,它仍然保留着本身的主節點運行,但由於它達不到大多數的要求,因此s1是被阻塞的,若是網卡長時間沒有恢復,則s1會被標記爲ERROR。

這種狀況下的s1,要讓它從新加入到組中,應該重啓組複製,更安全的方法是重啓mysql實例,由於組可能尚未標記爲ERROR,這個組暫時還存在,它與s二、s3所屬的組同名,可能會致使腦裂問題。

若是是自願離組呢?能夠測試下,不管自願退出多少個節點,只要組中還有節點,組都不會被阻塞。

相關文章
相關標籤/搜索