PXC(Percona XtraDB Cluster)是什么?

PXC(Percona XtraDB Cluster)是一个开源的MySQL高可用解决方案。他将Percona Server和XtraBackup与Galera库集成,以实现同步多主复制。基于Galera的高可用方案主要有MariaDB Galera Cluster和Percona XtraDB Cluster,目前PXC架构在生产线上用的更多而且更成熟一些。PXC相比那些传统的基于主从模式的集群架构MHA和双主,Galera Cluster 最突出的特点就是解决了诟病已久的复制延迟问题,基本上可以达到实时同步。而且节点与节点之间,它们互相的关系是对等的。本身Galera Cluster也是一种多主架构。PXC是在存储引擎层实现的同步复制,而非异步复制,所以其数据的一致性是相当高的。


集群IP示例

集群别名

集群IP

集群角色

镜像

master

192.168.1.1

mysql-pxc:5.7

slave

192.168.1.2

mysql-pxc:5.7

arbiter

192.168.1.3

mysql-pxc:5.7


PXC优缺点

  • 优点:

    • 实现了MySQL集群的高可用性和数据的强一致性;

    • 完成了真正的多节点读写的集群方案;

    • 改善了主从复制延迟问题,基本上达到了实时同步;

    • 新加入的节点可以自动部署,无需提交手动备份,维护方便;

    • 由于是多节点写入,所以DB故障切换很容易。

  • 缺点:

    • 加入新节点时开销大。添加新节点时,必须从现有节点之一复制完整数据集。如果是100GB,则复制100GB。

    • 任何更新的事务都需要全局验证通过,才会在其他节点上执行,集群性能受限于性能最差的节点,也就说常说的木桶定律。

    • 因为需要保证数据的一致性,PXC采用的实时基于存储引擎层来实现同步复制,所以在多节点并发写入时,锁冲突问题比较严重。

    • 存在写扩大的问题。所以节点上都会发生写操作,对于写负载过大的场景,不推荐使用PXC。

    • 只支持InnoDB存储引擎。


部署前置服务*Docker


创建Mysql-Pxc数据盘

  • 部署所在服务器:master slave arbiter

docker volume create mysql-data 
docker volume create mysql-conf 
docker volume create mysql-back 

导入镜像

  • 部署所在服务器:master slave arbiter

docker  load < pxc.tar

启动初始化主服务等待加入集群

  • 部署所在服务器:master

    • 注:一定要等待容器及端口完全启动后才能进行下一步操作,启动过程可以使用 docker logs mysql-pxc -fn200 查看。

# 启动一个没有任何节点初始化专用的pxc

docker run -itd --privileged=true --restart=always --name mysql-pxc -v mysql-data:/var/lib/mysql -v mysql-conf:/etc/mysql -v mysql-back:/data -e MYSQL_ROOT_PASSWORD=mysqlpassword -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=mysqlpassword --net=host pxc:latest

启动从节点

  • 部署所在服务器:slave

# 获取主机名
hostname=$(cat /etc/hostname)

# 写入配置文件
cat << EOF > /var/lib/docker/volumes/mysql-conf/_data/node.cnf
[mysqld]
pxc_strict_mode=PERMISSIVE
ignore-db-dir=lost+found
datadir=/var/lib/mysql
socket=/tmp/mysql.sock
skip-host-cache

group_concat_max_len = 10240000
max_connections=65535
max_allowed_packet=500M
wait_timeout=2880000
interactive_timeout=2880000
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
thread_cache_size=64


#coredumper
#server_id=0
binlog_format=ROW
default_storage_engine=InnoDB

innodb_flush_log_at_trx_commit  = 0
innodb_flush_method             = O_DIRECT
innodb_file_per_table           = 1
innodb_autoinc_lock_mode=2

bind_address = 0.0.0.0

wsrep_slave_threads=2
wsrep_cluster_address=gcomm://192.168.1.1,192.168.1.2,192.168.1.3   #修改集群IP地址
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so

wsrep_cluster_name=PXC
wsrep_node_address=192.168.1.2   #加入集群的IP地址
wsrep_node_incoming_address=$hostname:3306 

wsrep_sst_method=xtrabackup-v2
wsrep_sst_auth='xtrabackup:mysqlpassword'

[client]
socket=/tmp/mysql.sock

[sst]
progress=/var/lib/mysql/sst_in_progress

EOF

# 赋权
mkdir -p /var/lib/docker/volumes/mysql-conf/_data/conf.d
chown 1001:docker /var/lib/docker/volumes/mysql-conf/_data/conf.d
chown 1001:docker /var/lib/docker/volumes/mysql-conf/_data/node.cnf
chmod 644 /var/lib/docker/volumes/mysql-conf/_data/node.cnf

# 启动容器
docker run -itd --privileged=true --restart=always --name mysql-pxc \
    -v mysql-data:/var/lib/mysql \
    -v mysql-conf:/etc/mysql \
    -v mysql-back:/data \
    -e MYSQL_ROOT_PASSWORD=mysqlpassword \
    -e CLUSTER_NAME=PXC \
    -e CLUSTER_JOIN=192.168.1.1,192.168.1.2,192.168.1.3 \
    -e XTRABACKUP_PASSWORD=mysqlpassword \
    --net=host \
    pxc:latest

启动仲裁节点

  • 部署所在服务器:arbiter

#!/bin/bash

# 获取主机名
hostname=$(cat /etc/hostname)

# 写入配置文件
cat << EOF > /var/lib/docker/volumes/mysql-conf/_data/node.cnf
[mysqld]
pxc_strict_mode=PERMISSIVE
ignore-db-dir=lost+found
datadir=/var/lib/mysql
socket=/tmp/mysql.sock
skip-host-cache

group_concat_max_len = 10240000
max_connections=65535
max_allowed_packet=500M
wait_timeout=2880000
interactive_timeout=2880000
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
thread_cache_size=64

#coredumper
#server_id=0
binlog_format=ROW
default_storage_engine=InnoDB

innodb_flush_log_at_trx_commit  = 0
innodb_flush_method             = O_DIRECT
innodb_file_per_table           = 1
innodb_autoinc_lock_mode=2

bind_address = 0.0.0.0

wsrep_slave_threads=2
wsrep_cluster_address=gcomm://192.168.1.1,192.168.1.2,192.168.1.3   #修改集群IP地址
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so

wsrep_cluster_name=PXC
wsrep_node_address=192.168.1.3   #加入集群的IP地址
wsrep_node_incoming_address=$hostname:3306

wsrep_sst_method=xtrabackup-v2
wsrep_sst_auth='xtrabackup:mysqlpassword'

[client]
socket=/tmp/mysql.sock

[sst]
progress=/var/lib/mysql/sst_in_progress

EOF

# 赋权
mkdir -p /var/lib/docker/volumes/mysql-conf/_data/conf.d
chown 1001:docker /var/lib/docker/volumes/mysql-conf/_data/conf.d
chown 1001:docker /var/lib/docker/volumes/mysql-conf/_data/node.cnf
chmod 644 /var/lib/docker/volumes/mysql-conf/_data/node.cnf

# 启动容器
docker run -itd --privileged=true --restart=always --name mysql-pxc \
    -v /etc/hosts:/etc/hosts \
    -v mysql-data:/var/lib/mysql \
    -v mysql-conf:/etc/mysql \
    -v mysql-back:/data \
    -e MYSQL_ROOT_PASSWORD=mysqlpassword \
    -e CLUSTER_NAME=PXC \
    -e CLUSTER_JOIN=192.168.1.1,192.168.1.2,192.168.1.3 \
    -e XTRABACKUP_PASSWORD=mysqlpassword \
    --net=host \
    pxc:latest

主节点重新加入集群

  • 部署所在服务器:master

#!/bin/bash

# 停止初始的容器
docker rm -f mysql-pxc

# 获取主机名
hostname=$(cat /etc/hostname)

# 写入配置文件
cat << EOF > /var/lib/docker/volumes/mysql-conf/_data/node.cnf
[mysqld]
pxc_strict_mode=PERMISSIVE
ignore-db-dir=lost+found
datadir=/var/lib/mysql
socket=/tmp/mysql.sock
skip-host-cache

group_concat_max_len = 10240000
max_connections=65535
max_allowed_packet=500M
wait_timeout=2880000
interactive_timeout=2880000
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
thread_cache_size=64


#coredumper
#server_id=0
binlog_format=ROW
default_storage_engine=InnoDB

innodb_flush_log_at_trx_commit  = 0
innodb_flush_method             = O_DIRECT
innodb_file_per_table           = 1
innodb_autoinc_lock_mode=2

bind_address = 0.0.0.0

wsrep_slave_threads=2
wsrep_cluster_address=gcomm://192.168.1.1,192.168.1.2,192.168.1.3  #修改集群IP地址
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so

wsrep_cluster_name=PXC 
wsrep_node_address=192.168.1.1     #加入集群的IP地址
wsrep_node_incoming_address=$hostname:3306

wsrep_sst_method=xtrabackup-v2
wsrep_sst_auth='xtrabackup:mysqlpassword'

[client]
socket=/tmp/mysql.sock

[sst]
progress=/var/lib/mysql/sst_in_progress

EOF

# 赋权
mkdir -p /var/lib/docker/volumes/mysql-conf/_data/conf.d
chown 1001:docker /var/lib/docker/volumes/mysql-conf/_data/conf.d
chown 1001:docker /var/lib/docker/volumes/mysql-conf/_data/node.cnf
chmod 644 /var/lib/docker/volumes/mysql-conf/_data/node.cnf

# 启动容器
docker run -itd --privileged=true --restart=always --name mysql-pxc \
    -v mysql-data:/var/lib/mysql \
    -v mysql-conf:/etc/mysql \
    -v mysql-back:/data \
    -e MYSQL_ROOT_PASSWORD=mysqlpassword \
    -e CLUSTER_NAME=PXC \
    -e CLUSTER_JOIN=192.168.1.1,192.168.1.2,192.168.1.3 \
    -e XTRABACKUP_PASSWORD=mysqlpassword \
    --net=host \
    pxc:latest

查看进群数量

docker exec -it mysql-pxc mysql -u root -p'mysqlpassword' -e "SHOW VARIABLES LIKE 'wsrep_cluster_size';"

Mysql-pxc集群常见问题处理

1. 节点A正常关闭

# 此时B、C会收到A退出集群的消息,B、C的集群属性(例如wsrep_cluster_size)、节点属性(例如wsrep_local_index)会自动变更,集群可正常提供服务。节点A重新启动后,会自动加入集群,集群属性随之变更。

2. 节点A、B正常关闭

# 此时集群规模减小到1,集群仍然可以正常提供服务。但是,当节点A/B重新启动后,节点C的状态将变为Donor/Desynced,因为C必须至少向首个加入集群的节点提供状态传输。

mysql> show status like 'wsrep_local_state_comment';

+---------------------------+--------+

| Variable_name             | Value  |

+---------------------------+--------+

| wsrep_local_state_comment | Donor/Desynced |

+---------------------------+--------+

1 row in set (0.69 sec)

3. 节点A、B、C依次正常关闭

## 此时集群完全关闭,节点关闭时会将其最后执行操作的序列号写入/var/lib/docker/volumes/mysql-conf/_data/grastate.dat,通过比较seqno的值,可以看出节点的关闭顺序(前提是节点关闭后集群仍有数据写入,否则各节点该值相同)。seqno值最大的节点,为最后一个关闭的节点,必须使用此节点启动集群。启动命令为:

docker start mysql-pxc

此外,最后一个正常关闭的节点grastate.dat文件中safe_to_bootstrap值会被置为1,也可通过该值来判断需要使用哪个节点来启动集群。

示例,在对集群持续写入数据的情况下,依次关闭A、B、C,此时各节点的grastate.dat文件如下:

节点A:

# GALERA saved state

version: 2.1

uuid: 8acc13d0-def3-11eb-ae7a-c7af3f0ad825

seqno: 1360

safe_to_bootstrap: 0

节点B:

# GALERA saved state

version: 2.1

uuid: 8acc13d0-def3-11eb-ae7a-c7af3f0ad825

seqno: 1361

safe_to_bootstrap: 0

节点C:

# GALERA saved state

version: 2.1

uuid: 8acc13d0-def3-11eb-ae7a-c7af3f0ad825

seqno: 1362

safe_to_bootstrap: 1

## 建议在完全关闭集群前停止对集群的写入,以便所有节点的seqno能够停在同一位置。否则低位节点重新启动时,必须完成完整的SST(State Snapshot Transfer)才能加入集群,集群启动速度变慢。

4. 节点A异常关闭

# 当集群中的某个节点因为断电、硬件故障、内核奔溃、进程奔溃、kill -9 mysql_pid等原因而异常关闭时,此时节点会无法将最后执行位置写入grastate.dat,seqno值为运行时值-1,如下:

# GALERA saved state

version: 2.1

uuid: 8acc13d0-def3-11eb-ae7a-c7af3f0ad825

seqno: -1

safe_to_bootstrap: 0

5. 节点A、B异常关闭

由于A、B异常关闭,剩余的节点C无法单独形成法定人数,集群将变为non primary状态。此时节点C上的mysql进程仍在运行并可以连接,但所有涉及数据的SQL查询都将报错:

mysql> select * from worker;

ERROR 1047 (08S01): WSREP has not yet prepared node for application use

此时通过 docker start mysql-pxc 将无法正常启动A、B,必须手动指定节点C为主节点,才可拉起A、B方法如下(节点C执行):

mysql> SET GLOBAL wsrep_provider_options='pc.bootstrap=true';

注意,此方法仅在其余节点异常关闭时才可使用,否则将会产生两个不同的集群。

6. 节点A、B、C均异常关闭

如遇数据中心断电、mysql/Galera发生致命错误导致所有节点均异常关闭,集群的数据一致性可能受到损坏,各个节点的grastate.dat都未能更新,显示为:

# GALERA saved state

version: 2.1

uuid: 8acc13d0-def3-11eb-ae7a-c7af3f0ad825

seqno: -1

safe_to_bootstrap: 0

此时无法通过该文件来判断哪一个节点是最后关闭的,也就无法判断以哪一个节点为主节点启动集群。

6.1 方法一

在这种情况下,首先需要通过如下命令检查各个节点的最后一次数据提交操作的序列号,结果示例如下:
节点A:

[root@test1 ~]# docker exec -it mysql-pxc mysqld_safe --wsrep-recover

---> mysqld_safe WSREP: Recovered position 8acc13d0-def3-11eb-ae7a-c7af3f0ad825:1364


节点B:

[root@test2 ~]# mysqld_safe --wsrep-recover

---> mysqld_safe WSREP: Recovered position 8acc13d0-def3-11eb-ae7a-c7af3f0ad825:1365


节点C:

[root@test3 ~]# mysqld_safe --wsrep-recover

---> mysqld_safe WSREP: Recovered position 8acc13d0-def3-11eb-ae7a-c7af3f0ad825:1365

#### 可以看到,节点B、C的序列号值相等且大于A,代表这两个节点是最后关闭的,因为我们可以指定B、C中任意一个为主节点启动集群,修改其grastate.dat文件中safe_to_bootstrap值为1,随后按正常步骤启动集群即可(因为PXC集群的节点默认开机自启,服务器异常重启时mysql进程可能自启并卡死,因此启动前需要先kill掉卡死的mysql进程)。

节点B:

docker start mysql-pxc

节点C:

docker start mysql-pxc

节点A:

docker start mysql-pxc

6.2 方法二

## 如果使用PXC 5.6.19以上版本,还可以通过/data/mysql/gvwstate.dat文件来判断节点关闭前的集群状态。

## 新版本及以后新增了一个pc.recovery选项(https://www.percona.com/doc/percona-xtradb-cluster/5.7/wsrep-provider-index.html#pc.recovery),该选项默认启用,用于保存primary集群状态到每个节点的gvwstate.dat文件中。在上述示例中,各个节点的gvwstate.dat文件内容如下:

## 节点A:

[root@test1 mysql]# more gvwstate.dat

my_uuid: 052b3a03-e072-11eb-b713-e3fd5bcb270c

#vwbeg

view_id: 3 052b3a03-e072-11eb-b713-e3fd5bcb270c 15

bootstrap: 0

member: 052b3a03-e072-11eb-b713-e3fd5bcb270c 0

member: 6c9f69d0-e072-11eb-80bf-ce66d30836b1 0

member: 969e273e-e072-11eb-98eb-eaa0cbaec88b 0

#vwend

## 节点B:

[root@test2 mysql]# more gvwstate.dat

my_uuid: 969e273e-e072-11eb-98eb-eaa0cbaec88b

#vwbeg

view_id: 3 6c9f69d0-e072-11eb-80bf-ce66d30836b1 16

bootstrap: 0

member: 6c9f69d0-e072-11eb-80bf-ce66d30836b1 0

member: 969e273e-e072-11eb-98eb-eaa0cbaec88b 0

#vwend

## 节点C:

[root@test3 mysql]# more gvwstate.dat

my_uuid: 6c9f69d0-e072-11eb-80bf-ce66d30836b1

#vwbeg

view_id: 3 6c9f69d0-e072-11eb-80bf-ce66d30836b1 16

bootstrap: 0

member: 6c9f69d0-e072-11eb-80bf-ce66d30836b1 0

member: 969e273e-e072-11eb-98eb-eaa0cbaec88b 0

#vwend

## 文件释义如下:

my_uuid: 本节点的UUID

#vwbeg 视图信息开始标志

view_id: [view_type] [view_uuid] [view_seq],view_type始终为3代表primary视图,view_seq表示视图操作序列号,值越大表示该视图的数据越新

bootstrap: 0或1,但不影响pc.recovery过程

member: [node's uuid] [node's segment],表示节点关闭时的集群中所有正常的节点

member:

member:

#vwend 试图信息结束标志
通过查看该文件,可以得出与方法一同样的结论,即B、C节点的数据最新,可作为主节点启动集群。
上述示例中,节点A、B、C异常关闭的时间不同,且不停有数据写入,因此各个节点的wsrep的位置(操作序列号)不同,集群无法自动恢复。
如果发生例如数据中心断电、ABC节点同时异常关闭的情况,此时ABC节点的wsrep位置相同,则恢复供电、ABC服务器启动后,集群可自动恢复,无需人工干预。
这也是pc.recovery特性的一个限制,即节点位置必须相同才可自动恢复。
  • 上述示例中,节点A、B、C异常关闭的时间不同,且不停有数据写入,因此各个节点的wsrep的位置(操作序列号)不同,集群无法自动恢复。

  • 如果发生例如数据中心断电、ABC节点同时异常关闭的情况,此时ABC节点的wsrep位置相同,则恢复供电、ABC服务器启动后,集群可自动恢复,无需人工干预。

  • 这也是pc.recovery特性的一个限制,即节点位置必须相同才可自动恢复。