MySQL之PXC集群搭建
- 2019 年 10 月 3 日
- 筆記
一、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 <info@codership.com> | | 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)
發現數據已經同步了。