前言
本博客基于之前的mysql单主单从容器自动化搭建。
架构图
补:这里使用nginx不是最优选择,nginx只能通过UDP简单轮询实现mysql的负载均衡,应该使用MySQL Router或ProxySQL实现
相较于单主单从节点,双主双从自动化的难点大多数都相同,但是又有新的问题。
1.双主的启动顺序问题?
因为两个master都要互相change master,这就需要另一个master已经启动,这就会产生冲突,个人想法是先启动master1服务,但是在change master前添加阻塞(sleep或远程连接master2),先让master2完成''启动+change master'',然后再继续master1的change master。
具体请参考<启动策略>
2.master的ROLE问题?
初始化脚本init.sh是按照角色来划分SQL的使用,但是双主的master既做master又做slave,所以我添加了新角色m_s,并且优化了脚本的构成,使用更多的函数,方便理解。
3.nginx的负载均衡?
用nginx的upstream模块实现mysql集群的负载均衡。
启动策略
一般情况(即主机安装),mysql集群的启动比较随意,因为手动输入可以控制何时执行命令,大致流程如下
1.master1启动,授权
2.master2启动,授权
3.master1完成复制
4.master2完成复制
5.slave1启动,复制
6.salve2启动,复制
相较于单主单从,双主双从更加复杂,如果不加以限制同时启动master1与master2使其同时执行毫无疑问是错的,但是简单使master1先于master2服务启动仍是有问题的,关键就在于master1复制时要求master2已经完成启动并授权,反过来也是。
如果只是先启动master1,master1已经到了复制,但是发现master2没有授权甚至没有启动,会直接报错,所以master1与master2的执行SQL顺序更加重要!
1.没有限制启动的双主
如果没有限制,在'复制'时可能由于另一个master没有启动或没有授权导致错误
要求
- 复制时另一个master必须启动且已经授权
- master执行流程相同(便于写脚本,非硬性要求)
2.推导流程图
关于双主启动策略这块,个人觉得是mysql双主双从自动化启动的核心问题
1.master2的等待1:主要是为了保证master1优先启动,通过docker-compose的healthycheck实现(通过脚本测试master1是否启动也可行,但是不选用)
2.master2的等待2:这里其实可以不等待,因为此时master1已经开始,没有实际意义,仅用于使master1于master2流程相同
3.master1的等待:这个等待的用处是很大的,主要是通过脚本实现检查并等待master2启动完成+授权,然后再进行复制
注意:为了保证init.sh的通用性,使的master1和master2的启动流程相同,我把流程不同的部分分到docker-compose中(没错,就是'等待1'),但实际上,可以选择使用多个init.sh分别启动master1和master2
总结:
''等待1'会写入docker-compose,作为master1的healthycheck
''等待''与''等待2'会写入脚本,用于测试另一个master是否已经启动
从节点只需要等待主节点启动即可
nginx可以直接启动,不需要控制
实操

env是环境变量文件
init.sh是容器初始化脚本
my{1..4}.cnf分别是master1,master2,slave1,slave2的mysql配置文件
nginx目录下的都是nginx的配置文件
START是启动脚本
1.docker-compose.yml
version: "3.8"
services:
master1:
image: mysql:5.7
container_name: mysql_master1
restart: always
ports:
- 3306
volumes:
- data1:/var/lib/mysql
- /app/docker/mysql_cluster/my1.cnf:/etc/mysql/conf.d/my.cnf
- /app/docker/mysql_cluster/init.sh:/docker-entrypoint-initdb.d/init.sh
networks:
- mysql_cluster
env_file:
- env
environment:
- ROLE=m_s
#双主的master也有担当slave的角色,所以需要传输MASTER
- MASTER=mysql_master2
#command:
healthcheck:
test: [ "CMD","mysqladmin","-u","root","-p${MYSQL_ROOT_PASSWORD}","ping","-h","localhost" ]
interval: 3s
timeout: 2s
retries: 5
master2:
image: mysql:5.7
container_name: mysql_master2
#build:
restart: always
ports:
- 3306
#依赖master1,设置master1先启动
depends_on:
master1:
restart: true
condition: service_healthy
required: true
volumes:
- data2:/var/lib/mysql
- /app/docker/mysql_cluster/my2.cnf:/etc/mysql/conf.d/my.cnf
- /app/docker/mysql_cluster/init.sh:/docker-entrypoint-initdb.d/init.sh
networks:
- mysql_cluster
env_file:
- env
environment:
- ROLE=m_s
- MASTER=mysql_master1
#command:
healthcheck:
test: [ "CMD","mysqladmin","-u","root","-p${MYSQL_ROOT_PASSWORD}","ping","-h","localhost" ]
interval: 3s
timeout: 2s
retries: 5
slave1:
image: mysql:5.7
container_name: mysql_slave1
#build:
restart: always
ports:
- 3306
#依赖master1,将master1作为复制对象
depends_on:
master1:
restart: true
condition: service_healthy
required: true
volumes:
- data3:/var/lib/mysql
- /app/docker/mysql_cluster/my3.cnf:/etc/mysql/conf.d/my.cnf
- /app/docker/mysql_cluster/init.sh:/docker-entrypoint-initdb.d/init.sh
networks:
- mysql_cluster
env_file:
- env
environment:
- ROLE=slave
- MASTER=mysql_master1
#command:
slave2:
image: mysql:5.7
container_name: mysql_slave2
#build:
restart: always
ports:
- 3306
#依赖master2,将master2作为复制对象
depends_on:
master2:
restart: true
condition: service_healthy
required: true
volumes:
- data4:/var/lib/mysql
- /app/docker/mysql_cluster/my4.cnf:/etc/mysql/conf.d/my.cnf
- /app/docker/mysql_cluster/init.sh:/docker-entrypoint-initdb.d/init.sh
networks:
- mysql_cluster
env_file:
- env
environment:
- ROLE=slave
- MASTER=mysql_master2
#command:
nginx:
image: nginx:alpine
container_name: mysql_nginx
#build:
restart: always
ports:
#打开80端口作为http的监听端口,打开3306作为stream的监听端口(小坑)
- 88:80
- 888:3306
volumes:
- /app/docker/mysql_cluster/nginx/nginx.conf:/etc/nginx/nginx.conf
- /app/docker/mysql_cluster/nginx/conf.d:/etc/nginx/conf.d
- /app/docker/mysql_cluster/nginx/html:/var/www/html
- log:/var/log/nginx
networks:
- mysql_cluster
#command:
networks:
mysql_cluster:
driver: bridge
volumes:
data1: {}
data2: {}
data3: {}
data4: {}
log: {}2.env
#传入root密码,初始化普通用户+密码+数据库
MYSQL_ROOT_PASSWORD=MyStr0ngP@ssw0rd!
MYSQL_USER=slave
MYSQL_PASSWORD=123
MYSQL_DATABASE=box3.my1.cnf(master的mysql配置文件)
my2.cnf仅需要修改server_id,不再展示
[mysqld]
datadir=/var/lib/mysql
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
server_id=1
## 开启binlog日志
log_bin=mysql-bin.log
binlog_format=ROW
max_binlog_size=500M
expire_logs_days=7
slave_skip_errors=1062
log-slave-updates=1
auto-increment-increment=2
auto-increment-offset=1
replicate-ignore-db=mysql
replicate-ignore-db=sys
replicate-ignore-db=information_schema
replicate-ignore-db=performance_schema
bind-address = 0.0.0.0
4.my3.cnf(slave的mysql配置文件)
my4.cnf仅需要修改server_id,不再展示
[mysqld]
datadir=/var/lib/mysql
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
server_id=3
read_only = 1
bind-address = 0.0.0.05.START
#!/bin/bash
#停止docker-compose
docker compose down
#删除数据卷(因为仅用于测试)
docker volume rm mysql_cluster_log
docker volume rm mysql_cluster_data1
docker volume rm mysql_cluster_data1
docker volume rm mysql_cluster_data3
docker volume rm mysql_cluster_data4
#启动
docker compose up -d
docker ps6.init.sh
#!/bin/bash
#需要ROLE(本机身份,用于判断)
#需要MASTER指定复制节点容器名
#错误判断
function test_order() {
if [ $? -ne 0 ]; then
echo "命令执行错误"
exit 1
fi
}
#主节点配置
function master() {
#创建用户(在没有传入MYSQL_USER和MYSQL_PASSWORD时启用)
#CREATE_SQL="create user "${MYSQL_USER}"@'%' identified by '${MYSQL_PASSWORD}';"
#授权复制用户
GRANT_SQL="grant replication slave on *.* to "${MYSQL_USER}"@'%';"
#刷新权限
FLUSH_SQL="flush privileges;"
mysql -u 'root' -p"${MYSQL_ROOT_PASSWORD}" -e "${GRANT_SQL}" &>/dev/null
test_order
mysql -u 'root' -p"${MYSQL_ROOT_PASSWORD}" -e "${FLUSH_SQL}" &>/dev/null
test_order
}
#从节点配置
function slave() {
SHOW_STATUS_SQL1="SHOW MASTER STATUS;"
FILE="$(mysql -u'root' -p"${MYSQL_ROOT_PASSWORD}" -h ${MASTER} -e"${SHOW_STATUS_SQL1}"|grep mysql|awk '{print $1}')"
POSITION="$(mysql -u'root' -p"${MYSQL_ROOT_PASSWORD}" -h ${MASTER} -e"${SHOW_STATUS_SQL1}"|grep mysql|awk '{print $2}')"
#从节点开启复制
CHANGE_MASTER_SQL="change master to master_host='${MASTER}',master_user='${MYSQL_USER}',master_password='${MYSQL_PASSWORD}',master_log_file='${FILE}',master_log_pos=${POSITION};"
START_SLAVE="start slave;"
#指定主节点
mysql -u 'root' -p"${MYSQL_ROOT_PASSWORD}" -e "${CHANGE_MASTER_SQL} ${START_SLAVE}"
test_order
}
function check() {
#注意,这里有大坑,不可使用while true进行循环,未知错误原因,正在查询
for i in {1..5}
do
mysqladmin -u root -p"${MYSQL_ROOT_PASSWORD}" -h ${MASTER} ping
$? && return 0 || exit 1
done
}
#实现主函数
function main() {
#角色为master
if [ "${ROLE}" = "master" ]; then
echo "1"
master
#角色为slave
elif [ "${ROLE}" = "slave" ]; then
echo "2"
slave
#双从身份(双主的master既作为master又作为slave)
elif [ "${ROLE}" = "m_s" ]; then
echo "3"
master
check
slave
else
echo "没有指定角色或角色错误"
fi
}
main7.nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
#负载均衡
stream {
upstream mysql_cluster {
#使用容器名作为转发目标
server mysql_master1:3306 max_fails=3 fail_timeout=10s;
server mysql_master2:3306 max_fails=3 fail_timeout=10s;
}
server {
listen 3306;
proxy_pass mysql_cluster;
proxy_connect_timeout 10s;
proxy_timeout 300s;
}
}
优化方向
nginx负载均衡-->MySQL Router
使用slave账号进行测试(可以直接使用slave账号进行测试,因为修bug的原因忘改了没改)
