1 部署架构设计
在数据库集群中每一个 MySQL 都要发布到 Docker 虚拟机的实例中,即将每一个 MySQL 部署到一个独立的容器中,通过负载均衡分散数据库的请求。 前后端同样通过集群实现高可用,使用多个节点进行部署,一个节点宕机,其余节点仍能提供服务,中间件使用 Nginx。
2 实验环境准备
2.1 虚拟机
- 磁盘: ≥ 5 GB
- 内存: ≥ 2 GB
- 处理器:1 个 4 核 CPU
- cent OS的优势:
- 跨平台的硬件支持
- 可靠的安全性
- 丰富的软件支持
- 多用户多任务
- 良好的稳定性
- 完善的网络功能
2.2 后端项目
- 技术栈:SpringBoot + Shrio + SSM + JWT + Redis + Swagger
- Maven 环境
- MySQL 环境
2.3 前端项目
- 技术栈:Vue + ElementUI
- Node.js 环境
2.4 Docker虚拟机
Docker 创建的所有虚拟实例共用同一个 Linux 内核,对硬件占用小,属于轻量级虚拟机。容器是从镜像中创建出来的虚拟实例,容器是读写层,用来运行程序,镜像是只读层,用来安装程序。
- 更新 yum 软件管理器,安装 Docker
yum -y update
yum install -y docker
service docker start/stop/restart
【Error】[Errno 14] HTTP Error 404 - Not Found
【Solution】
yum clean all
rpm --rebuilddb
- Docker 虚拟机管理命令
-
配置 DaoCloud 的Docker加速器
-
在线安装 Java 镜像
docker search java
docker pull java
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
java latest d23bdf5b1b1b 4 years ago 643MB
# 将镜像保存为文件
$ docker save docker.io/java > /home/java.tar.gz
# 移除镜像
$ docker rmi docker.io/java
# 将文件导入为镜像
$ docker load < /home/java.tar.gz
- 启动容器:启动镜像会创建出一个运行状态的容器
$ docker run -it --name myjava java bash
$ docker run -it --name myjava -p 9000:8080 -p 9001:8085 java bash
$ docker run -it -p 9000:8000 -p 9001:8085 -v /home/project:/soft --privileged --name myjava docker.io/java bash
【Error】docker: Error response from daemon: Conflict. The container name "/myjava" is already in use by container "8301ced0145f6f10223f7403c222630118791de905eb9c119445495e67c7d7fd". You have to remove (or rename) that container to be able to reuse that name.
【Solution】通过docker ps -a
查看容器运行状态,通过docker rm id/name
移除容器。
- 暂停和停止容器
[root@localhost/] docker pause myjava
[root@localhost/] docker uppause myjava
[root@localhost/] docker stop myjava
# 进入容器
[root@localhost/] docker start -i myjava
3 搭建数据库集群
3.1 单节点数据库的缺陷
- 大型互联网程序用户群体庞大,单节点数据库无法满足性能要求
- 单节点数据库没有冗余设计,无法满足高可用
3.2 常见的 MySQL 集群方案
方案 | 特点 | 适用场景 |
---|---|---|
Replication | 速度快、弱一致性、低价值 | 日志、新闻、帖子 |
PXC | 速度慢、强一致性、高价值 | 订单、账户、财务 |
PXC 原理
PXC 的全称是 Percona XtraDB Cluster,是基于 Galera 技术实现的集群,任何一个数据库的节点都是可读可写的
使用的数据库实例可以是原生的 MySQL,但建议 PXC 使用 PerconaServer,PerconaServer 是 MySQL 的改进版,具有很好的性能提升。
PXC 和 Replication 方案对比
PXC 方案:PXC 具有数据强一致性,通过同步复制,事务在所有节点上要么同时提交,要么都不提交。
Replication 方案:Replication 采用异步复制,无法保证数据的一致性。
3.3 PXC集群安装
Docker 的镜像仓库中包含了 PXC 数据库的镜像,可以通过docker pull docker.io/percona/percona-xtradb-cluster
直接下载。
处于安全考虑,需要给 PXC 集群实例创建 Docker 内部网络
# 创建网段
docker network create net1
# 查看网段相关信息
docker network inspect net1
# 删除网段
docker network rm net1
[root@localhost ~]# docker network create --subnet=172.18.0.0/24 net19322b84026c901b5501d2e680d388e8dfcf6cd0fa54cbeeb38de82e3fa095859
[root@localhost ~]# docker inspect net1
[
{
"Name": "net1",
"Id": "9322b84026c901b5501d2e680d388e8dfcf6cd0fa54cbeeb38de82e3fa095859",
"Created": "2021-04-11T08:24:17.009369885Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/24"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
3.4 创建 Docker 卷
容器中的 PXC 节点映射数据目录的解决方法
[root@localhost ~]# docker volume create v1
v1
[root@localhost ~]# docker inspect v1
[
{
"CreatedAt": "2021-04-11T08:29:20Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/v1/_data",
"Name": "v1",
"Options": {},
"Scope": "local"
}
]
3.5 创建 PXC 容器
向 PXC 镜像传入运行参数,创建出 PXC 容器。
相关参数:
- -d 创建出的实例后台运行
- -p 指定端口( port1 宿主机端口: port2 容器端口)
- -v 路径映射(数据卷中的文件目录)
- -e 启动参数
启动5个容器:
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -v v1:/var/lib/mysql --privileged --name=node1 --net=net1 --ip 172.18.0.2 pxc
docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=node1 -v v2:/var/lib/mysql --privileged --name=node2 --net=net1 --ip 172.18.0.3 pxc
docker run -d -p 3308:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=node1 -v v3:/var/lib/mysql --privileged --name=node3 --net=net1 --ip 172.18.0.4 pxc
docker run -d -p 3309:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=node1 -v v4:/var/lib/mysql --privileged --name=node4 --net=net1 --ip 172.18.0.5 pxc
docker run -d -p 3310:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=node1 -v v5:/var/lib/mysql --privileged --name=node5 --net=net1 --ip 172.18.0.6 pxc
【Error】docker 使用 pxc 创建 mysql 集群节点启动自动退出
【Solution】使用指定版本的 PXC,docker pull percona/percona-xtradb-cluster:5.7.21
, 每个节点创建之后,因为要执行pxc的初始化和加入集群等工作,耐心等待一点时间再用客户端连接MySQL,客户端连接成功后,再去创建node2节点。设置volume目录读写权限,chmod -R 777 /var/lib/docker/volumes/v1/_data
完成5个节点的启动:
3.5 数据库负载均衡
在搭建集群的同时,还需要使用数据库负载均衡,否则单节点处理所有请求,负载高,性能差。可以使用 Haroxy 等中间件做负载均衡,请求会被均匀分发给各个节点,单节点负载低,性能好。
中间件产品 | 免费 | 虚拟机 | HTTP协议 | TCP/IP协议 | 插件 | 性能 |
---|---|---|---|---|---|---|
Haproxy | 是 | 支持 | 支持 | 支持 | 不支持 | 好 |
Nginx | 是 | 支持 | 支持 | 支持 | 支持 | 好 |
Apache | 是 | 支持 | 支持 | 不支持 | 不支持 | 一般 |
LVS | 是 | 不支持 | 支持 | 支持 | 不支持 | 最好 |
拉取Haproxy:docker pull haproxy
创建Haproxy配置文件:touch /home/soft/haproxy.cfg
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:abc123456
#数据库负载均衡
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.18.0.2:3306 check weight 1 maxconn 2000
server MySQL_2 172.18.0.3:3306 check weight 1 maxconn 2000
server MySQL_3 172.18.0.4:3306 check weight 1 maxconn 2000
server MySQL_4 172.18.0.5:3306 check weight 1 maxconn 2000
server MySQL_5 172.18.0.6:3306 check weight 1 maxconn 2000
#使用keepalive检测死链
option tcpka
创建Haproxy容器:
# 后台监控端口是8888 和数据库实例在一个网段
docker run -it -d -p 4001:8888 -p 4002:3306 -v /home/soft/haproxy:/usr/local/etc/haproxy --name h1 --privileged --net=net1 --ip 172.18.0.7 haproxy bash
进入Haproxy容器:docker exec -it h1 bash
【Error】Error response from daemon: Container dd729aa0c8bdbbee428ceda7af5802dbedf454ee7c4fbee7a796ebedf36165c9 is not running
【Solution】通过docker ps -a
发现 h1 处于退出状态,在上面的 run 的执行语句中少加了bash,每次 run 完 2s 后就退出了,加上 bash 后,持续处于 up 状态。
加载配置文件:haproxy -f /usr/local/etc/haproxy/haproxy.cfg
【Error】文件不存在
【Solution】将文件从宿主机拷贝到容器中:docker cp /home/soft/haproxy.cfg h1:/usr/local/etc/haproxy/
在MySQL中创建一个 haproxy 账号:create user 'haproxy'@'%' identified by ''
访问DBS:http://192.168.3.113:4001/dbs
输入配置文件中定义的用户名和密码
4 双机热备
单节点Haproxy不具有高可用性,必须要有冗余设计。
Linux可以在一个网卡中定义多个虚拟IP,可以将IP分配给不同的应用程序。
4.1 利用keepalived实现双机热备
Keepalived用来抢占虚拟IP,争抢到虚拟IP的作为主服务器,等待虚拟IP的作为备用服务器,两个服务器之间存在心跳检测,如果心跳检测没有响应,说明主服务器可能出现了故障,备用服务器可以去争抢虚拟IP。
4.2 Haproxy双机热备方案
通过上述步骤创建数据库集群,里面存在多个 PXC 实例,通过负载均衡转发请求。 创建两个容器分别部署 Haproxy,内部安装 Keepalived,这样一来,任意一个容器宕机之后,还有另一个容器可以使用。两个 Keepalived 抢占 172.18.0.X 虚拟 IP。Docker 内的虚拟 IP 不可以被外网使用,所以需要借助宿主机 Keepalived 映射为外网可以访问的虚拟 IP。
4.3 安装Keepalived
- Keepalived 必须安装在 Haproxy 所在的容器之内
# 先进入h1
[root@localhost ~]# docker exec -it h1 bash
# 在h1中安装
root@a5dfcd63c066:/# apt-get update
root@a5dfcd63c066:/# apt-get install keepalived
- 编辑keepalived的配置文件
/etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
# keepalived的身份(MASTER/BACKUP)主服务器抢占虚拟IP 备用服务器不会抢占
state MASTER
# 网卡设备
interface eth1
# 虚拟路由标识,主备的虚拟路由标识必须一致(0~255)
virtual_router_id 51
# MASTER权重高于BACKUP 数字越大优先级越高
priority 100
# 心跳检测的间隔
时间,单位为s
advert_int 1
# 主服务器验证方式,主备必须使用相同的密码才能正常通信
authentication {
auth_type PASS
auth_pass 123456
}
# 虚拟IP地址,可以设置多个虚拟IP,每行一个
virtual_ipaddress {
172.18.0.201
}
}
- 启动 Keepalived:
service keepalived start
- 宿主机可以 ping 通虚拟 IP。 【Error】keepalived 绑定虚拟IP失败。
vrrp_instance VI_1 { state MASTER interface eth1 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 172.18.0.201 } }
【Error】apt-get 换源的问题
W: GPG 错误:http://mirrors.ustc.edu.cn/ubuntu trusty Release: 由于没有公钥,无法验证下列签名: NO_PUBKEY 40976EAF437D05B5 NO_PUBKEY 3B4FE6ACC0B21F32
E: 仓库 “http://mirrors.ustc.edu.cn/ubuntu trusty Release” 没有数字签名。
【Solution】
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 # 最后这部分就是上面那个NO_PUBKEY 后面的一坨
4.4 热备份数据
冷备份是关闭数据库时的备份方式,通常做法是拷贝数据文件。冷备份是最 简单、最安全的一种备份方式,大型网站无法关闭业务备份数据,所以冷备份不是好的选择。
热备份是在系统运行的状态下备份数据,也是难度最大的备份。MySQL 常见的热备份有 LVM 和 XtraBackup 两种方案。
LVM 是 Linux 自带的备份方式,Linux 对某个分区创建快照,实现对这个分区的备份,缺点是备份数据库的时候需要对数据库加锁,数据库只能读数据不能写数据。
XtraBackup 是一款基于 InnoDB 的在线热备工具,开源免费、支持在线热备,占用磁盘空间小,可以快速的备份和恢复数据库,XtraBackup 不需要锁表就可以备份,备份时不会打断正在执行的事务,基于压缩等功能节约磁盘空间和流量,所以建议使用 XtraBackup。
XtraBackup 分为全量备份和增量备份,全量备份是备份全部数据,备份时间长,占用空间大。增量备份时只备份变化的那部分的数据,备份时间短,占用空间小。一般一周进行一次全量备份,一天进行一次增量备份。
4.5 PXC 全量备份
PXC 容器中安装 XtraBackup,并执行备份。在 node1 中添加 backup,删除node1,重新创建 node1。
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -v v1:/var/lib/mysql -v backup:/data --privileged -e CLUSTER_JOIN=node2 --name=node1 --net=net1 --ip 172.18.0.2 pxc
# 进入 node1
docker exec -it node1 bash
apt-get update
apt-get install percona-xtrabackup-24
# 数据库用户名和密码 存储位置
innobackupex --user=root --password=123456 /data/backup/full
完成备份:
4.6 PXC全量恢复
数据库可以热备份,但是不能热还原,为了避免恢复过程中的数据同步,我们采用空白的 MySQL 还原数据,然后再建立 PXC 集群。
还原数据前要将未提交的事务回滚,还原数据之后,重启 MySQL。
rm -rf /var/lib/mysql/*
innobackupex --user=root --password=123456 --apply-back /data/backup/2020-4-12_02-58-43/
innobackupex --user=root --password=123456 --copy-back /data/backup/2020-4-12_02-58-43/
5 Redis集群搭建
Redis 是 Wmvare开发的开源免费的 key-value 的 NoSQL 缓存产品,Redis 具有很好的性能,最多可以提供 10W 次/秒的读写。
Redis目前的集群方案包括:
- RedisCluster:官方推荐,没有中心节点,客户端和 Redis 节点直连,不需要中间代理层。数据可以被分片存储。管理方便,后续可以新增和删除节点。
- Codis:中间件产品,存在中心节点
- Twemproxy:中间件产品,存在中心节点
5.1 Redis 主从同步
Redis 集群中的数据库复制是通过主从同步实现的,主节点把数据分发给从节点,主从同步的优点是高可用,Redis 节点有冗余设计。
Redis 集群中应该包含奇数个 Master,至少有3个 Master,Redis 有选举机制,超过半数节点宕机,无法执行选举。且每个 Master 都应该有 Slave。1
5.2 获取redis镜像
5.2.1 创建redis docker基础镜像
- 下载redis安装包:
wget http://download.redis.io/releases/redis-4.0.1.tar.gz
- 解压:
tar zxvf redis-4.0.1.tar.gz
- 进入redis文件夹,编译:
make
- 修改redis配置:
vi /home/soft/docker_redis/redis-4.0.1/redis.conf
daemonize yes #以后台进程运行
cluster-enabled yes # 开启集群
cluster-config-file nodes.conf # 集群配置文件
cluster-node-timeout 15000 # 超时时间
appendonly yes # 开启AOF模式
- 进入/home/soft/docker_redis,制作镜像:
vi Dockerfile
# Redis
# Version 4.0.1
FROM centos:7
ENV REDIS_HOME /usr/local
ADD redis-4.0.1.tar.gz / # 本地的redis源码包复制到镜像的根路径下,ADD命令会在复制过后自动解包。被复制的对象必须处于Dockerfile同一路径,且ADD后面必须使用相对路径
RUN mkdir -p $REDIS_HOME/redis # 创建安装目录
ADD redis-4.0.1/redis.conf $REDIS_HOME/redis/ # 将一开始编译产生并修改后的配置复制到安装目录
RUN yum -y update # 更新yum源
RUN yum install -y gcc make # 安装编译需要的工具
WORKDIR /redis-4.0.1
RUN make
RUN mv /redis-4.0.1/src/redis-server $REDIS_HOME/redis/ # 编译后,容器中只需要可执行文件redis-server
WORKDIR /
RUN rm -rf /redis-4.0.1 # 删除解压文件
RUN yum remove -y gcc make # 安装编译完成之后,可以删除多余的gcc跟make
VOLUME ["/var/log/redis"] # 添加数据卷
EXPOSE 6379 # 暴露6379端口,也可以暴露多个端口,这里不需要如此
- 构建镜像:
docker build -t xd1998/cluster-redis
5.2.2 拉取官方 Redis
这里有一点需要注意,官方的 Redis 镜像没有 Redis 的配置文件,所以需要操作一下。
- redis 官网下载压缩包,拿到 redis.conf
- 修改 redis.conf
# bind 127.0.0.1
daemonize no # 用守护线程的方式启动
appendonly yes # redis持久化
tcp-keepalive 300 # 防止出现远程主机强迫关闭了一个现有的连接的错误 默认是300
- 创建本地存放redis的配置:
mkdir /data/redis/data
- 复制:
cp -p redis.conf /data/redis/
- 启动docker:
docker run -p 6379:6379 --name redis -v /data/redis/redis.conf:/etc/redis/redis.conf -v /data/redis/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes
5.3 安装 redis-trib.rb
redis-trib 是基于 Ruby 的 Redis 集群命令工具。
cp /usr/redis/src/redis-trib.rb /usr/redis/cluster/
cd /usr/redis/cluster
apt-get install ruby
apt-get install rubygems
gem install redis
利用 redis-trib.rb 创建 Redis 集群:./redis-trib.rb create --replicas 1 172.19.0.2:6379 172.19.0.3:6379 172.19.0.4:6379 172.19.0.5:6379 172.19.0.6:6379 172.19.0.7:6379