Loading... ## 简介 `PXC` 的全称是 `Percona XtraDB Cluster`,是基于 `Galera` 的 `MySQL` 高可用解决方案,由于是基于 `Galera`,所以 `PXC` 也具有 `Galera` 的特性。 PXC 集群特点: - 多主集群,即每个节点都可以读写数据 - 同步复制,事务在所有节点要么都提交,要么都不提交 - 同步强一致性,受益于**同步复制**,所有节点的数据都是强一致的,不会像 MySQL 自带的 `replication` 那样存在数据不一致的情况。 - 集群中的节点数量可以动态增加或减少,但建议节点数为奇数,如 3、5、7 等,这样可以避免脑裂。 - 因为是同步强一致性,根据木桶原理,集群的性能取决于最慢的节点,所以也不建议节点数过多,因为会带来同步压力。 - binlog_format 必须为 `ROW`。 > 本文不建议跳着看,因为前面的配置会相对简单一些,是为了让大家先对 PXC 有一个大致的了解,避免刚开始就看到复杂的配置而放弃。但简单的配置会有一些坑,后面会增加配置一一解决。 ## 集群搭建 (单机多节点) ### 创建网络 为了方便集群内部通信,我们需要创建一个 docker 网络,这样集群内部的容器就可以通过容器名互相访问了。 ```bash docker network create pxc_net ``` ### 创建 node1 ```bash mkdir -p ./node1/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node1 docker run -d --name=node1 \ --net=pxc_net \ -p 9000:3306 \ -v ./node1/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node1/data/:/var/lib/mysql/ \ -v ./node1/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ percona/percona-xtradb-cluster:5.7 docker logs -f node1 ``` - 第一行创建了一个目录,用于存放 `node1` 的数据、配置文件等,其中 `data` 目录用于存放 `MySQL` 数据,`conf.d` 目录用于存放 `MySQL` 配置文件,`cluster.conf.d` 目录用于存放 `PXC` 集群相关配置文件。 - 第二行将目录的所有者改为 `1001:1001`,这是因为 `PXC` 的容器内部的 `MySQL` 进程是以 `1001` 用户运行的,如果不改,会导致容器内部无法写入数据。 - 第三行创建了一个容器,其中: - `-name` 用于指定容器名,这里是 `node1`。 - `-net` 用于指定容器使用的网络,我们设置为了 `pxc_net`。 - `-p` 用于指定容器对外暴露的端口,这里是将容器的 `3306` 端口映射到主机的 `9000` 端口,这样我们就可以通过主机的 `9000` 端口访问容器内部的 `MySQL` 服务了。 - `-v` 用于挂载目录,避免容器重建后数据丢失。 - `-e` 用于设置环境变量,这里设置了时区,`MySQL` 的 `root` 用户密码和集群名称。 - `percona/percona-xtradb-cluster:5.7` 是 `PXC` 的镜像名称和版本。这里先使用 5.7 版本,因为 8.0 版本的 `PXC` 配置要多一些步骤,后面会介绍。 - 第四行查看容器日志,如果容器启动成功,会输出 `[Note] InnoDB: Buffer pool(s) load completed at xxx`,按 `Ctrl + C` 退出日志查看。 ### 创建 node2 待 `node1` 启动成功,并尝试通过工具连接 `MySQL` 服务成功后,我们再创建 `node2`,这很重要,如果 `node1` 还未完全启动,`node2` 将无法加入集群。 ```bash mkdir -p ./node2/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node2 docker run -d --name=node2 \ --net=pxc_net \ -p 9001:3306 \ -v ./node2/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node2/data/:/var/lib/mysql/ \ -v ./node2/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e CLUSTER_JOIN=node1 \ percona/percona-xtradb-cluster:5.7 docker logs -f node2 ``` 这里和创建 `node1` 的命令基本一致,做了以下修改: - 修改目录、容器名从 `node1` 改为 `node2`。 - 修改端口从 `9000` 改为 `9001`。 - 新增一个环境变量 `CLUSTER_JOIN`,用于指定加入哪个节点的集群,这里我们指定为 `node1`,这样 `node2` 就会加入 `node1` 的集群了。(两者的 `CLUSTER_NAME` 要一致) 其他的保持不变。 ### 创建 node3 `PXC` 建议集群节点数为奇数,所以我们再创建一个 `node3`。(是为了避免发生脑裂,如偶数个节点 node1 node2 node3 node4,如果出现一种情况 node1 和 node2 通信正常,node3 和 node4 通信正常,其他节点之间互相通信不正常,这样就会导致 node1 和 node2 形成一个集群,node3 和 node4 形成一个集群,大家都以为是一个集群,但实际上是两个集群,这就是脑裂。) ```bash mkdir -p ./node3/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node3 docker run -d --name=node3 \ --net=pxc_net \ -p 9002:3306 \ -v ./node3/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node3/data/:/var/lib/mysql/ \ -v ./node3/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e CLUSTER_JOIN=node1 \ percona/percona-xtradb-cluster:5.7 docker logs -f node3 ``` 这里和创建 `node2` 的命令基本一致,做了以下修改: - 修改目录、容器名从 `node2` 改为 `node3`。 - 修改端口从 `9001` 改为 `9002`。 环境变量中 `CLUSTER_JOIN` 这里也指定的 `node1`,但也可以指定 `node2`,因为 `node2` 已经加入了 `node1` 的集群 (前提 `node2` 已经启动成功,而不是也正在加入集群中。) ### 验证集群 接下来你可以在集群中任意一个节点上建库、建表、插入数据,然后在其他节点上查看,你会发现数据是同步的。 还可以通过以下命令查看集群状态: ```bash mysql> show status like 'wsrep_cluster_%'; +--------------------------+--------------------------------------+ | Variable_name | Value | +--------------------------+--------------------------------------+ | wsrep_cluster_weight | 3 | | wsrep_cluster_conf_id | 3 | | wsrep_cluster_size | 3 | | wsrep_cluster_state_uuid | acf9cb67-bfe4-11ee-8173-d6bb71ba1416 | | wsrep_cluster_status | Primary | +--------------------------+--------------------------------------+ 5 rows in set (0.00 sec) ``` 其中 `wsrep_cluster_size` 表示集群中节点的数量,其他的可以参考 [官方文档](https://www.percona.com/doc/percona-xtradb-cluster/5.7/wsrep-status-index.html)。 通过以下命令查看所处集群名称,当前节点 join 入集群时的节点: ```bash mysql> show variables like 'wsrep_cluster_%'; +-----------------------+---------------+ | Variable_name | Value | +-----------------------+---------------+ | wsrep_cluster_address | gcomm://node1 | | wsrep_cluster_name | pxc-cluster1 | +-----------------------+---------------+ 2 rows in set (0.00 sec) ``` 注意这里的 `wsrep_cluster_address` 其实就是我们在 docker 启动时设置的环境变量 `CLUSTER_JOIN`,`node1` 作为第一个节点启动的,所以没有配置 `CLUSTER_JOIN`,那么这里会显示 `gcomm://`。 这个值表示启动时从哪个地址连接到集群,如不指定端口,默认是 `4567`,可以写多个地址,用逗号分隔,如 `gcomm://node1,node2,node3`,这样就可以从这三个节点的 `4567` 端口连接到集群。 ### 关闭集群节点 在 `PXC` 中,节点停止时会给其他节点发送通知,最后一个节点停止时,会将配置文件 `/var/lib/mysql/grastate.dat` 中的 `safe_to_bootstrap` 设置为 `1`,表示可以**将该节点作为主节点安全启动(准确的说是 `引导节点`)**。 因为最后一个退出的节点,会包含更完整的数据,所以我们的所有节点都停止后,应**先启动最后一个退出的节点**,然后再启动其他节点。如果你弄错了不小心将 `safe_to_bootstrap=0` 的节点作为第一个启动了也没关系,`PXC` 可以检测到这种情况,启动时报错: ```bash [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 . ``` 注意我们前面强调的是全部停止的情况下启动第一个节点,如果集群中有节点正在运行,且要启动的节点 `wsrep_cluster_address` 中包含了正在运行的节点,那么他的 `safe_to_bootstrap=0` 也没事,因为他不会作为主节点(引导节点),而是会从正在运行的节点同步数据,这样也是安全的。 如果节点是异常退出的,没来得及将 `safe_to_bootstrap` 设置为 `1`,或者 `safe_to_bootstrap=1` 的节点数据丢了,你只能手动将你认为数据最全节点的 `safe_to_bootstrap` 设置为 `1`,然后启动他,再启动其他节点会从他同步数据。 那么 `safe_to_bootstrap` 和 `wsrep_cluster_address` 的关系是什么呢?我们可以这样理解: - 如果 `safe_to_bootstrap=0`,表示他不是最后一个退出的节点,那么他的 `wsrep_cluster_address` 中**必须包含其他节点的信息**,因为他需要从其他节点同步数据,如果不包含其他节点的信息,或包含的节点他无法连接上,将无法启动成功。 - 如果 `safe_to_bootstrap=1`,表示他是最后一个退出的节点,会作为主节点(引导节点)启动,那么他不需要从其他节点同步数据,启动时也就不会读取 `wsrep_cluster_address` 的值(即使配置了)。 ### 坑 1:节点重启后无法加入集群 你可能会在网上看到其他文章中写到 **`PXC` 中不区分主从节点,只要有两个或以上个节点,就都是主节点**,然后你看到上一节 **关闭节点**,既然不分主从节点,如果所有节点退出时,最后一个节点先启动,如果有正在运行的节点,其他节点可以随意启停,实际上不是这样的,或者说在我们现在的配置下不是这样的,你可能会遇到报错,无法重启节点。 回顾下前面,我们创建 node1 没有指定 `CLUSTER_JOIN`,node2 和 node3 的时指定了 `CLUSTER_JOIN=node1`,可以在各个节点上执行: `show variables like 'wsrep_cluster_%';` 查看到: - node1: `wsrep_cluster_address=gcomm://` - node2: `wsrep_cluster_address=gcomm://node1` - node3: `wsrep_cluster_address=gcomm://node1` 场景 1:**node1 在运行时,重启 node2 或 node3**:可以正常重启,因为他们的 `wsrep_cluster_address` 中指向的是 `node1`,`node1` 还在运行中,所以他们可以正常重启。 场景 2:**node1 停止,node2 和 node3 保持运行**:这时 `node2` 和 `node3` 之间其实是可以正常通信的,即使他们的 `wsrep_cluster_address` 中指向的 `node1` 已经停止了,因为他们的缓存中是包含所有节点的信息的,但如果这时 node2 或 node3 重启,当前节点缓存中数据丢失,根据上面的参数,他们启动时会从 `node1` 的 `4567` 端口连接到集群,但 `node1` 已经停止了,这时就会因为连接不到 `node1` 导致无法加入集群而启动失败。 场景 3:**node1 重启,node2 和 node3 保持运行**:同理,`node2` 和 `node3` 之间是可以正常通信的,但 `node1` 无法重启成功,因为不是最后一个退出集群的,`safe_to_bootstrap` 为 `0` 表示他不是作为主节点(引导节点)启动,也就是需要 join 到 `wsrep_cluster_address` 里配置的节点,但 `node1` 这个参数不包含其他节点,无法连接到集群,启动失败。 场景 4:**node2 和 node3 先停止,最后停止 node1**:这时 `node1` 的 `safe_to_bootstrap` 会被设置为 `1`,表示可以作为主节点启动,虽然他 `wsrep_cluster_address` 中不包含其他节点的信息,但是他此时是作为主节点启动的,也就是说他不需要连接到集群,所以他可以正常启动,然后再启动 `node2` 和 `node3`,这样整个集群就恢复了。 所以在当前的配置中,我们无法随意的启动节点,因为 `node1` 的 `wsrep_cluster_address` 为空,但我们最开始初始化集群时他作为第一个节点本就不应配置也不知道其他节点,而且 docker 容器的 -e 配置的环境变量无法修改,无法再给他加上 `CLUSTER_JOIN`。 这很不方便,但幸运的是有解决办法,还记得我们最开始创建容器时挂载的目录吗? ```bash -v ./node1/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d ``` 我们可以在 `./node1/cluster.conf.d/` 目录下创建一个 `cluster.cnf` 文件,内容如下: ```bash [mysqld] wsrep_cluster_address=gcomm://node1,node2,node3 ``` 这里可以不包含当前节点自身,不过加上也不影响,`PXC` 不会自己咬自己尾巴(不会从当前节点作为来源同步数据)。 同理,我们也可以在 `./node2/cluster.conf.d/` 和 `./node3/cluster.conf.d/` 目录下创建一个 `cluster.cnf` 文件,配置上所有节点的信息,这样我们就可以随意启动节点了。 ### 节点自动发现 上面的配置虽然解决了节点重启后无法加入集群的问题,但是如果我们要扩容或缩容集群,还是需要手动修改所有节点的配置文件,如果你像我一样是**懒人**,那么可以按照以下步骤配置节点自动发现: 目前 `PXC` 只支持 `etcd` 作为自动发现的后端,所以我们需要先安装 `etcd`,这里我们使用 `docker` 安装 `etcd`。 ```bash docker run -d --name pxc-etcd \ --net=pxc_net \ -e ETCD_ENABLE_V2=true \ -e ETCD_ROOT_PASSWORD='j99*v#3xu%GP4@Kv#!' \ bitnami/etcd:latest ``` 这里我们使用 `bitnami/etcd:latest` 镜像创建一个名为 `pxc-etcd` 的容器,`--net=pxc_net` 表示使用我们之前创建的 pxc 网络,这样他们才能互相通信,`ETCD_ENABLE_V2=true` 表示启用 `etcd` 的 `v2` 版本的 API(pxc 用的是 v2 的 api),`ETCD_ROOT_PASSWORD` 配置了 root 用户的密码,但实际上我们不会用到,这里配置是因为该容器要求必须配置,不然无法启动。(这里我们没有使用 -p 参数对外暴露端口,因为不需要,只要 `pxc_net` 这个网络内各个容器可以互相访问即可) 然后就不需要配置 `cluster.cnf` 了,我们只需要在启动容器时增加个环境变量 `DISCOVERY_SERVICE`,指定 `etcd` 的地址即可,如:`-e DISCOVERY_SERVICE=pxc-etcd:2379`,这样 PXC 节点启动时会自动将自身注册到 `etcd` 中,并获取集群其他节点地址,然后加入集群。 修改后各个节点的启动命令如下: node1: ```bash mkdir -p ./node1/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node1 docker run -d --name=node1 \ --net=pxc_net \ -p 9000:3306 \ -v ./node1/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node1/data/:/var/lib/mysql/ \ -v ./node1/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:5.7 docker logs -f node1 ``` node2: ```bash mkdir -p ./node2/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node2 docker run -d --name=node2 \ --net=pxc_net \ -p 9001:3306 \ -v ./node2/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node2/data/:/var/lib/mysql/ \ -v ./node2/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:5.7 docker logs -f node2 ``` node3: ```bash mkdir -p ./node3/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node3 docker run -d --name=node3 \ --net=pxc_net \ -p 9002:3306 \ -v ./node3/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node3/data/:/var/lib/mysql/ \ -v ./node3/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8112424SPEsU124#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:5.7 docker logs -f node3 ``` 可以通过以下命令查看 `etcd` 中集群的信息: ```bash CONTAINER_NAME="pxc-etcd" CLUSTER_NAME="pxc-cluster1" PXC_ETCD_PID=$(docker inspect -f "<ruby>.State.Pid}}" $CONTAINER_NAME) nsenter -t $PXC_ETCD_PID -n curl http<rp> (</rp><rt>//127.0.0.1:2379/v2/keys/pxc-cluster/$CLUSTER_NAME?recursive=true ``` 简化为一行是: ```bash CONTAINER_NAME="pxc-etcd" && CLUSTER_NAME="pxc-cluster1" && nsenter -t $(docker inspect -f "{{.State.Pid</rt><rp>) </rp></ruby>" $CONTAINER_NAME) -n curl http://127.0.0.1:2379/v2/keys/pxc-cluster/$CLUSTER_NAME?recursive=true ``` ### 坑 2:节点自动发现的 IP 逐渐增多 虽然我们现在配置了集群自动发现,但是你查看 etcd 或者节点的 `wsrep_cluster_address` 可能会发现,每次重启节点后,集群中的节点 IP 都会增加,这是因为现在我们的 docker 容器没有指定 IP,所以每次重启后,节点都会将自身 IP 添加到集群中,导致 etcd 中的 IP 逐渐增多。 解决办法是给容器指定 IP,即增加 `--ip` 参数,但配置这个参数需要在 docker network 创建时指定 IP 范围,所以我们需要先删除之前创建的 `pxc_net` 网络和依赖该网络的容器,然后重新创建一个指定 IP 范围的网络。 ```bash docker rm -f node1 node2 node3 pxc-etcd # 删除容器 rm -rf ./node1 ./node2 ./node3 # 删除目录 docker network rm pxc_net # 删除网络 docker network create --subnet=172.10.0.0/16 pxc_net # 创建网络, 指定 IP 范围 ``` `etcd` 可以不指定 IP,仍然是之前的命令不变: ```bash docker run -d --name pxc-etcd --net=pxc_net -e ETCD_ENABLE_V2=true -e ETCD_ROOT_PASSWORD='j99*v#3xu%GP4@Kv#!' bitnami/etcd:latest ``` 但是启动 pxc 节点容器时增加 `--ip` 参数,如: node1: ```bash mkdir -p ./node1/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node1 docker run -d --name=node1 \ --net=pxc_net \ --ip=172.10.0.101 \ -p 9000:3306 \ -v ./node1/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node1/data/:/var/lib/mysql/ \ -v ./node1/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:5.7 docker logs -f node1 ``` node2: ```bash mkdir -p ./node2/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node2 docker run -d --name=node2 \ --net=pxc_net \ --ip=172.10.0.102 \ -p 9001:3306 \ -v ./node2/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node2/data/:/var/lib/mysql/ \ -v ./node2/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:5.7 docker logs -f node2 ``` node3: ```bash mkdir -p ./node3/{data,conf.d,cluster.conf.d} chown -R 1001:1001 ./node3 docker run -d --name=node3 \ --net=pxc_net \ --ip=172.10.0.103 \ -p 9002:3306 \ -v ./node3/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node3/data/:/var/lib/mysql/ \ -v ./node3/conf.d/:/etc/mysql/conf.d/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:5.7 docker logs -f node3 ``` ### 坑 3:etcd 中的节点信息不会自动清理 虽然现在我们的集群可以自动发现,但是不会自动清理 etcd 中的节点信息,所以如果我们删除了某个节点,etcd 中的节点信息不会自动删除。这可能会带来问题,比如我们使用 `172.10.0.100` 创建了容器第一个节点,然后我们后悔了,想删掉这个节点,然后使用 `172.10.0.200` 作为第一个节点,但是这时 etcd 中已经有了 `172.10.0.100` 的节点信息,这是会尝试加入到这个 ip 所在的集群中,但是显然是不可能加入成功的,这时就会启动失败报错。 官方也给了我们解决办法,就是自己删除 etcd 中的节点信息,官方说明地址为: [Percona XtraDB Cluster docker image](https://hub.docker.com/r/percona/percona-xtradb-cluster),内容为: ```bash Currently, there is no automatic cleanup for the discovery service registry. You can remove all entries using curl http://$ETCD_HOST/v2/keys/pxc-cluster/$CLUSTER_NAME?recursive=true -XDELETE. ``` 按照官方的提示我们可以使用以下命令删除 etcd 中的节点信息: ```bash CONTAINER_NAME="pxc-etcd" CLUSTER_NAME="pxc-cluster1" PXC_ETCD_PID=$(docker inspect -f "<ruby>.State.Pid}}" $CONTAINER_NAME) nsenter -t $PXC_ETCD_PID -n curl -XDELETE http<rp> (</rp><rt>//127.0.0.1:2379/v2/keys/pxc-cluster/$CLUSTER_NAME?recursive=true ``` 简化为一行是: ```bash CONTAINER_NAME="pxc-etcd" && CLUSTER_NAME="pxc-cluster1" && nsenter -t $(docker inspect -f "{{.State.Pid</rt><rp>) </rp></ruby>" $CONTAINER_NAME) -n curl -XDELETE http://127.0.0.1:2379/v2/keys/pxc-cluster/$CLUSTER_NAME?recursive=true ``` 如果不想清空所有节点信息,可以使用以下命令删除指定 IP 节点信息: ```bash CONTAINER_NAME="pxc-etcd" CLUSTER_NAME="pxc-cluster1" NODE_IP="172.10.0.101" PXC_ETCD_PID=$(docker inspect -f "<ruby>.State.Pid}}" $CONTAINER_NAME) nsenter -t $PXC_ETCD_PID -n curl -XDELETE http<rp> (</rp><rt>//127.0.0.1:2379/v2/keys/pxc-cluster/$CLUSTER_NAME/$NODE_IP?dir=true ``` 简化为一行是: ```bash CONTAINER_NAME="pxc-etcd" && CLUSTER_NAME="pxc-cluster1" && NODE_IP="172.10.0.101" && nsenter -t $(docker inspect -f "{{.State.Pid</rt><rp>) </rp></ruby>" $CONTAINER_NAME) -n curl -XDELETE http://127.0.0.1:2379/v2/keys/pxc-cluster/$CLUSTER_NAME/$NODE_IP?dir=true ``` ## 集群搭建 (多机多节点) 前面我们是在一台机器上创建了多个节点,如果我们想在多台机器上创建多个节点就要先解决跨机器之间容器的通信问题。 > 如有两台机器 machine1 和 machine2,都创建了 docker 网络 docker network create pxc_net,然后在 machine1 上创建了容器 node1,machine2 上创建了容器 node2,按照我们现在的配置 node1 和 node2 是无法通信的,因为他们之间没有任何关联,在这里只是恰好同名而已。 ### Docker Swarn Overlay Network 实现跨机器容器通信 我们可以通过 docker 的 `overlay` 网络来解决这个问题,使用 `overlay` 网络需要在 `docker swarm` 模式下,将 machine1 和 machine2 加入到同一个 `swarm` 中,然后创建一个 `overlay` 网络,这样 machine1 和 machine2 上的容器就可以互相通信了。 首先在其中一个机器上初始化 `swarm`: ```bash docker swarm init --advertise-addr 10.0.0.246 ``` - `-listen-addr`:表示 Docker Swarm 监听的`网络接口`,默认是 `0.0.0.0:2377`。 - `-advertise-addr`:广播地址,也就是允许其他节点通过哪个`网络接口`加入该 swarm 集群,主要用于机器上有多个网卡的情况,如果只有一个网卡,可以不指定,会使用 `-listen-addr` 的值。但如果有多个网卡,就必须指定一个地址,且需保证这个地址在 `listen-addr` 监听的网卡/IP + 端口上。 - `-data-path-addr`:数据域地址,服务器之间的流量通过哪个`网络接口`发送,也可以使用此参数将容器的数据流量和集群之间的管理流量分开,非必填,默认使用 `-advertise-addr` 中的 `网络接口`,端口使用 `4789`。 上面三个选项说的 `网络接口` 可以是 `IP` 也可以是网卡名,如 `eth0`。都可以不写端口,`--listen-addr` 和`--advertise-addr` 默认是 `2377`,`--data-path-addr` 默认是 `4789`。 执行完会返回一个命令: ```bash Swarm initialized: current node (weize2oucyzb9rcbmebx4amxz) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-02cdwgs8mhu5mm9c20z4y1gzaou4de4n8gtkxi1afmq4ozogqs-38g6tm7oe2v9g1mepwrhj4qab 10.0.0.246:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions. ``` 可以在其他机器上执行这个 `docker swarm join` 命令,将其他机器以 worker 身份加入到该 swarm 集群中,如果你忘记了,可以通过以下命令查看: - `docker swarm join-token manager`: 查询以 manager 节点身份到当前 swarm 用的命令 - `docker swarm join-token worker`: 查询以 worker 节点身份到当前 swarm 用的命令 > 这些 swarm、worker、manager 等概念可以参考 Docker Swarm 官方文档,但注意不要陷进太多细节中,先按照我的提示说明操作跑起来再去了解这些细节也不迟。 > 执行上面提示的命令,将其他机器加入到 swarm 集群: ```bash ~ docker swarm join --token SWMTKN-1-02cdwgs8mhu5mm9c20z4y1gzaou4de4n8gtkxi1afmq4ozogqs-38g6tm7oe2v9g1mepwrhj4qab 10.0.0.246:2377 This node joined a swarm as a worker. ``` 然后在 `manager` 节点上执行 `docker node ls` 可以查看到当前 swarm 集群中的节点信息(带 * 的表示当前节点): ```bash ~ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION weize2oucyzb9rcbmebx4amxz * vm1 Ready Active Leader 25.0.0 331o8d403cl0t8c7kjtd5c8qx vm2 Ready Active 25.0.0 ``` 然后我们就可以在 `manager` 节点创建一个 `overlay` 网络了: ```bash docker network create --driver overlay --subnet=172.10.0.0/16 --attachable pxc_net_overlay ``` 相较于之前创建的 `pxc_net` 网络,这里多了一个 `--driver overlay` 参数,表示创建一个 `overlay` 网络,`overlay` 网络是 `swarm` 模式下的网络,可以跨机器通信,但他默认限制了只有通过 docker swarm 创建的容器才能加入到该网络中,所以我们需要在创建容器时增加 `--attachable` 参数,表示允许通过 `docker run` 命令将容器加入到该网络中。 然后在新建 `PXC` 节点容器时,使用 `--net=pxc_net_overlay` 指定使用 `pxc_net_overlay` 网络,这样就可以跨机器通信了,步骤和之前在单机多节点一样,这里不再赘述。 ## PXC 8.0 流量加密 PXC 8.0 有个比较大的区别是默认开启了 [**`pxc-encrypt-cluster-traffic=ON`**](https://docs.percona.com/percona-xtradb-cluster/8.0/upgrade-guide.html#traffic-encryption-is-enabled-by-default),这表示默认使用加密方式进行流量传输,需要配置下证书或者关闭这个选项。 ### 流量加密 **1. 需要先创建证书,如:** ```bash mkdir -m 777 -p ./cert docker run --name pxc-cert --rm -v ./cert/:/cert/ percona/percona-xtradb-cluster:8.0 mysql_ssl_rsa_setup -d /cert/ ``` 这样会生成证书在 `./cert/` 目录下: ```bash -rw------- 1 1001 1001 1679 4月23日 20:56 ca-key.pem -rw-r--r-- 1 1001 1001 1115 4月23日 20:56 ca.pem -rw-r--r-- 1 1001 1001 1115 4月23日 20:56 client-cert.pem -rw------- 1 1001 1001 1675 4月23日 20:56 client-key.pem -rw------- 1 1001 1001 1675 4月23日 20:56 private_key.pem -rw-r--r-- 1 1001 1001 451 4月23日 20:56 public_key.pem -rw-r--r-- 1 1001 1001 1115 4月23日 20:56 server-cert.pem -rw------- 1 1001 1001 1679 4月23日 20:56 server-key.pem ``` **2. 创建一个配置文件 `ssl.cnf`,内容如下:** ```bash [mysqld] ssl-ca = /cert/ca.pem ssl-cert = /cert/server-cert.pem ssl-key = /cert/server-key.pem [client] ssl-ca = /cert/ca.pem ssl-cert = /cert/client-cert.pem ssl-key = /cert/client-key.pem [sst] encrypt = 4 ssl-ca = /cert/ca.pem ssl-cert = /cert/server-cert.pem ssl-key = /cert/server-key.pem ``` 3. 将配置文件和证书放到节点目录下,然后启动时挂载到容器中: ```bash mkdir -p ./node1/{data,conf.d,cluster.conf.d,cert} # 这里多了一个 cert 目录 cp ./cert/* ./node1/cert/ # 假设第一步生成的证书在 ./cert/ 目录下,复制到节点的 cert 目录下 cp ./ssl.cnf ./node1/conf.d/ssl.cnf # 将配置文件复制到节点的 conf.d 目录下 chown -R 1001:1001 ./node1 docker run -d --name=node1 \ --net=pxc_net \ --ip=172.10.0.101 \ -p 9000:3306 \ -v ./node1/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node1/data/:/var/lib/mysql/ \ -v ./node1/conf.d/:/etc/mysql/conf.d/ \ -v ./node1/cert/:/cert/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:8.0 docker logs -f node1 ``` 4. 再启动其他节点,如 node2 和 node3,同样需要将证书和配置文件放到节点目录下,然后启动时挂载到容器中。 > 注意:集群内的节点需使用同一份证书,不能每个节点单独生成证书。 node2: ```bash mkdir -p ./node2/{data,conf.d,cluster.conf.d,cert} # 这里多了一个 cert 目录 cp ./cert/* ./node2/cert/ # 假设第一步生成的证书在 ./cert/ 目录下,复制到节点的 cert 目录下 cp ./ssl.cnf ./node2/conf.d/ssl.cnf # 将配置文件复制到节点的 conf.d 目录下 chown -R 1001:1001 ./node2 docker run -d --name=node2 \ --net=pxc_net \ --ip=172.10.0.102 \ -p 9001:3306 \ -v ./node2/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node2/data/:/var/lib/mysql/ \ -v ./node2/conf.d/:/etc/mysql/conf.d/ \ -v ./node2/cert/:/cert/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:8.0 docker logs -f node2 ``` node3: ```bash mkdir -p ./node3/{data,conf.d,cluster.conf.d,cert} # 这里多了一个 cert 目录 cp ./cert/* ./node3/cert/ # 假设第一步生成的证书在 ./cert/ 目录下,复制到节点的 cert 目录下 cp ./ssl.cnf ./node3/conf.d/ssl.cnf # 将配置文件复制到节点的 conf.d 目录下 chown -R 1001:1001 ./node3 docker run -d --name=node3 \ --net=pxc_net \ --ip=172.10.0.103 \ -p 9002:3306 \ -v ./node3/cluster.conf.d/:/etc/percona-xtradb-cluster.conf.d \ -v ./node3/data/:/var/lib/mysql/ \ -v ./node3/conf.d/:/etc/mysql/conf.d/ \ -v ./node3/cert/:/cert/ \ -e MYSQL_ROOT_PASSWORD='8e7T8SPEsU#iDq*fgt' \ -e TZ=Asia/Shanghai \ -e CLUSTER_NAME=pxc-cluster1 \ -e DISCOVERY_SERVICE=pxc-etcd:2379 \ percona/percona-xtradb-cluster:8.0 docker logs -f node3 ``` 5. 可以通过命令查看流量加密是否开启: ```sql mysql> show variables like 'pxc_encrypt_cluster_traffic'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | pxc_encrypt_cluster_traffic | ON | +-----------------------------+-------+ 1 row in set (0.00 sec) ``` ### 关闭流量加密 关闭流量加密需要在配置文件中添加 `pxc-encrypt-cluster-traffic=OFF`,如: ```bash [mysqld] pxc-encrypt-cluster-traffic = OFF ```` > 注意:关闭流量加密可能会导致数据泄露,不建议在生产环境中关闭流量加密。且如果关闭所有节点都需要关闭,否则会导致节点之间无法通信。 ## PXC 集群原理 ### PXC 中重要端口 - `3306`: 数据库对外服务的端口号 - `4444`: 请求 SST(State Snapshot Transfer 全量同步),在新节点加入时起做用来同步所有数据。 - `4567`: 组成员之间沟通的端口 - `4568`: 传输 IST(Incremental State Transfer 增量同步),节点下线,重启加入时同步缺失的部分数据。 ### PXC 中集群的状态 可通过命令查看集群状态: ```sql show status like 'wsrep_cluster_status'; ``` ### PXC 中集群节点的状态 可通过命令查看节点状态: ```sql show status like 'wsrep_local_state_comment'; ``` - `open`:节点启动成功,尝试连接到集群。 - `primary`:节点已在集群中,在新节点加入集群时,选取 donor 进行数据同步时会产生式的状态。 - `joiner`:节点处于等待接收同步数据文件的状态。 - `joined`:节点已完成了数据同步,尝试保持和集群中其它节点进度一致。 - `synced`:节点正常提供服务的状态,表示已经同步完成并和集群进度保持一致。 - `donor`:节点处于为新加入节点提供全量数据时的状态。 ### 如何选择 donor 节点 官方提供了一篇博客来解释如何选择 donor 节点:[Understanding how an IST donor is selected](https://www.percona.com/blog/understanding-ist-donor-selected/) 简单总结下: 1. 所有节点在其 `gcache` 中保留一些写入集合作为缓存。 2. 当一个节点需要重新加入集群时,它需要与集群状态进行重新同步。 3. 重新加入的节点将检查当前集群的最新状态,确定自己需要的写入集合范围。 4. 节点开始寻找潜在的 DONOR,如果一个节点的范围包含所需的写入集合范围,则该节点可以作为DONOR。 5. 如果没有找到合适的DONOR节点,重启的节点将采用全量状态传输(SST)来与集群重新同步。 还有一篇个人的博客可以参考下: [Percona XtraDB Cluster 5.6 寻找合适的 IST 捐赠者](https://blog.wu-boy.com/2014/02/percona-xtradb-cluster-5-6-ist-and-sst/) > docker compose 2.21.0 以上版本可能存在无法自动发现 swarm overlay 网络的问题,需要安装旧版本,查看当前源所有版本命令 `sudo apt list -a docker-compose-plugin`,安装指定版本 `sudo apt install docker-compose-plugin=2.21.0-1~debian.12~bookworm -y` ## 参考 - [Percona XtraDB Cluster 官方文档 5.7](https://www.percona.com/doc/percona-xtradb-cluster/5.7/index.html) - [Percona XtraDB Cluster 官方文档 8.0](https://www.percona.com/doc/percona-xtradb-cluster/8.0/index.html) - [PXC 集群使用 - 个人博客](https://mynamelancelot.github.io/database/PXC.html) - [mysql pxc集群 原理](https://www.cnblogs.com/crazymakercircle/p/15344207.html#autoid-h3-9-1-0) - [Understanding how an IST donor is selected](https://www.percona.com/blog/understanding-ist-donor-selected/) - [Percona XtraDB Cluster 5.6 寻找合适的 IST 捐赠者](https://blog.wu-boy.com/2014/02/percona-xtradb-cluster-5-6-ist-and-sst/) 最后修改:2024 年 08 月 12 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请我喝杯咖啡吧。