Docker基础知识 (14) - 使用 Docker 部署 MariaDB 集群 (二) | 基于 MyCat 的读写分离和负载均衡


读写分离适用的场景:读操作的频率远高于写操作的频率,写操作的耗时长于读操作的耗时,即长耗时低频率的写操作降低了整个数据库的高并发读写能力,同时读操作对数据实时性要求不高,允许一定时间的延时。

MySQL 读写分离基本原理是让 master 数据库处理写操作,slave 数据库处理读操作,master 将写操作的变更同步到各个 slave 节点。

MySQL 读写分离能提高系统性能的原因:

    (1) 物理服务器增加,机器处理能力提升,拿硬件换性能;
    (2) 主从只负责各自的读和写,极大程度缓解 X 锁和 S 锁争用;
    (3) slave 可以配置 MyIASM 引擎,提升查询性能以及节约系统开销;
    (4) master 直接写是并发的,slave 通过主库发送来的 binlog 恢复数据是异步;
    (5) slave 可以单独设置一些参数来提升其读的性能;
    (6) 增加冗余,提高可用性;

实现读写分离的方案:

    1) 基于 MySQL Proxy 代理的方式

        MySQL 的代理最常见的是 mysql-proxy、cobar、mycat、Atlas 等。这种方式对于应用来说,MySQL Proxy 是完全透明的,应用则只需要连接到 MySQL Proxy 的监听端口即可。
        
        当然,这样 Proxy 机器可能会单点失效,但可以使用多个 Proxy 机器做为冗余,在应用服务器的连接池配置中配置到多个 Proxy 的连接参数即可。

            (1) mysql-proxy 是一个轻量的中间代理,是官方提供的 MySQL 中间件产品可以实现负载平衡,读写分离,failover 等,依靠内部一个 lua 脚本实现读写语句的判断。项目地址:https://github.com/mysql/mysql-proxy ,该项目已经六七年没有维护了,官方也不建议应用于生成环境。
            (2) cobar 是阿里提供的一个中间件,已经停止更新。项目地址:https://github.com/alibaba/cobar。
            (3) mycat 的前身就是 cobar,活跃度比较高,完全使用 java 语言开发。 项目地址:https://github.com/MyCATApache/Mycat-Server。  
            (4) moeba(变形虫)是阿里工程师陈思儒基于 java 开发的一款数据库读写分离的项目(读写分离只是它的一个小功能),与 MySQL 官方的 MySQL Proxy 相比,作者强调的是 amoeba 配置的方便(基于XML的配置文件,用 SQLJEP 语法书写规则,比基于 lua 脚本的MySQL Proxy 简单)。更多详细介绍请参考:https://www.biaodianfu.com/amoeba.html , 下载地址:https://sourceforge.net/projects/amoeba/ 。
            (5) Atlas 是奇虎 360 的一个开源中间代理,是在 MySQL 官方 mysql-proxy 0.8.2 的基础上进行了优化,增加一些新的功能特性。 项目地址: https://github.com/Qihoo360/Atlas 。

    2) 基于应用内路由的方式

        基于 Spring 的 AOP 实现: 用AOP 来拦截 Spring 项目的 dao 层方法,根据方法名称就可以判断要执行的 SQL 类型(即是 read 还是 write 类型),进而动态切换主从数据源。

        参考项目:https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter

    3) 基于 mysql-connector-java 的 jdbc 驱动方式

        使用 MySQL 驱动 Connector/J 的可以实现读写分离。即在 jdbc 的 url 中配置为如下的形示:

            jdbc:mysql:replication://master,slave1,slave2,slave3/test

        Java 程序通过在连接 MySQL 的 jdbc 中配置主库与从库等地址,jdbc 会自动将读请求发送给从库,将写请求发送给主库,此外,mysql 的 jdbc 驱动还能够实现多个从库的负载均衡。

        关于 jdbc 文档地址:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html
        关于读写分离文档地址:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-source-replica-replication-connection.html

    4) 基于 sharding-jdbc 的方式

        sharding-sphere 是强大的读写分离、分表分库中间件,sharding-jdbc 是 sharding-sphere 的核心模块,sharding-jdbc 可以与 Springboot 集成。官方网址:https://shardingsphere.apache.org/。

以上四种方案各有优缺点,基于 MySQL Proxy 代理的方式对于应用来说相对简单,但是在项目稳定性、事务支持性等方面还存在问题;而基于应用内路由的方式固然灵活度比较高,但是也增加了应用逻辑的复杂度;基于 mysql-connector-java 的 jdbc 驱动和 sharding-jdbc 的方式在使用上相对简单,但限制了需要使用 java 开发。

本文部署 MyCat + MariaDB 集群来演示如何实现读写分离和负载均衡。


1. 部署环境

  IP 地址(本地测试环境):192.168.0.10
  操作系统:Linux CentOS 7.9
  Docker 版本:20.10.7
  Docker Compose 版本:2.6.1
  MyCat 版本:1.6.7.5

  MariaDB 集群目录:/home/docker/mysql_cluster
  MyCat 目录:/home/docker/mysql_cluster/mycat

  MyCat GitHub:https://github.com/MyCATApache/Mycat-Server


2. 构建 mycat 镜像

    1) 下载 Mycat

        $ cd /home/docker/mysql_cluster/mycat
        $ wget https://github.com/MyCATApache/Mycat-Server/releases/download/Mycat-server-1675-release/Mycat-server-1.6.7.5-release-20200422133810-linux.tar.gz
        $ tar -vxzf Mycat-server-1.6.7.5-release-20200422133810-linux.tar.gz
        $ cp -R mycat/conf/ ./

    2) 创建 Dockerfile

        $ cd /home/docker/mysql_cluster/mycat
        $ vim Dockerfile

            FROM openjdk:8
            ADD mycat/ /usr/local/mycat/
            VOLUME /usr/local/mycat/conf
            ENV MYCAT_HOME=/usr/local/mycat
            EXPOSE 8066 1984
            CMD ["/usr/local/mycat/bin/mycat", "console"]

        注:这里使用 openjdk:8 基础镜像来演示,建议实际应用中使用较新的 Java 版本,可以访问 https://hub.docker.com/_/openjdk/tags 查询。

    3) 构建
        
        $ cd /home/docker/mysql_cluster/mycat
        $ docker build -t mycat-1.6.7:2022-09-28 . 

            Sending build context to Docker daemon  49.36MB
            Step 1/6 : FROM openjdk:8
            8: Pulling from library/openjdk
            001c52e26ad5: Pull complete
            d9d4b9b6e964: Pull complete
            2068746827ec: Pull complete
            9daef329d350: Pull complete
            d85151f15b66: Pull complete
            52a8c426d30b: Pull complete
            8754a66e0050: Pull complete
            Digest: sha256:86e863cc57215cfb181bd319736d0baf625fe8f150577f9eb58bd937f5452cb8
            Status: Downloaded newer image for openjdk:8
            ---> b273004037cc
            Step 2/6 : ADD mycat/ /usr/local/mycat/
            ---> 1fcae8e968b0
            Step 3/6 : VOLUME /usr/local/mycat/conf
            ---> Running in 6af0a0e6d779
            Removing intermediate container 6af0a0e6d779
            ---> 43cb021696d6
            Step 4/6 : ENV MYCAT_HOME=/usr/local/mycat
            ---> Running in f78ac994990f
            Removing intermediate container f78ac994990f
            ---> 45509b7f7fb6
            Step 5/6 : EXPOSE 8066 1984
            ---> Running in f24b257ccee6
            Removing intermediate container f24b257ccee6
            ---> c878bd1130c0
            Step 6/6 : CMD ["/usr/local/mycat/bin/mycat", "console"]
            ---> Running in e373990ba208
            Removing intermediate container e373990ba208
            ---> c15cdad98dd6
            Successfully built c15cdad98dd6
            Successfully tagged mycat-1.6.7:2022-09-28


        $ docker images

            REPOSITORY    TAG              IMAGE ID           CREATED          SIZE
            mycat-1.6.7   2022-09-28     c4ee675557be   30 seconds ago   552MB
            openjdk        8                      b273004037cc   8 weeks ago       526MB
            ...

 

3. 创建 docker-compose.yml

    $ cd /home/docker/mysql_cluster/mycat
    $ vim docker-compose.yml

        version: "3"
        services:
            mariadb_master:
                image: mariadb:10.4
                container_name: mariadb_master-10.4
                ports:
                    - "3306:3306"
                restart: always
                environment:
                    - MARIADB_ROOT_PASSWORD=123456
                volumes:
                    - /home/docker/mysql_cluster/master/conf:/etc/mysql/conf.d
                    - /home/docker/mysql_cluster/master/data:/var/lib/mysql
                    - /home/docker/mysql_cluster/master/log:/var/log/mysql
            mariadb_node_1:
                image: mariadb:10.4
                container_name: mariadb_node_1-10.4
                ports:
                    - "3307:3307"
                restart: always
                environment:
                    - MARIADB_ROOT_PASSWORD=123456
                volumes:
                    - /home/docker/mysql_cluster/node_1/conf:/etc/mysql/conf.d
                    - /home/docker/mysql_cluster/node_1/data:/var/lib/mysql
                    - /home/docker/mysql_cluster/node_1/log:/var/log/mysql
            mariadb_node_2:
                image: mariadb:10.4
                container_name: mariadb_node_2-10.4
                ports:
                    - "3308:3308"
                restart: always
                environment:
                    - MARIADB_ROOT_PASSWORD=123456
                volumes:
                    - /home/docker/mysql_cluster/node_2/conf:/etc/mysql/conf.d
                    - /home/docker/mysql_cluster/node_2/data:/var/lib/mysql
                    - /home/docker/mysql_cluster/node_2/log:/var/log/mysql
            mycat:
                image: mycat-1.6.7:2022-09-28
                container_name: mycat
                ports:
                    - "8066:8066"
                    - "1984:1984"
                restart: always
                volumes:
                    - /home/docker/mysql_cluster/mycat/conf:/usr/local/mycat/conf
                    - /home/docker/mysql_cluster/mycat/logs:/usr/local/mycat/logs

 

4. MariaDB 配置文件

    1) 创建 master 的 my_mariadb.cnf 文件

        在 /home/docker/mysql_cluster/master/conf 目录下,创建 my_mariadb.cnf 文件,内容如下:

            [mysqld]
            server-id=1
            port=3306
            
            #basedir=/usr/local/mysql
            #tmpdir=/tmp
            datadir=/var/lib/mysql

            # 查询日志,默认在 /var/lib/mysql 目录下
            #general_log=1
            #general_log_file=mysql_general-1.log

            # 二进制日志,默认在 /var/lib/mysql 目录下
            log_bin=mysql_binlog-1

            # 慢查询日志,默认在 /var/log/mysql 目录下
            #slow_query_log=1
            #long_query_time=1
            #slow_query_log_file=mysql_slow_query-1.log

            # 错误日志,指定到 /var/log/mysql 目录
            log_error=/var/log/mysql/mysql_err-1.log

        注:MariaDB (MySQL) 的默认配置文件是 /etc/mysql/my.cnf 文件。如果想要自定义配置,在 /etc/mysql/conf.d 目录中创建 *.cnf 文件。新建的文件可以任意起名,只要保证后缀名是 cnf 即可。新建的文件中的配置项可以覆盖 /etc/mysql/my.cnf 中的配置项。

    2) 创建 node_1 的 my_mariadb.cnf 文件

        在 /home/docker/mysql_cluster/node_1/conf 目录下,创建 my_mariadb.cnf 文件,内容如下:

            [mysqld]
            server-id=2
            port=3307
        
            #basedir=/usr/local/mysql
            #tmpdir=/tmp
            datadir=/var/lib/mysql

            # 查询日志,默认在 /var/lib/mysql 目录下
            #general_log=1
            #general_log_file=mysql_general-2.log

            # 二进制日志,默认在 /var/lib/mysql 目录下
            #log_bin=mysql_binlog-2

            # 慢查询日志,默认在 /var/log/mysql 目录下
            #slow_query_log=1
            #long_query_time=1
            #slow_query_log_file=mysql_slow_query-2.log

            # 错误日志,指定到 /var/log/mysql 目录
            log_error=/var/log/mysql/mysql_err-2.log

            # relay-log
          relay-log=mysql-relaylog-2


    3) 创建 node_2 的 my_mariadb.cnf 文件

        在 /home/docker/mysql_cluster/node_2/conf 目录下,创建 my_mariadb.cnf 文件,内容如下:

            [mysqld]
            server-id=3
            port=3308
        
            #basedir=/usr/local/mysql
            #tmpdir=/tmp
            datadir=/var/lib/mysql

            # 查询日志,默认在 /var/lib/mysql 目录下
            #general_log=1
            #general_log_file=mysql_general-3.log

            # 二进制日志,默认在 /var/lib/mysql 目录下
            #log_bin=mysql_binlog-3

            # 慢查询日志,默认在 /var/log/mysql 目录下
            #slow_query_log=1
            #long_query_time=1
            #slow_query_log_file=mysql_slow_query-3.log

            # 错误日志,指定到 /var/log/mysql 目录
            log_error=/var/log/mysql/mysql_err-3.log

            # relay-log
          relay-log=mysql-relaylog-3

 

5. 启动

    $ cd /home/docker/mysql_cluster/mycat   # 进入 docker-compose.yml 所在目录
    $ docker-compose up -d   # 在后台运行

        [+] Running 5/5
        ⠿ Network mycat_default          Created                0.0s
        ⠿ Container mariadb_node_1-10.4  Started                1.0s
        ⠿ Container mycat                Started                1.0s
        ⠿ Container mariadb_node_2-10.4  Started                0.7s
        ⠿ Container mariadb_master-10.4  Started                0.5s

        注:本地 docker 没有所需要的镜像时,会自动下载所需的镜像。遇到无法自动下载的情况,可以使用 docker pull 命令下载所需的镜像,再运行 docker-compose up 命令。
        
    $ docker images     # 查看镜像

        REPOSITORY    TAG              IMAGE ID       CREATED          SIZE
        mycat-1.6.7   2022-09-28       c4ee675557be   14 minutes ago   552MB
        mariadb       10.4             801fdf0164b2   3 weeks ago      404MB
        openjdk       8                b273004037cc   8 weeks ago      526MB


    $ docker-compose ps     # 类似 docker ps 的作用

        NAME                 COMMAND  SERVICE          STATUS       PORTS
        mariadb_master-10.4   ...     mariadb_master   running   0.0.0.0:3306->3306/tcp,...
        mariadb_node_1-10.4           mariadb_node_1   running   0.0.0.0:3307->3307/tcp,...
        mariadb_node_2-10.4           mariadb_node_2   running   0.0.0.0:3308->3308/tcp,...
        mycat                         mycat            running   0.0.0.0:1984->1984/tcp,...


    容器内的程序要在挂载的 log 目录下创建 log 文件,需要确保 root/root 以外的用户也有 log 目录的写权限,修改 log 目录的权限,命令如下:

        $ chmod a+w /home/docker/mysql_cluster/master/log /home/docker/mysql_cluster/node_1/log /home/docker/mysql_cluster/node_2/log

    修改后需要重启容器,命令如下:

        $ docker restart mariadb_master-10.4 mariadb_node_1-10.4 mariadb_node_2-10.4

    注:这里把 MariaDB 集群配置成主从复制(一主二从),如何配置主从复制,请参考 “Docker基础知识 (13) - 部署 MariaDB 集群 (一) | 主从复制” 的 “5. 配置主从复制” 部分。


6. 测试

    1) 在 master 上创建创建数据库和表

        $ docker exec -it mariadb_master-10.4 /bin/bash     # 进入 master 容器

            # 进入 MySQL 命令行控制台
            root@986cfd3cdf63:/# mysql -u root -p
            Enter password:
            Welcome to the MariaDB monitor.  Commands end with ; or \g.

            MariaDB [(none)]> CREATE DATABASE IF NOT EXISTS test_mycat;
            Query OK, 1 row affected (0.000 sec)

            MariaDB [(none)]> show databases;

                +--------------------+
                | Database           |
                +--------------------+
                | information_schema |
                | mysql              |
                | performance_schema |
                | test_mycat         |
                +--------------------+
                4 rows in set (0.001 sec)

            MariaDB [(none)]> use test_mycat;
            Database changed

            MariaDB [test_mycat]> CREATE TABLE `test_table` (`id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
            Query OK, 0 rows affected (0.008 sec)

        注:master 上创建的库和表会同步到 node_1 和 node_2 上。
    
    2) 修改 mycat 配置文件

        (1) 主要配置文件

            server.xml 是 mycat 服务器参数调整和用户授权的配置文件;
            schema.xml 是逻辑库定义和表以及分片定义的配置文件;
            rule.xml 是分片规则的配置文件;

        (2) 修改 schema.xml

            $ cd /home/docker/mysql_cluster/mycat/conf
            $ vim schema.xml

                <?xml version="1.0"?>
                <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
                <mycat:schema xmlns:mycat="http://io.mycat/">

                        <schema name="test_mycat" checkSQLschema="false" sqlMaxLimit="100" dataNode="test_mycat" />
                        <dataNode name="test_mycat" dataHost="localhost1" database="test_mycat" />

                        <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">

                                <heartbeat>select 1</heartbeat>

                                <!-- 一主二从,读写分离 -->
                                <writeHost host="hostM1" url="192.168.0.10:3306" user="root" password="123456">

                                        <readHost host="hostS1" url="192.168.0.10:3307" user="root" password="123456" />
                                        <readHost host="hostS2" url="192.168.0.10:3308" user="root" password="123456" />

                                </writeHost>

                <!--            <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/>   -->

                        </dataHost>

                </mycat:schema>


        (3) 修改 server.xml

            <?xml version="1.0" encoding="UTF-8"?>
            <!-- - - Licensed under the Apache License, Version 2.0 (the "License");
                    - you may not use this file except in compliance with the License. - You
                    may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0
                    - - Unless required by applicable law or agreed to in writing, software -
                    distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT
                    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the
                    License for the specific language governing permissions and - limitations
                    under the License. -->
            <!DOCTYPE mycat:server SYSTEM "server.dtd">
            <mycat:server xmlns:mycat="http://io.mycat/">

                    ...

                    <user name="root" defaultAccount="true">
                            <property name="password">123456</property>
                            <property name="schemas">test_mycat</property>
                            <property name="defaultSchema">test_mycat</property>
                            <!--No MyCAT Database selected 错误前会尝试使用该schema作为schema,不设置则为null,报错 -->

                            <!-- 表级 DML 权限设置 -->
                            <!--
                            <privileges check="false">
                                    <schema name="TESTDB" dml="0110" >
                                            <table name="tb01" dml="0000"></table>
                                            <table name="tb02" dml="1111"></table>
                                    </schema>
                            </privileges>
                            -->
                    </user>

                    <user name="user">
                            <property name="password">user</property>
                            <property name="schemas">test_mycat</property>
                            <property name="readOnly">true</property>
                            <property name="defaultSchema">test_mycat</property>
                    </user>

            </mycat:server>


        (4) 重新启动 mycat

            $ docker restart mycat

                mycat

    3) 测试 mycat

        Windows 测试终端的环境配置

            OS:Windows 10 Home 21H2
            MySQL Client:mysql  Ver 15.1 Distrib 10.4.21-MariaDB, for Win64 (AMD64)
            IP:192.168.0.2

        Step 1:访问 mycat

            C:>mysql -h 192.168.0.10 -P 8066 -u root -p     
                Enter password: ******
                Welcome to the MariaDB monitor.  Commands end with ; or \g.

                ...

                MySQL [(none)]> use test_mycat;
                Database changed

                MySQL [(none)]> INSERT INTO test_table (id, name) VALUES ('1', 'mycat master');

                Query OK, 1 row affected (0.022 sec)

                MySQL [test_mycat]> select * from test_table;
                +----+--------------+
                | id | name         |
                +----+--------------+
                |  1 | mycat master |
                +----+--------------+
                1 row in set (0.003 sec)


        Step 2:直接访问 node_1

            C:>mysql -h 192.168.0.10 -P 3307 -u root -p

                Enter password: ******
                Welcome to the MariaDB monitor.  Commands end with ; or \g.

                ...

                MySQL [(none)]> use test_mycat;
                Database changed

                MariaDB [test_mycat]> select * from test_table;
                +----+--------------+
                | id | name         |
                +----+--------------+
                |  1 | mycat master |
                +----+--------------+
                1 row in set (0.001 sec)

                MariaDB [test_mycat]> update test_table set name='mycat node_1' where id='1';


        Step 3:直接访问 node_2

            C:>mysql -h 192.168.0.10 -P 3308 -u root -p

                Enter password: ******
                Welcome to the MariaDB monitor.  Commands end with ; or \g.

                ...

                MySQL [(none)]> use test_mycat;
                Database changed

                MariaDB [test_mycat]> select * from test_table;
                +----+--------------+
                | id | name         |
                +----+--------------+
                |  1 | mycat master |
                +----+--------------+
                1 row in set (0.001 sec)

                MariaDB [test_mycat]> update test_table set name='mycat node_2' where id='1'; 

            
        Step 4:再访问 mycat

            C:>mysql -h 192.168.0.10 -P 8066 -u root -p     
                Enter password: ******
                Welcome to the MariaDB monitor.  Commands end with ; or \g.

                ...

                MySQL [(none)]> use test_mycat;
                Database changed

                MySQL [test_mycat]> select * from test_table;
                +----+--------------+
                | id | name         |
                +----+--------------+
                |  1 | mycat node_1 |
                +----+--------------+
                1 row in set (0.002 sec)

                MySQL [test_mycat]> select * from test_table;
                +----+--------------+
                | id | name         |
                +----+--------------+
                |  1 | mycat node_2 |
                +----+--------------+
                1 row in set (0.002 sec)

                MySQL [test_mycat]> select * from test_table;
                +----+--------------+
                | id | name         |
                +----+--------------+
                |  1 | mycat node_1 |
                +----+--------------+
                1 row in set (0.001 sec)

                ...

                注:很显然,从 mycat 的 8066 端口读不到 master 的数据,master 只负责写数据。

        Step 5:直接访问 master

            C:>mysql -h 192.168.0.10 -P 3306 -u root -p     
                Enter password: ******
                Welcome to the MariaDB monitor.  Commands end with ; or \g.

                ...

                MySQL [(none)]> use test_mycat;
                Database changed

                MySQL [test_mycat]> select * from test_table;
                +----+--------------+
                | id | name         |
                +----+--------------+
                |  1 | mycat master |
                +----+--------------+
                1 row in set (0.001 sec)


    测试结果:
    
        (1) 通过 mycat (端口:8066)写数据,都写到了 master 的 test_mycat 数据库;
        (2) 通过 mycat 读数据,数据交替来自 node_1 和 node_2 的 test_mycat 数据库;
        (3) 根据 (1)、(2),可以判断 mycat 实现了读写分离,并实现了读数据时负载均衡。
   

posted @ 2022-09-29 11:04  垄山小站  阅读(853)  评论(0)    收藏  举报