Haproxy+PXC搭建Mysql集群

安装环境

centos7, docker

集群方案介绍

大型网站数据库需要应对高负载、实现高可用。而单节点数据库在并发量大的情况下无法满足性能要求,且一台宕机,整个服务受影响。

  • Mysql集群:PXC
  • 负载均衡:Haproxy
  • 高可用:Keepalived
高可用Mysql集群方案

搭建pxc集群

安装pxc镜像

docker官方仓库(https://hub.docker.com)搜索 percona-xtradb-cluster,拉取镜像命令为: docker pull percona/percona-xtradb-cluster
终端执行:

// 安装pxc镜像
➜ docker pull percona/percona-xtradb-cluster
// 查看镜像
➜ docker images
REPOSITORY                                        TAG                 IMAGE ID            CREATED             SIZE
docker.io/percona/percona-xtradb-cluster        latest              f3abd21f393a        3 days ago          72.1 MB
// 镜像名称太长,为方便使用,修改一下
➜ docker tag docker.io/percona/percona-xtradb-cluster pxc
// 删除原镜像
docker rmi docker.io/percona/percona-xtradb-cluster

创建Docker内部网络

出于安全考虑,不要把pxc容器的IP直接暴露出去,而是创建docker内部网络。

// 网段subnet自定义,网段名自定义为network20
➜ docker network create --subnet=172.20.0.0/16 network20
2e9196b0637074cad609be8e4e890aa3e889209af8c2b55ce8097981000bf53f
// 查看网段详细信息
➜ docker network inspect network20

创建Docker卷

docker的使用原则之一:不要在容器内保存业务数据,而应该保存在宿主机中
实现方式:通过目录映射,把宿主机的目录映射到容器内,运行容器时,把数据保存在映射目录中,也就是保存在宿主机中。当容器发生故障时,只需把故障容器删除掉,重新启动一个容器,把宿主机的目录映射给新容器。
pxc运行在容器中,无法直接使用映射目录,会发生闪退。需要借助Docker卷完成映射。

// 创建一个docker卷,自定义名称为 volume-mysql-1
➜ docker volume create --name volume-mysql-1
// 可以查看数据卷在宿主机上当真实目录
➜ docker volume inspect volume-mysql-1
[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/volume-mysql-1/_data", // 数据卷在宿主机真实的路径
        "Name": "volume-mysql-1", // 数据卷名称
        "Options": {},
        "Scope": "local"
    }
]

创建PXC容器

  • 命令分解
// -d 创建出的容器后台运行
docker run -d
// -p 端口映射,[宿主机端口]:[容器端口]
-p 3311:3306
// -e 启动参数,MYSQL_ROOT_PASSWORD数据库root密码
-e MYSQL_ROOT_PASSWORD=123456
// CLUSTER_NAME集群名称
-e CLUSTER_NAME=pxc-test 
// XTRABACKUP_PASSWORD数据库间节点同步密码
-e XTRABACKUP_PASSWORD=123456 
// -v 目录映射,[宿主机目录(数据卷)]:[容器目录]
-v volume-mysql-1:/var/lib/mysql
// 最高权限
--privileged
// --name 容器名称
--name mysql-node1
// --net 给容器指定网段,指定IP
--net network20 --ip 172.20.0.2 
// 镜像名
pxc

执行指令创建第一个pxc节点

// 创建第一个pxc节点 mysql-node1
➜ docker run -d -p 3311:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=pxc-test -e XTRABACKUP_PASSWORD=123456 -v volume-mysql-1:/var/lib/mysql --privileged --name mysql-node1 --net network20 --ip 172.20.0.2 pxc

注意:容器创建很快就会完成,但是容器内mysql数据库的初始化大概需要2分钟左右,一定要等到mysql初始化完成后,再创建第2个容器,否则第2个容器会发生闪退! 判断mysql-node1是否完成初始化,可以在宿主机终端执行指令:docker exec -it mysql-node1 mysql -uroot -p,看是否连接成功。

创建第2-5个pxc节点,与第1个节点略有区别:

  • 修改映射宿主机端口号(-p)
  • 修改容器名称 (--name)
  • 修改映射的数据卷 (-v)
  • 修改分配的IP地址 (--ip)
  • 增加一个参数用于加入集群,和第一个pxc节点进行同步: -e CLUSTER_JOIN=mysql-node1
  • 除第1个容器以外,其余mysql初始化时间非常短,无需等待
// 创建pxc容器 mysql-node2
➜ docker run -d -p 3312:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=pxc-test -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=mysql-node1 -v volume-mysql-2:/var/lib/mysql --privileged --name mysql-node2 --net network20 --ip 172.20.0.3 pxc
// 创建pxc容器 mysql-node3
➜ docker run -d -p 3313:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=pxc-test -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=mysql-node1 -v volume-mysql-3:/var/lib/mysql --privileged --name mysql-node3 --net network20 --ip 172.20.0.4 pxc
// 创建pxc容器 mysql-node4
➜ docker run -d -p 3314:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=pxc-test -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=mysql-node1 -v volume-mysql-4:/var/lib/mysql --privileged --name mysql-node4 --net network20 --ip 172.20.0.5 pxc
// 创建pxc容器 mysql-node5
➜ docker run -d -p 3315:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=pxc-test -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=mysql-node1 -v volume-mysql-5:/var/lib/mysql --privileged --name mysql-node5 --net network20 --ip 172.20.0.6 pxc

检测pxc集群的数据同步

可在宿主机上直接登陆某个容器的mysql客户端;也可以使用mysql可视化工具,如Navicat等远程登陆,远程登陆的需要防火墙开放端口。
以下演示宿主机终端验证方法:

// 登陆mysql-node1节点的mysql终端,创建db=demo,table=user
➜ docker exec -it mysql-node1 mysql -uroot -p
mysql> create database demo;
mysql> use demo
Database changed
Query OK, 1 row affected (0.02 sec)
mysql> create table student(id int unsigned not null auto_increment, name varchar(50) not null default '', primary key (id)) engine=innodb;
Query OK, 0 rows affected (0.04 sec)
mysql> exit
Bye

// 退出mysql-node1,登陆mysql-node2
➜ docker exec -it mysql-node2 mysql -uroot -p
mysql> use demo
Database changed
mysql> show tables;
+----------------+
| Tables_in_demo |
+----------------+
| student        |
+----------------+
1 row in set (0.01 sec)
// 其它节点同mysql-node2

任意节点写入新数据,其它节点都会同步。至此,pxc集群搭建完成。

haproxy配置负载均衡

负载均衡简单介绍

负载均衡可以使应用数据均匀的落在pxc集群的每一个节点上。如果不使用负载均衡,单节点处理所有请求,会导致该节点负载高、性能差;其它节点却空闲浪费。
haproxy做负载均衡器,它不是数据库,只是一个转发件。


负载均衡器

Docker安装Haproxy镜像

// 拉取镜像
➜ docker pull haproxy
// 查看镜像
➜ docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
docker.io/haproxy           latest              f3abd21f393a        3 days ago          72.1 MB
pxc                         latest              70b3670450ef        4 weeks ago         408 MB

创建haproxy配置文件

// 宿主机上创建haproxy配置文件,路径自己指定,之后创建容器会用到
➜ touch /home/softconf/haproxy/haproxy.cfg
// haproxy.conf写入下列内容,具体配置需要修改
global
    #工作目录
    chroot /usr/local/etc/haproxy
    #日志文件,使用rsyslog服务中local5日志设备(/var/log/local5),等级info
    log 127.0.0.1 local5 info
    #守护进程运行
    daemon

defaults
    log global
    mode    http
    #日志格式
    option  httplog
    #日志中不记录负载均衡的心跳检测记录
    option  dontlognull
    #连接超时(毫秒)
    timeout connect 5000
    #客户端超时(毫秒)
    timeout client  50000
    #服务器超时(毫秒)
    timeout server  50000

#监控界面
listen  admin_stats
    #监控界面的访问的IP和端口
    bind  0.0.0.0:8888
    #访问协议
    mode        http
    #URI相对地址
    stats uri   /dbs
    #统计报告格式
    stats realm     Global\ statistics
    #登陆帐户信息
    stats auth  admin:admin123456
#数据库负载均衡
listen  proxy-mysql
    #访问的IP和端口
    bind  0.0.0.0:3306
    #网络协议
    mode  tcp
    #负载均衡算法(轮询算法)
    #轮询算法:roundrobin
    #权重算法:static-rr
    #最少连接算法:leastconn
    #请求源IP算法:source
    balance  roundrobin
    #日志格式
    option  tcplog
    #在MySQL中创建一个没有权限的haproxy用户,密码为空。Haproxy使用这个账户对MySQL数据库心跳检测
    option  mysql-check user haproxy
    server  MySQL_1 172.20.0.2:3306 check weight 1 maxconn 2000
    server  MySQL_2 172.20.0.3:3306 check weight 1 maxconn 2000
    server  MySQL_3 172.20.0.4:3306 check weight 1 maxconn 2000
    server  MySQL_4 172.20.0.5:3306 check weight 1 maxconn 2000
    server  MySQL_5 172.20.0.6:3306 check weight 1 maxconn 2000
    #使用keepalive检测死链
    option  tcpka

haproxy.cnf 与pxc相关配置 重点查看listen proxy-mysql

创建haproxy容器

// -p 端口号 8888 :haproxy后台监控界面端口,与haproxy.cnf中的配置一致
// -p 端口号 3306 :haproxy对外提供负载均衡服务端口,可以直接通过该端口连接到pxc集群
// -v 目录映射,把宿主机的配置文件所在目录映射到容器的工作目录中
// --name 容器名,建议带数字编号1,高可用时haproxy需要配置成双节点
// --net 网段需要和pxc集群在同一网段中
// --ip 可以指定IP,当不指定时docker虚拟机会默认分配一个
➜ docker run -it -d -p 4001:8888 -p 4002:3306 -v /home/softconf/haproxy:/usr/local/etc/haproxy --name mysql-haproxy1 --privileged --net=network20 haproxy
// 进入容器中,荣国配置文件启动 haproxy
➜ docker exec -it mysql-haproxy1 bash
root@0672cb121dde:/# haproxy -f /usr/local/etc/haproxy/haproxy.cfg

创建mysql的haproxy用户

进入pxc某个节点,在mysql中创建haproxy用户。haproxy中间件需要使用该账号登陆数据库,发送心跳检测。

// 登陆一个pxc节点的mysql终端
➜ docker exec -it mysql-node2 mysql -uroot -p
mysql> use mysql
Database changed
mysql> create user 'haproxy'@'%' identified by '';
mysql> flush privileges;

验证haproxy

通过浏览器访问haproxy监控页面,远程访问需要开放端口(CentOS7使用firewalld打开关闭防火墙与端口)

  • 访问地址:http://宿主机IP:4001/dbs
  • 访问路径配置文件中:stats uri /dbs
  • 用户名、密码在配置文件中:stats auth admin:admin123456
    haproxy监控页面

在监控见面中可以看到5个节点都是启动状态,当暂停/停止一个pxc节点时,监控界面的down节点状态(颜色)发生变化。由于haproxy会将请求发送至其它可用节点,此时pxc集群仍然可用。

// 终端执行命令,暂停一个节点
➜  haproxy docker pause mysql-node2
haproxy监控界面 - node down

mysql客户端连接haproxy

// 宿主机上连接mysql客户端
// -P 端口号,haproxy的3306端口映射到宿主机的4002端口
// -p 密码,pxc集群中的mysql密码
➜  haproxy mysql -h127.0.0.1 -P4002 -uroot -p
// 或通过navicat工具连接,-h为宿主机的公网IP

// 进入mysql后,操作的所有数据都同步到pxc集群的全部节点
...

TODO : 集群高可用

单节点的Haproxy不具备高可用,必须要有冗余设计。
keepalived与haproxy安装在同一个容器中,抢占虚拟IP。
阿里云服务器不支持虚拟IP,keepalived实现高可用待实现

Tips

Keepalived+mysql 双主一般来说,有几个需要注意的点,原文地址:MySQL 高可用性keepalived+mysql双主

1.采用 keepalived 作为高可用方案时,两个节点最好都设置成 BACKUP模式,避免因为意外情况下(比如 脑裂)相互抢占导致往两个节点写入相同数据而引发冲突;
2.把两个节点的 auto_increment_increment(自增步长)和 auto_increment_offset(自增起始值)设成不同值。其目的是为了避免 master 节点意外宕机时,可能会有部分 binlog 未能及时复制到slave上被应用,从而会导致slave新写入数据的自增值和原先master上冲突了,因此一开始就使其错开;当然了,如果有合适的容错机制能解决主从自增 ID 冲突的话,也可以不这么做;
3.slave 节点服务器配置不要太差,否则更容易导致复制延迟。作为热备节点的 slave 服务器,硬件配置不能低于 master 节点;
4.如果对延迟问题很敏感的话,可考虑使用 MariaDB 分支版本,或者直接上线 MySQL 5.7 最新版本,利用多线程复制的方式可以很大程度降低复制延迟;

参考资料

推荐阅读更多精彩内容