1. 程式人生 > >MySQL之PXC叢集搭建

MySQL之PXC叢集搭建

一、PXC 介紹

1.1 PXC 簡介

PXC 是一套 MySQL 高可用叢集解決方案,與傳統的基於主從複製模式的叢集架構相比 PXC 最突出特點就是解決了詬病已久的資料複製延遲問題,基本上可以達到實時同步。而且節點與節點之間,他們相互的關係是對等的。PXC 最關注的是資料的一致性,對待事物的行為時,要麼在所有節點上執行,要麼都不執行,它的實現機制決定了它對待一致性的行為非常嚴格,這也能非常完美的保證 MySQL 叢集的資料一致性;

1.2 PXC特性和優點

  • 完全相容 MySQL。
  • 同步複製,事務要麼在所有節點提交或不提交。
  • 多主複製,可以在任意節點進行寫操作。
  • 在從伺服器上並行應用事件,真正意義上的並行複製。
  • 節點自動配置,資料一致性,不再是非同步複製。
  • 故障切換:因為支援多點寫入,所以在出現資料庫故障時可以很容易的進行故障切換。
  • 自動節點克隆:在新增節點或停機維護時,增量資料或基礎資料不需要人工手動備份提供,galera cluster會自動拉取線上節點資料,叢集最終會變為一致;

PXC最大的優勢:強一致性、無同步延遲

1.3 PXC的侷限和劣勢

  • 複製只支援InnoDB 引擎,其他儲存引擎的更改不復制
  • 寫入效率取決於節點中最慢的一臺

1.4 PXC與Replication的區別

Replication PXC
資料同步是單向的,master負責寫,然後非同步複製給slave;如果slave寫入資料,不會複製給master。 資料同步時雙向的,任何一個mysql節點寫入資料,都會同步到叢集中其它的節點。
非同步複製,從和主無法保證資料的一致性 同步複製,事務在所有叢集節點要麼同時提交,要麼同時不提交

1.5 PXC 常用埠

  • 3306:資料庫對外服務的埠號。
  • 4444:請求SST的埠。
  • 4567:組成員之間進行溝通的一個埠號
  • 4568:用於傳輸IST。

名詞解釋:

  • SST(State Snapshot Transfer): 全量傳輸
  • IST(Incremental state Transfer):增量傳輸

二、實踐

2.1 搭建 PXC 叢集

MySQL 不同的是 PXC 官方提供了 Docker

映象,所以我們可以很方便的搭建 PXC 叢集。

1)下載 Docker 映象

docker pull percona/percona-xtradb-cluster:5.7

2) 重新命名映象名稱

docker tag percona/percona-xtradb-cluster:5.7 pxc:5.7

3)刪除原始映象

docker rmi percona/percona-xtradb-cluster:5.7

4) 建立 Docker 網路,用於 PXC 叢集獨立使用

docker network create pxc-network

5) 建立資料卷用於之後掛載

docker volume create --name v1
docker volume create --name v2
docker volume create --name v3

注:PXC容器只支援資料卷掛載方式,不支援目錄掛載

6) 建立第一個節點

docker run -di --name=pn1 --net=pxc-network -p 9000:3306 -v v1:/var/lib/mysql --privileged -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=cluster1 -e XTRABACKUP_PASSWORD=123456  pxc:5.7 

因為後續節點的新增需要關聯到第一個節點,所以需要等待資料庫啟動完成。通過 docker logs pn1 檢視日誌,如果出現下面的輸出,證明啟動成功:

2019-09-04T06:27:30.085880Z 0 [Note] InnoDB: Buffer pool(s) load completed at 190904  6:27:30

注:CLUSTER_NAME 名稱不要用關鍵字PXC,否則無法啟動。

7) 加入第二個節點

docker run -di --name=pn2 --net=pxc-network -p 9001:3306 -v v2:/var/lib/mysql --privileged -e MYSQL_ROOT_PASSWORD=123456  -e CLUSTER_NAME=cluster1 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=pn1 pxc:5.7  

需要注意是第二個節點開始需要增加 e CLUSTER_JOIN=pn1 引數,表示與 pn1 節點同步,否則 pn1 容器會自動關閉。

PXC叢集中存在兩個節點以上之後就沒有主節點的概念了。叢集中最後一個退出的節點就會變為主節點,在 /var/lib/mysql/grastate.dat 檔案中屬性 safe_to_bootstrap 的值 會從 0 被設定為 1 它表示該節點是主節點。

8)加入第三個節點

docker run -di --name=pn3 --net=pxc-network -p 9002:3306 -v v3:/var/lib/mysql --privileged -e MYSQL_ROOT_PASSWORD=123456  -e CLUSTER_NAME=cluster1 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=pn2 pxc:5.7  

可以看到我們這次我們 CLUSTER_JOIN 的是 pn2 容器,可以證明我們剛剛說的 當 PXC 叢集存在兩個節點以上以上之後就沒有主節點的概念了這個說法是正確的。

9)進入 pn1 節點

docker exec -it pn1 /usr/bin/mysql -uroot -p123456

10) 檢視狀態

mysql> show status like 'wsrep%';
+----------------------------------+-------------------------------------------------+
| Variable_name                    | Value                                           |
+----------------------------------+-------------------------------------------------+
| wsrep_local_state_uuid           | 068dd5e8-cedd-11e9-904d-466e75bd8fe1            |
| wsrep_protocol_version           | 9                                               |
| wsrep_last_applied               | 16                                              |
| wsrep_last_committed             | 16                                              |
| wsrep_replicated                 | 0                                               |
| wsrep_replicated_bytes           | 0                                               |
| wsrep_repl_keys                  | 0                                               |
| wsrep_repl_keys_bytes            | 0                                               |
| wsrep_repl_data_bytes            | 0                                               |
| wsrep_repl_other_bytes           | 0                                               |
| wsrep_received                   | 10                                              |
| wsrep_received_bytes             | 800                                             |
| wsrep_local_commits              | 0                                               |
| wsrep_local_cert_failures        | 0                                               |
| wsrep_local_replays              | 0                                               |
| wsrep_local_send_queue           | 0                                               |
| wsrep_local_send_queue_max       | 1                                               |
| wsrep_local_send_queue_min       | 0                                               |
| wsrep_local_send_queue_avg       | 0.000000                                        |
| wsrep_local_recv_queue           | 0                                               |
| wsrep_local_recv_queue_max       | 2                                               |
| wsrep_local_recv_queue_min       | 0                                               |
| wsrep_local_recv_queue_avg       | 0.100000                                        |
| wsrep_local_cached_downto        | 0                                               |
| wsrep_flow_control_paused_ns     | 0                                               |
| wsrep_flow_control_paused        | 0.000000                                        |
| wsrep_flow_control_sent          | 0                                               |
| wsrep_flow_control_recv          | 0                                               |
| wsrep_flow_control_interval      | [ 173, 173 ]                                    |
| wsrep_flow_control_interval_low  | 173                                             |
| wsrep_flow_control_interval_high | 173                                             |
| wsrep_flow_control_status        | OFF                                             |
| wsrep_cert_deps_distance         | 0.000000                                        |
| wsrep_apply_oooe                 | 0.000000                                        |
| wsrep_apply_oool                 | 0.000000                                        |
| wsrep_apply_window               | 0.000000                                        |
| wsrep_commit_oooe                | 0.000000                                        |
| wsrep_commit_oool                | 0.000000                                        |
| wsrep_commit_window              | 0.000000                                        |
| wsrep_local_state                | 4                                               |
| wsrep_local_state_comment        | Synced                                          |
| wsrep_cert_index_size            | 0                                               |
| wsrep_cert_bucket_count          | 22                                              |
| wsrep_gcache_pool_size           | 1592                                            |
| wsrep_causal_reads               | 0                                               |
| wsrep_cert_interval              | 0.000000                                        |
| wsrep_open_transactions          | 0                                               |
| wsrep_open_connections           | 0                                               |
| wsrep_ist_receive_status         |                                                 |
| wsrep_ist_receive_seqno_start    | 0                                               |
| wsrep_ist_receive_seqno_current  | 0                                               |
| wsrep_ist_receive_seqno_end      | 0                                               |
| wsrep_incoming_addresses         | 172.19.0.2:3306,172.19.0.3:3306,172.19.0.4:3306 |
| wsrep_cluster_weight             | 3                                               |
| wsrep_desync_count               | 0                                               |
| wsrep_evs_delayed                |                                                 |
| wsrep_evs_evict_list             |                                                 |
| wsrep_evs_repl_latency           | 0/0/0/0/0                                       |
| wsrep_evs_state                  | OPERATIONAL                                     |
| wsrep_gcomm_uuid                 | 11ed51e2-cedd-11e9-b362-af453a7ac074            |
| wsrep_cluster_conf_id            | 3                                               |
| wsrep_cluster_size               | 3                                               |
| wsrep_cluster_state_uuid         | 068dd5e8-cedd-11e9-904d-466e75bd8fe1            |
| wsrep_cluster_status             | Primary                                         |
| wsrep_connected                  | ON                                              |
| wsrep_local_bf_aborts            | 0                                               |
| wsrep_local_index                | 0                                               |
| wsrep_provider_name              | Galera                                          |
| wsrep_provider_vendor            | Codership Oy <[email protected]>               |
| wsrep_provider_version           | 3.37(rff05089)                                  |
| wsrep_ready                      | ON                                              |
+----------------------------------+-------------------------------------------------+
71 rows in set (0.06 sec)

可以看到 wsrep_incoming_addresses 的值就是我們三個容器的IP地址

| wsrep_incoming_addresses         | 172.19.0.2:3306,172.19.0.3:3306,172.19.0.4:3306 |

叢集完整性檢查:

屬性 含義
wsrep_cluster_state_uuid 在叢集所有節點的值應該是相同的,有不同值的節點,說明其沒有連線入叢集.
wsrep_cluster_conf_id 正常情況下所有節點上該值是一樣的.如果值不同,說明該節點被臨時”分割槽”了.當節點之間網路連線恢復 的時候應該會恢復一樣的值.
wsrep_cluster_size 如果這個值跟預期的節點數一致,則所有的叢集節點已經連線.
wsrep_cluster_status 叢集組成的狀態.如果不為”Primary”,說明出現”分割槽”或是”split-brain”腦裂狀況.

節點狀態檢查:

屬性 含義
wsrep_ready 該值為 ON,則說明可以接受 SQL 負載.如果為 Off,則需要檢查 wsrep_connected
wsrep_connected 如果該值為 Off,且 wsrep_ready 的值也為 Off,則說明該節點沒有連線到叢集.(可能是 wsrep_cluster_address 或 wsrep_cluster_name 等配置錯造成的.具體錯誤需要檢視錯誤日誌)
wsrep_local_state_comment 如果 wsrep_connected 為 On,但 wsrep_ready 為 OFF,則可以從該項檢視原因

複製健康檢查:

屬性 含義
wsrep_flow_control_paused 表示複製停止了多長時間.即表明叢集因為 Slave 延遲而慢的程度.值為 0~1,越靠近 0 越好,值為 1 表示 複製完全停止.可優化 wsrep_slave_threads 的值來改善
wsrep_cert_deps_distance 有多少事務可以並行應用處理.wsrep_slave_threads 設定的值不應該高出該值太多
wsrep_flow_control_sent 表示該節點已經停止複製了多少次
*wsrep_local_recv_queue_avg 表示 slave 事務佇列的平均長度.slave 瓶頸的預兆. 最慢的節點的 wsrep_flow_control_sent 和 wsrep_local_recv_queue_avg 這兩個值最高.這兩個值較低的話,相對更好

檢測慢網路問題:

屬性 含義
wsrep_local_send_queue_avg 網路瓶頸的預兆.如果這個值比較高的話,可能存在網路瓶頸

衝突或死鎖的數目:

屬性 含義
wsrep_last_committed 最後提交的事務數目
wsrep_local_cert_failures 和 wsrep_local_bf_aborts 回滾,檢測到的衝突數目

2.2 叢集同步驗證

1) 在節點一上建立資料庫 test

mysql> create database test;
Query OK, 1 row affected (0.02 sec)

2) 節點二上檢視:

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
+--------------------+
5 rows in set (0.00 sec)

3) 在節點二上建立表

mysql> use test;
Database changed
mysql> create table sys_user(id int ,name varchar(30));
Query OK, 0 rows affected (0.11 sec)

4)在節點三上查看錶結構

mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| sys_user       |
+----------------+
1 row in set (0.00 sec)

5) 在節點三上插入資料

mysql> insert into sys_user values(1,'a');
ERROR 1105 (HY000): Percona-XtraDB-Cluster prohibits use of DML command on a table (test.sys_user) without an explicit primary key with pxc_strict_mode = ENFORCING or MASTER

看到沒有顯示的主鍵就無法插入資料,我們修改下表結構:

alter table sys_user add primary key (id);

插入資料:

mysql> insert into sys_user values(1,'a');
Query OK, 1 row affected (0.05 sec)

6)在節點一查看錶資料

mysql> select * from sys_user;
+----+------+
| id | name |
+----+------+
|  1 | a    |
+----+------+
1 row in set (0.00 sec)

可以看到三個節點資料正常同步,並且都可讀可寫。

2.3 新增資料庫節點操作

當資料庫不夠用時,我們通常需要增加資料庫節點來分擔壓力,我們來演示一下新增節點的操作。

1) 建立資料卷

docker volume create --name v4

2)新增容器

docker run -di --name=pn4 --net=pxc-network -p 9003:3306 -v v4:/var/lib/mysql --privileged -e MYSQL_ROOT_PASSWORD=123456  -e CLUSTER_NAME=cluster1 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=pn3 pxc:5.7 

要注意的是,這次 CLUSTER_JOIN 連的是 pn3

1) 進入節點4檢視資料

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
+--------------------+
5 rows in set (0.00 sec)
mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| sys_user       |
+----------------+
1 row in set (0.00 sec)
mysql> select * from sys_user;
+----+------+
| id | name |
+----+------+
|  1 | a    |
+----+------+
1 row in set (0.00 sec)

可以看到之前的資料也自動同步過來了。

2.4 宕機操作

1) 將節點pn4容器關閉,造成宕機現象

docker stop pn4

2) 在節點 pn2 上做檢視叢集狀態

mysql> show status like 'wsrep%';
......
| wsrep_local_state                | 4                                               |
| wsrep_local_state_comment        | Synced                                          |
| wsrep_cert_index_size            | 3                                               |
......
| wsrep_incoming_addresses         | 172.19.0.4:3306,172.19.0.3:3306,172.19.0.2:3306 |

可以看到叢集應該有4個節點,但是現在只有3個正常連線。

3)在節點 pn2 上做修改操作

mysql> update sys_user set name='b' where id=1; 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

4) 將節點 pn4 容器啟動

[root@VM_0_15_centos ~]# docker start pn4

5) 進入容器 pn4 檢視修改操作是否同步

docker exec -it pn4 /usr/bin/mysql -uroot -p123456
mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from sys_user;
+----+------+
| id | name |
+----+------+
|  1 | b    |
+----+------+
1 row in set (0.00 sec)

可以看到節點正常加入叢集,並且資料也同步了。

pn4 是以指定主節點形式進入 PXC 叢集建立的容器,那麼 pn1直接以自身為主節點啟動的容器會怎麼樣呢?我們來演示一下:

1) 關閉 pn1 節點

docker stop pn1

2) 在 pn2 節點上插入一條資料

mysql> insert into sys_user values('2','c');
Query OK, 1 row affected (0.01 sec)

3) 啟動 pn1節點

docker start pn1

等待一分鐘,檢視容器啟動列表

docker ps -a

發現 pn1 節點並沒有啟動

CONTAINER ID        IMAGE    ......   STATUS                           NAMES
fa123563e787        pxc:5.7  ......   Exited (1) About a minute ago    pn1               

檢視下錯誤日誌:

docker logs pn1

異常資訊如下:

2019-09-04T07:21:56.412918Z 0 [ERROR] WSREP: It may not be safe to bootstrap the cluster from this node. It was not the last one to leave the cluster and may not contain all the updates. To force cluster bootstrap with this node, edit the grastate.dat file manually and set safe_to_bootstrap to 1 .
2019-09-04T07:21:56.412922Z 0 [ERROR] WSREP: Provider/Node (gcomm://) failed to establish connection with cluster (reason: 7)
2019-09-04T07:21:56.412929Z 0 [ERROR] Aborting

翻譯成中文:

2019-09-04T07:21:56.412918Z 0 [錯誤] WSREP:從此節點引導群集可能不安全。 它不是離開群集的最後一個,可能不包含所有更新。 要使用此節點強制群集引導,請手動編輯grastate.dat檔案並將safe_to_bootstrap設定為1。
2019-09-04T07:21:56.412922Z 0 [錯誤] WSREP:提供者/節點(gcomm://)無法與群集建立連線(原因:7)
2019-09-04T07:21:56.412929Z 0 [錯誤]中止

錯誤提示很明顯了,因為 pn1 節點不是最後一個離開叢集的不能再以主節點的形式啟動了,如果要以主節點的形式啟動必須調整 grastate.dat檔案中的 safe_to_bootstrap 引數為 1

但是要注意的是因為叢集中其他節點並沒有關閉,這樣啟動的容器跟之前的叢集就沒有關係了資料也不會同步,我們來驗證下看看:

1) 檢視資料卷存放的路徑

docker volume inspect v1
[
    {
        "CreatedAt": "2019-09-05T09:22:22+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/v1/_data",
        "Name": "v1",
        "Options": {},
        "Scope": "local"
    }
]

2) 進入資料卷目錄,檢視是否存在 grastate.dat檔案

[root@VM_0_15_centos ~]# cd /var/lib/docker/volumes/v1/_data
[root@VM_0_15_centos _data]# ll
total 323444
-rw-r----- 1 1001 1001        56 Sep  5 08:34 auto.cnf
-rw------- 1 1001 1001      1680 Sep  5 08:34 ca-key.pem
-rw-r--r-- 1 1001 1001      1120 Sep  5 08:34 ca.pem
-rw-r--r-- 1 1001 1001      1120 Sep  5 08:34 client-cert.pem
-rw------- 1 1001 1001      1676 Sep  5 08:34 client-key.pem
-rw-r----- 1 1001 1001         2 Sep  5 08:34 fa123563e787.pid
-rw-r----- 1 1001 1001 134219048 Sep  5 09:22 galera.cache
-rw-r----- 1 1001 1001       113 Sep  5 09:21 grastate.dat
-rw-r----- 1 1001 1001      1300 Sep  5 08:34 ib_buffer_pool
-rw-r----- 1 1001 1001  79691776 Sep  5 09:15 ibdata1
-rw-r----- 1 1001 1001  50331648 Sep  5 09:15 ib_logfile0
-rw-r----- 1 1001 1001  50331648 Sep  5 08:34 ib_logfile1
-rw-r----- 1 1001 1001  12582912 Sep  5 08:38 ibtmp1
-rw-r----- 1 1001 1001     34751 Sep  5 08:38 innobackup.backup.log
drwxr-x--- 2 1001 1001      4096 Sep  5 08:34 mysql
drwxr-x--- 2 1001 1001      4096 Sep  5 08:34 performance_schema
-rw------- 1 1001 1001      1676 Sep  5 08:34 private_key.pem
-rw-r--r-- 1 1001 1001       452 Sep  5 08:34 public_key.pem
-rw-r--r-- 1 1001 1001      1120 Sep  5 08:34 server-cert.pem
-rw------- 1 1001 1001      1676 Sep  5 08:34 server-key.pem
drwxr-x--- 2 1001 1001     12288 Sep  5 08:34 sys
drwxr-x--- 2 1001 1001      4096 Sep  5 09:07 test
-rw-r--r-- 1 1001 1001       143 Sep  5 09:22 version_info
-rw-r----- 1 1001 1001   3932160 Sep  5 09:15 xb_doublewrite

3) 編輯檔案

vim grastate.dat

safe_to_bootstrap 引數值修改為1,儲存退出

# GALERA saved state
version: 2.1
uuid:    068dd5e8-cedd-11e9-904d-466e75bd8fe1
seqno:   20
safe_to_bootstrap: 1

4) 重啟 pn1 容器

docker start pn1

5) 進入容器,檢視資料

docker exec -it pn1 /usr/bin/mysql -uroot -p123456
mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from sys_user;
+----+------+
| id | name |
+----+------+
|  1 | b    |
+----+------+
1 row in set (0.01 sec)

發現數據並沒有同步,那麼要怎麼將 pn1 節點加入到叢集中呢?

我們可以直接將 pn1 容器刪除,以加入節點的形式重新建立容器,並且因為我們之前已經將容器的資料掛載到資料捲了,所以資料也不會存在丟失的風險,我們來操作下:

1) 刪除 pn1容器

docker stop pn1
docker rm pn1

2) 以從節點方式加入叢集

docker run -di --name=pn1 --net=pxc-network -p 9000:3306 -v v1:/var/lib/mysql --privileged -e MYSQL_ROOT_PASSWORD=123456  -e CLUSTER_NAME=cluster1 -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=pn2 pxc:5.7  

等待容器初始化完畢

3)進入容器,檢視資料是否同步

docker exec -it pn1 /usr/bin/mysql -uroot -p123456
mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from sys_user;
+----+------+
| id | name |
+----+------+
|  1 | b    |
|  2 | c    |
+----+------+
2 rows in set (0.00 sec)

發現數據已經同步了