• 搜索
  • 夜间模式
    ©2026  依刻学习 Theme by OneBlog

    依刻学习博客

    搜索
    标签
  • 首页>
  • 学习的一天>
  • 正文
  • mysql双主双从+nginx容器自动化部署

    2025年02月28日 27 阅读 0 评论 9552 字

    前言

    本博客基于之前的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=box

    3.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.0

    5.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 ps

    6.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
    }
    
    main

    7.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的原因忘改了没改)

    本文著作权归作者 [ wymm ] 享有,未经作者书面授权,禁止转载,封面图片来源于 [ 互联网 ] ,本文仅供个人学习、研究和欣赏使用。如有异议,请联系博主及时处理。
    取消回复

    发表留言
    回复

    Copyright©2026  All Rights Reserved.  Load:0.017 s
    Theme by OneBlog V3.6.5
    夜间模式

    开源不易,请尊重作者版权,保留基本的版权信息。