• MyCat搭建MySQL双主双从及分库分表


    一、MyCat的高级特性

    1.MyCat读写分离概述

    • 读写分离,基本原理是让主数据库处理事务性增、删、改操作,而从数据库处理查询操作。
    • 从集中到分布,最基本的一个需求不是数据存储的瓶颈,而是在于计算的瓶颈,即SQL查询的瓶颈,我们知道,正常情况下,insert SQL就是几十个毫秒的时间写入完成,而系统中的大多数SelectSQL则要几秒到几分钟才能有结果,很多复杂的SQL,其消耗CPU的能力超强,不亚于死循环的威力。
      所以使用读写分离就是为了增强数据库的性能。

    读写分离方案

    • MyCat的读写分离是建立在MySQL主从复制的基础上实现的,所以必须先搭建MySQL的主从复制。数据库读写分离对于大型系统或访问量很高的互联网应用来说,是必不可少的一个重要功能。
      MyCat实现的读写分离和自动切换机制,需要MySQL的主从复制技术配合。
    • MySQL主从复制的常见拓扑结构
      • 一主一从
        最基础的复制结构,用来分担之前单机数据库服务器的压力,可以进行读写分离。
      • 一主多从
        一台Slave承受不住读请求压力时,可以添加多台,进行负载均衡,分散读压力
      • 双主复制
        双主结构就是用来解决这个问题的,互相将对方作为自己的Master,自己作为对方的Slave来进行复制,但对外来说,还是一主一从。
      • 级联复制
        级联结构就是通过减少直接从属于Master的Slave数量,减轻Master的压力,分散复制请求,从而提高整体的复制效率。
      • 双主级联
        MySQL的复制结构有很多种方式,复制的最大问题是数据延时,选择复制结构时需要根据自己的具体情况,并评估好目标结构的延时对系统的影响。

    2.搭建读写分离

    • 双击master会话,打开一个新窗口进行MyCat的操作。
    • 修改schema.xml,修改如下
    "1.0"?>
    <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
    "http://io.mycat/">
            <!-- 数据库配置 与server.xml中的数据库对应 即逻辑库  -->
            "TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="db_node">
            </schema>
            <!-- 分片配置 即分片节点 -->
            "db_node" dataHost="db_host" database="db_test" />
            <!-- 物理数据库配置 即节点主机 -->
            "db_host" maxCon="1000" minCon="10" balance="1"
                              writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                    select user()</heartbeat>
                    <!-- can have multi write hosts -->
                    "hostM1" url="192.168.126.17:3305" user="root"
                                       password="123456">
                            "hostS1" url="192.168.126.18:3304" user="root" password="123456" />
                    </writeHost>
    </mycat:schema>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    参数:

    • schema参数:

      • schema:逻辑库
      • name:逻辑库名称
      • sqlMaxLimit:一次最多取多少条数据
      • table:逻辑表
      • dataNode:数据节点 对应dataNode标签
      • rule:分片规则 对应rule.xml
      • primaryKey:分片主键 可缓存
    • dataNode参数:

      • name:分片名字
      • dataHost:分片主机
      • database:分片数据库
    • dataHost参数:

      • dataHost:数据主机(节点主机)

      • dbType:数据库驱动native:MySQL JDBC: oracle SQLServer

      • Balance参数设置:

        • balance=“0”, 所有读操作都发送到当前可⽤的writeHost上。
        • balance=“1”,所有读操作都随机的发送到readHost。
        • balance=“2”,所有读操作都随机的在writeHost、readhost上分发
      • WriteType参数设置:

        • writeType=“0”, 所有写操作都发送到可⽤的writeHost上。
        • writeType=“1”,所有写操作都随机的发送到readHost。
        • writeType=“2”,所有写操作都随机的在writeHost、readhost分上发。
      • switchType参数设置:

        • switchType=“1”, 主从自动切换
        • switchType=“2”,从机延时超过slaveThreshold值时切换为主读
    • 在此新窗口mycat,重启mycat:mycat restart

    • 进入mycat数据库管理命令行:mysql -u root -p123456 -h 192.168.126.17 -P 8066
      这-p和密码不能有空格
      如果连接不上,可能是配置文件schema.xml配错了,或者mysql没启动等原因

    • 在数据端口8066命令行使用TESTDB数据库:use TESTDB

    • 从MyCat数据端口命令行插入一条数据:insert into dog values(2,@@hostname);
      @@hostname是docker的容器id,因为是插入操作,所以是master的id。

    3.搭建MySQL双主双从

    1.配置MySQL的配置文件

    • 双主双从,即将对方作为自己的master,自己作为对方的slave来进行复制,但对外来讲,还是一主一从。
      一主一从,读写压力比较大会出现性能性问题;使用一主多从来解决,防止master单点故障;又使用双主双从来解决。

    • 准备4台虚拟机或者在,搭建双主双从
      不小心区分了端口号,但是没有影响,问题不大

    • 第一台虚拟机的MySQL服务master1docker run -d -p 3360:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=master1 mysql:latest

    • 第二台虚拟机的MySQL服务slave1docker run -d -p 3370:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=slave1 mysql:latest

    • 第三台虚拟机的MySQL服务master2docker run -d -p 3380:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=master2 mysql:latest

    • 第四台虚拟机的MySQL服务slave2docker run -d -p 3390:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=slave2 mysql:latest

    修改master1配置文件

    • 将mysql.cnf文件拷贝出来:docker cp master1:/etc/my.cnf .

    • 修改配置文件my.cnf:vim my.cnf,添加如下配置:

      [mysqld]
      
      # 配置唯一ID
      server-id=17
      # 开启二进制日志
      log-bin=mysql-bin
      # 设置不要复制的数据库(可设置多个)
      binlog-ignore-db=mysql
      binlog-ignore-db=information_schema
      # 设置logbin格式
      binlog_format=STATEMENT
      # 在作为从数据库的时候,有写入操作也要更新二进制文件
      log-slave-updates
      # 指自增字段的起始值,其默认值是1,取值范围是1~65535
      auto_increment_increment=2
      # 指字段一次递增多少,取值范围是1~65535
      auto_increment_offset=1
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 将my.cnf文件拷贝回master1容器:docker cp my.cnf master1:/etc/

    修改master2配置文件

    • 将mysql.cnf文件拷贝出来:docker cp master2:/etc/my.cnf .

    • 修改配置文件my.cnf:vim my.cnf,添加如下配置:

      [mysqld]
      
      # 配置唯一ID
      server-id=19
      # 开启二进制日志
      log-bin=mysql-bin
      # 设置不要复制的数据库(可设置多个)
      binlog-ignore-db=mysql
      binlog-ignore-db=information_schema
      # 设置需要复制的数据库
      #binlog-do-db=需要复制的master数据库
      # 设置logbin格式
      binlog_format=STATEMENT
      # 在作为从数据库的时候,有写入操作也要更新二进制文件
      log-slave-updates
      # 指自增字段的起始值,其默认值是1,取值范围是1~65535
      auto_increment_increment=2
      # 指字段一次递增多少,取值范围是1~65535
      auto_increment_offset=1
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 将my.cnf文件拷贝回master2容器:docker cp my.cnf master2:/etc/

    修改slave1配置文件

    • 将mysql.cnf文件拷贝出来:docker cp slave1:/etc/my.cnf .

    • 修改配置文件my.cnf:vim my.cnf,添加如下配置:

      [mysqld]
      
      # 配置唯一ID
      server-id=18
      # 开启中继日志
      relay-log=mysql-relay
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 将my.cnf文件拷贝回slave1容器:docker cp my.cnf slave1:/etc/

    修改slave2配置文件

    • 将mysql.cnf文件拷贝出来:docker cp slave2:/etc/my.cnf .

    • 修改配置文件my.cnf:vim my.cnf,添加如下配置:

      [mysqld]
      
      # 配置唯一ID
      server-id=20
      # 开启中继日志
      relay-log=mysql-relay
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 将my.cnf文件拷贝回slave2容器:docker cp my.cnf slave2:/etc/

    • 重启所有MySQL服务:

      • docker restart master1
      • docker restart master2
      • docker restart slave1
      • docker restart slave2
    • 进入容器:docker exec -it 容器名或id bash
      进入docker容器后只显示bash-4.4,则输入:cp /etc/skel/.bash* /root/
      exit退出容器,再进容器就可以了。
      如果此时若容器没有启动,可能是配置文件有问题。

    • 在两个master中,进入到Mysql命令行,创建数据同步用户slave:
      CREATE USER slave IDENTIFIED BY '123456';
      GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
      GRANT ALL PRIVILEGES ON *.* TO 'slave'@'%';
      FLUSH PRIVILEGES;

    • 因为版本是大于8.0.4的,需要在两个master上更换密码机制并修改密码:ALTER USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
      查看用户的密码机制:select user,host,plugin from user;

    • 查看master1的状态:show master status;

    • 从库slave1连接主库master1,在从库slave1中的mysql命令行执行:change master to master_host='192.168.126.17',master_user='slave',master_port=3360,master_password='123456',master_log_file='mysql-bin.000007',master_log_pos=157;

    • 启动复制功能:start slave;

    • 查看master2的状态:show master status;

    • 从库slave2连接主库master2,在从库slave2中的mysql命令行执行:change master to master_host='192.168.126.19',master_user='slave',master_port=3380,master_password='123456',master_log_file='mysql-bin.000006',master_log_pos=157;

    • 启动复制功能:start slave;

    • 查看连接状态:show slave status \G;
      当两个线程都显示yes时,配置主从复制成功。

    • 配置master1成为master2的slave
      在master1的mysql命令行,配置为master2的slave:change master to master_host='192.168.126.19',master_user='slave',master_port=3380,master_password='123456',master_log_file='mysql-bin.000007',master_log_pos=157;

    • 配置master2成为master1的slave
      在master2的mysql命令行,配置为master1的slave:change master to master_host='192.168.126.17',master_user='slave',master_port=3360,master_password='123456',master_log_file='mysql-bin.000008',master_log_pos=157;

    • 启动复制功能:start slave;

    • 查看连接状态:show slave status \G;
      当显示Slave_IO_Running: Yes,Slave_SQL_Running: Yes时,配置成功。

    2.配置MyCat的配置文件

    • 进入到/usr/local/mycat/conf目录下,编辑schema.xml文件:vim schema.xml,配置如下:

      "1.0"?>
      <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
      "http://io.mycat/">
      
              <!-- 数据库配置 与server.xml中的数据库对应 即逻辑库  -->
              "TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="db_node">
              </schema>
      
              <!-- 分片配置 即分片节点 -->
              "db_node" dataHost="db_host" database="db_test" />
      
              <!-- 物理数据库配置 即节点主机 -->
              "db_host" maxCon="1000" minCon="10" balance="1"
                                writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                      select user()</heartbeat>
                      <!-- 读写分离 双主双从 -->
                       "hostM1" url="192.168.126.17:3360" user="root" password="123456">
                              "hostS1" url="192.168.126.18:3370" user="root" password="123456" />
                      </writeHost>
                       "hostM2" url="192.168.126.19:3380" user="root" password="123456">
                              "hostS2" url="192.168.126.20:3390" user="root" password="123456" />
                      </writeHost>
      
              </dataHost>
      </mycat:schema>
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

      即在一主一从的配置基础上,再配置另一个主从。

      • 参数:
        • writeType=“0”:所有写操作发送到配置的第一个 writeHost,第一个挂了切到还生存的第二个writeHost,重新启动后已切换后的为准,切换记录在配置文件中:dnindex.properties.
          即第二个writeHost为第一个的备机。
        • writeType=“1”:所有写操作都随机的发送到配置的 writeHost,1.5 以后废弃不推荐。
        • switchType=“-1” :表示不自动切换 mysql 实例
        • switchType=“1” :默认值,自动切换
    • 启动mycat:mycat start

    二、MyCat分片技术

    1.垂直拆分-分库

    • 目前很多互联网系统都存在单表数据量过大的问题,这就降低了查询数据,影响了客户体验。为了提高查询效率,可以优化SQL语句,优化表结构和索引,不过对那些百万级千万级的数据库表,即便是优化过后,查询速度还是满足不了要求。

    • 垂直分割是指数据表列的拆分,把一张列比较多的表拆分为多张表。表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。
      拆分原则:

      • 把不常用的字段单独放在一张表
      • 把text,blob等大字段拆分出来放在附表中
      • 经常组合查询的列放在一张表中
    • 修改mycat目录下的schema.xml配置文件,配置如下:

      "1.0"?>
      <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
      "http://io.mycat/">
      
              <!-- 数据库配置 与server.xml中的数据库对应 即逻辑库  -->
              "TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
              		<!-- 垂直拆分 -->
                      "customer" dataNode="dn2"/>
              </schema>
      
              <!-- 分片配置 即分片节点 实现垂直分库 -->
              "dn1" dataHost="host1" database="orders" />
              "dn2" dataHost="host2" database="orders" />
              <!-- 物理数据库配置 即节点主机 -->
              "host1" maxCon="1000" minCon="10" balance="1"
                                writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                      select user()</heartbeat>
                       "hostM1" url="192.168.126.17:3360" user="root" password="123456">
                       </writeHost>
              </dataHost>
      
              "host2" maxCon="1000" minCon="10" balance="1"
                                writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                      select user()</heartbeat>
                       "hostM1" url="192.168.126.19:3380" user="root" password="123456">
                       </writeHost>
              </dataHost>
      </mycat:schema>
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
    • 停止之前两个master的相互复制,都在mysql命令行中执行:stop slave;

    • 两个master同时创建数据库orders:create database orders;

    • 将两个master的密码机制都改为mysql_native_password:ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
      这样MyCat才能访问到,否则密码不正确,因为Mysql是最新版的

    • 重启mycat:mycat restart

    • 进入到master1的Mysql命令行:mysql -u root -p123456 -h 192.168.126.17 -P 8066

    • 在MyCat中,使用数据库TESTDB:use TESTDB;

    • 创建表customer:create table customer(id int auto_increment,name varchar(200),primary key(id));

    • 创建表orders:create table orders(id int auto_increment,order_type int,customer_id int,amount decimal(10,2),primary key(id));

    • 创建表orders_detail:create table orders_detail(id int auto_increment,order_id int,detail varchar(200),primary key(id));

    • 创建表dict_orders_type:create table dict_orders_type(id int auto_increment,order_type varchar(200),primary key(id));

    • 此时在mycat的TESEDB逻辑库中,创建4个表,但是customer表被创建到master2的orders,其他都在master1的orders中,实现了分库。
      即指定customer表都在master2的orders中执行,其余都在master1的orders中执行。

    • 2.水平拆分-分表

      • 垂直拆分只是把表按模块分到不同数据库,但没有解决单表大数据量的问题。
      • 水平拆分,相对于垂直拆分水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。
        数据的水平切分是按照数据行的切分,就是将表中的某些行切分到一个数据库,而将另外某些行又切分到其他数据库中。

      实现分表:

      1. 选择要拆分的表
        MySQL单表存储条数是有瓶颈的,单表达到1000W条就达到了瓶颈,会影响查询效率,需要进行水平拆分(分表)来优化。

      2. 分表字段
        orders表为例,可以根据不同字段进行分表。
        字段id:查询订单注重时效,历史订单被查询的次数少,如此分片会造成一个节点访问多,一个节点访问少,不平均。
        字段customer_id:根据客户id去分,两个节点访问平均。

      3. 修改配置文件schema.xml

         <!-- 水平拆分 -->
         
      "orders" dataNode="dn1,dn2" rule="mod-rule" />
      • 1
      • 2

      即为orders表设置数据节点为dn1、dn2,并指定分片规则为mod-rule(自定义)

    • 修改配置文件rule.xml

      • 分片规则:在rule配置文件里新增分片规则mod-rule,并指定规则适用字段customer_id,还有选择分片算法mod_long(对字段求模运算),customer_id对两个节点求模,根据结果分片。配置算法mod-long参数count为2,两个节点
      • 代码:
      "mod-rule">
                  
                          customer_id</columns>
                          mod-long</algorithm>
                  </rule>
       </tableRule>
       <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
                  <!-- how many data nodes -->
                  "count">2</property>
       </function>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 在master2中的orders数据库中创建orders表

      create table orders(
        id int auto_increment,
        order_type int,
        customer_id int,
        amount decimal(10,2),
        primary key(id)
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 重启mycat,生效配置:mycat restart

    • 在mycat的8066端口插入数据,测试是否实现水平拆分

      INSERT INTO orders(id,order_type,customer_id,amount) VALUES (1,101,100,100100);
      INSERT INTO orders(id,order_type,customer_id,amount) VALUES(2,101,100,100300);
      INSERT INTO orders(id,order_type,customer_id,amount) VALUES(3,101,101,120000);
      INSERT INTO orders(id,order_type,customer_id,amount) VALUES(4,101,101,103000);
      INSERT INTO orders(id,order_type,customer_id,amount) VALUES(5,102,101,100400);
      INSERT INTO orders(id,order_type,customer_id,amount) VALUES(6,102,100,100020);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 在master1和master通过select查看分片是否正确即可。

      3.ER表

      • 问题,orders表分片了,那和它相关的orders_detail表未分片,join联查时,master1正常查询出结果,master2由于没有orders_detail表,则报错,最后聚合结果也是错的。

      • ER表,将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了JOIN的效率和性能问题,根据这一思路,提出了基于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。

      • 修改配置文件schema.xml,代码如下:

         
      "orders" dataNode="dn1,dn2" rule="mod-rule" > <!-- ER表 --> "orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" /> </table>
      • 1
      • 2
      • 3
      • 4

      即在原先的table下添加一个子表childTable。

      • 在master2中的orders数据库中创建orders_detail表:

        create table orders_detail(
        id int auto_increment,
        order_id int,
        detail varchar(200),
        primary key(id)
        );
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
      • 重启Mycat:mycat restart

      • 进入到8066端口,在mycat中插入orders_detail数据:

        insert into orders_detail(detail, order_id) values('detail1',1);
        insert into orders_detail(detail, order_id) values('detail1',2);
        insert into orders_detail(detail, order_id) values('detail1',3);
        insert into orders_detail(detail, order_id) values('detail1',4);
        insert into orders_detail(detail, order_id) values('detail1',5);
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • 在Mycat中使用join来测试ER表配置是否成功:select a.*,b.detail from orders a join orders_detail b on a.id=b.order_id;

      4.全局表

      • 全局表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,字典表具有以下特性:
        • 变动不频繁
        • 数据量总体变化不大
        • 数据规模不大,很少有超过10W条记录
      • 全局表特征:
        • 全局表插入更新操作会实时在所有节点上执行,保持各个分片的数据一致。
        • 全局表的查询操作,只从一个节点获取。
        • 全局表可以和任何一个表进行JOIN操作。
      • 将字典表或者符合字典表特性的一些表定义为全局表,则从另外一个方面,很好的解决了数据JOIN的难题。通过全局表+基于E-R关系的分片策略,可以满足80%的企业应用开发。
      • 在master2的orders数据库中创建dict_order_type表:
      create table dict_order_type(
        id int auto_increment,
        order_type varchar(200),
       primary key(id)
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 在master1修改一下表名: alter table dict_orders_type rename to dict_order_type;
        之前创建table错了好像,没有错就不用改名字

      • 在schema.xml配置全局表,如下

        <!-- 全局表 -->
        
      "dict_order_type" dataNode="dn1,dn2" type="global"></table>
      • 1
      • 2
      • 3
    • 重启MyCat:mycat restart

    • 重新进入8066端口,插入数据:

      insert into dict_order_type(id,order_type) values(101,'type1');
      insert into dict_order_type(id,order_type) values(102,'type2');
      
      • 1
      • 2
    • 此时在master1和master2都查询得到插入的两条记录:select * from dict_order_type;

    • 三、MyCat分片规则

      1. 取模分片

      • 取模分片就是根据数据表的某一个字段,通常是某一个整数型的字段,对其进行十进制的取模运算,将运算结果作为MyCat的路由结果。
        • 优点:这种策略可以很好的分散数据库写的压力。
        • 缺点:出现了范围查询,就需要MyCat去合并结果,当数据量偏高的时候,这种夸库查询+合并结果消耗的时间可能会增加很多,尤其是还出现order by的时候。
      • tableRule标签,定义表规则
        • name:指定唯一名字,用于标识不同的表规则。内嵌的rule标签则指定对物理表中的哪一列进行拆分和使用什么路由算法。
        • columns:指定要拆分的列名字。
        • algorithm:使用function标签的name属性。连接表规则和具体路由算法。多个表规则(tableRule)可以连接到同一个路由算法(function)上。
      • function标签,定义具体路由算法
        • name:指定算法的名字
        • class:指定路由算法具体的类名字
        • property:具体算法需要用的一些属性
        • count:表示需要取模的最大值,将数据分成该配置的切片

      2. 枚举分片

      • 实现原理,有些业务需要按照省份或区县来做保存,这类业务使用本条规则。

      • 实现过程,需要定义三个值,规则均是在rule.xml中定义。

        • tableRule
        • function
        • mapFile
      • 在master1和master2的orders数据库中,创建示例表orders_ware_info

        CREATE TABLE orders_ware_info(
         `id` INT AUTO_INCREMENT comment '编号',
         `order_id` INT comment '订单编号',
         `address` VARCHAR(200) comment '地址',
         `areacode` VARCHAR(20) comment '区域编号',
         PRIMARY KEY(id)
        );
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      • 修改schema.xml文件

         <!-- 枚举分片 -->
         
      "orders_ware_info" dataNode="dn1,dn2" rule="sharding-by-intfile" />
      • 1
      • 2

      即表orders_ware_info,按照规则sharding-by-intfile进行分片

    • 修改rule.xml文件

      <!-- 枚举分片 -->
      "sharding-by-intfile">
               
                       areacode</columns>
                       hash-int</algorithm>
               </rule>
      </tableRule>
      <function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
      	"mapFile">partition-hash-int.txt</property>
      	"type">1</property>
          "defaultNode">0</property>
      </function>
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      即按照字段areacode进行枚举分片。
      参数:

      • mapFile:指的是配置文件名。
      • type:默认值为0,0表示Integer,非零表示String。
      • defaultNode 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点。
    • 修改partition-hash-int.txt文件

      10086=0
      10010=1
      
      • 1
      • 2

      0则代表第一个节点,1则代表第二个节点

    • 重启mycat:mycat restart

    • 进入8066端口,使用TESTDB数据库,插入2条数据:

    • INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (1,1,'北京','10086');
      INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (2,2,'天津','10010');
      
      • 1
      • 2

      3.范围分片

      • 实现原理,此分片适用于提前规划好分片字段的某个范围属于哪个分片。

        • 优点:适用于想明确知道某个分片字段的某个范围具体在哪一个节点。
        • 缺点:如果短时间内有大量的批量插入操作,那么某个分片节点可能一下会承受比较大的数据库压力,而别的分片节点此时可能处于闲置状态,无法利用其他节点进行分担压力(热点数据问题)。
      • 在master1和master2的orders数据库中,创建示例表payment_info

        CREATE TABLE payment_info
        (
         `id` INT AUTO_INCREMENT comment '编号',
         `order_id` INT comment '订单编号',
         `payment_status` INT comment '支付状态',
         PRIMARY KEY(id)
        );
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      • 修改配置文件schema.xml

         <!-- 范围分片 -->
         
      "payment_info" dataNode="dn1,dn2" rule="auto-sharding-long" />
      • 1
      • 2
    • 修改配置文件rule.xml

      <!-- 范围分片 -->
      "auto-sharding-long">
             
                     order_id</columns>
                     rang-long</algorithm>
             </rule>
      </tableRule>
      <function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
             "mapFile">autopartition-long.txt</property>
      </function>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      参数

      • mapFile:指的是配置文件名
      • type:默认值为0,0表示Integer,非零表示String。因为我接下来的测试是基于省份分片,所以需type指定为1。
      • defaultNode 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点。
    • 修改文件autopartition-long.txt

      0-102=0
      103-200=1
      
      • 1
      • 2
    • 重启mycat:mycat restart

    • 在8066端口插入数据用来测试:

      INSERT INTO payment_info (id,order_id,payment_status) VALUES (1,101,0);
      INSERT INTO payment_info (id,order_id,payment_status) VALUES (2,102,1);
      INSERT INTO payment_info (id,order_id ,payment_status) VALUES (3,103,0);
      INSERT INTO payment_info (id,order_id,payment_status) VALUES (4,104,1);
      
      • 1
      • 2
      • 3
      • 4
    • 4.按日期(天)分片

      • 实现原理,此规则按天分片,设定时间格式、范围

      • 在master1和master2中创建示例表login_info

        create table login_info(
          id int auto_increment comment '编号',
          user_id int comment '用户编号',
          login_date date comment '登录时间',
         primary key(id)
        );
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
      • 修改配置文件schema.xml

        <!-- 日期(天)分片 -->
        
      "login_info" dataNode="dn1,dn2" rule="sharding-by-date" />
      • 1
      • 2
    • 修改配置文件rule.xml

      <!-- 日期(天)分片 -->
      "sharding-by-date">
              
                      login_date</columns>
                      partbyday</algorithm>
              </rule>
      </tableRule>
      <!-- 日期(天)分片 -->
      <function name="partbyday" class="io.mycat.route.function.PartitionByDate">
              "dateFormat">yyyy-MM-dd</property>
              "sNaturalDay">0</property>
              "sBeginDate">2022-01-01</property>
              "sEndDate">2022-01-04</property>
              "sPartionDay">2</property>
      </function>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      参数

      • columns:分片字段,algorithm:分片函数
      • dateFormat:日期格式
      • sBeginDate:开始日期
      • sEndDate:结束日期,则代表数据达到了这个日期的分片后循环从开始分片插入
      • sPartionDay:分区天数,即默认从开始日期算起,每隔 2 天一个分区
    • 重启mycat:mycat restart

    • 在8066端口插入数据用来测试:

      insert into login_info(id,user_id,login_date) values(1,101,'2022-01-01');
      insert into login_info(id,user_id,login_date) values(2,102,'2022-01-02');
      insert into login_info(id,user_id,login_date) values(3,103,'2022-01-03');
      insert into login_info(id,user_id,login_date) values(4,104,'2022-01-04');
      insert into login_info(id,user_id,login_date) values(5,105,'2022-01-05');
      insert into login_info(id,user_id,login_date) values(6,106,'2022-01-06');
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 5.全局序列

      • 在实现分库分表的情况下,数据库自增主键已无法保证全局唯一。

      解决方案

      • 本地文件方式
        此方式MyCat将sequence配置到文件中,当使用到sequence中的配置后,MyCat会更新classpath中的sequence_conf.properties文件中sequence当前的值。

        • 优点:本地加载,读取速度较快
        • 缺点:抗风险能力差,MyCat所在主机宕机后,无法读取本地文件。
      • 本地时间戳方式
        全局序列ID=64位二进制(42(毫秒)+ 5(机器ID)+ 5(业务编码)+ 12(重复累加))换算成十进制,为18位数的long类型,每毫秒可以并发12位二进制的累加

        • 优点:配置简单
        • 缺点:18位ID过长
      • 数据库方式
        利用数据库的一个表来进行计数累加。但是并不是每次生成序列都读写数据库,这样效率太低。MyCat会预加载一部分号段到MyCat内存中,这样大部分读写序列都是在内存中完成的。如果内存中的号段用完了,MyCat会再向数据库要一次。
        原理:在数据库中建立一张表,存放sequence名称(name),sequence当前值(current_value),步长(increment int类型每次读取多少个sequence,假设为K)等信息。

      数据库解决全局序列

      • 修改MyCat配置文件server.xml

        <!-- 全局序列类型:0-本地文件,1-数据库方式,2-时间戳方式 -->
        "sequenceHandlerType">1</property>
        
        • 1
        • 2
      • 修改配置文件schema.xml

        <!-- 全局序列-数据库方式 -->
        
      "test" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2" rule="mod-long" />
      "mycat_sequence" primaryKey="name" dataNode="dn2" />
      • 1
      • 2
      • 3

      即前面一个表用来做测试,后面一个表就是存放sequence的表。

    • 修改文件sequence_db_conf.properties

      GLOBAL=dn1
      COMPANY=dn1
      CUSTOMER=dn1
      ORDERS=dn1
      MYCAT=dn2
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 在master2的orders数据库中添加MYCAT_SEQUENCE表

      DROP TABLE IF EXISTS MYCAT_SEQUENCE;
      CREATE TABLE MYCAT_SEQUENCE (name VARCHAR(50) NOT NULL,current_value INT NOT NULL,increment INT NOT NULL DEFAULT 100, PRIMARY KEY(name)) ENGINE=InnoDB;
      
      • 1
      • 2
    • MYCAT_SEQUENCE表插入初始记录

      INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ('mycat', -99, 100);
      
      • 1

      代表插入了一个名为mycat的sequence,当前值为-99,步长为100。

    • 在master2的orders数据库中创建全局序列所需存储过程

      • 获取当前sequence的值
        DELIMITER $$ 
        CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS VARCHAR(64)
        DETERMINISTIC 
        BEGIN
        DECLARE retval VARCHAR(64);
        SET retval="-999999999,null";
        SELECT CONCAT(CAST(current_value AS CHAR),",",CAST(increment AS CHAR)) INTO retval FROM
        MYCAT_SEQUENCE WHERE NAME = seq_name;
        RETURN retval;
        END $$
        DELIMITER ;
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
      • 设置sequence的值
        DELIMITER $$
        CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),VALUE INTEGER) RETURNS 
        VARCHAR(64)
        DETERMINISTIC
        BEGIN
        UPDATE MYCAT_SEQUENCE
        SET current_value = VALUE
        WHERE NAME = seq_name;
        RETURN mycat_seq_currval(seq_name);
        END $$
        DELIMITER ;
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
      • 获取下一个sequence的值
        DELIMITER $$
        CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS VARCHAR(64) 
        DETERMINISTIC
        BEGIN
        UPDATE MYCAT_SEQUENCE
        SET current_value = current_value + increment WHERE NAME = seq_name;
        RETURN mycat_seq_currval(seq_name);
        END $$
        DELIMITER ;
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
    • 重启mycat:mycat restart

    • 在master1和master2中创建test表:

      create table test(id int,name varchar(10));
      
      • 1
    • 在MyCat中向test表中添加测试数据

      insert into test(id,name) values(next value for MYCATSEQ_MYCAT,(select database()));
      
      • 1
    • 查询数据进行验证,看是否自增:SELECT * FROM test order by id asc;

    • 总结:

      1. MyCat的读写分离及自动切换技术,是基于MySQL的主从复制技术实现的。
        MySQL主从复制解决数据一致性的问题。MyCat的读写分离主要是为了实现高性能。
        在schema.xml配置文件中配置逻辑库等,需要跟server.xml中的schema对应。
      2. MySQL双主双从,实现了读写分离、高可用、高性能,即两个readHost都可以对外提供读操作;而两个writeHost中,第一个writeHost对外提供写操作,第二个writeHost为第一个的备机。在一主多从到双主双从,可以减少单个writeHost的复制压力(即高性能),也实现了写操作的高可用。
        它们的结构则是两个主从,并且这两个主会相互成为对方的从。
      3. 垂直拆分,即将不常用字段和大字段进行拆分,将常用的组合查询的列放在一张表。
        垂直拆分用于分库,在配置文件schema.xml中配置,即schame参数有一个分片节点,当不指定表给某个节点时,全部的SQL都交由这个节点处理;有指定则将指定表的SQL交由指定节点处理。指定节点会有一个对应的节点主机dataHost。
        MyCat垂直拆分主要解决性能问题。
      4. 水平拆分,数据的水平切分是按照数据行的切分,就是将表中的某些行切分到一个数据库,而将另外某些行又切分到其他数据库中。
        需要在schema.xml配置需要水平切分的表,切分到哪个DataNode,以及分片规则rule的名字,对应到rule.xml中配置。
        配置相比于垂直切分,水平切分多配置了一个rule.xml;在主机层面,垂直切分,将表按字段切分到多个主机的同一个数据库名,水平切分,将表按行切分到多个主机的同一个数据库名。
        MyCat水平拆分主要解决表数据量大的问题。
      5. ER表,解决JOIN的效率和性能问题,即用来处理表与子表关联查询的问题,在schema.xml中配置,在表中添加一个子表childTable。
        即水平拆分时,行数据会被切分,在joid查询时,又需要数据库中存在对应的子表行数据,所以使用ER表来让子表的行数据跟随对应表的行数据。即joinKey与parentKey的值相同的会被分配在同一个主机的同一个数据库中。
      6. 全局表,即所有分片节点都有的一个表,并且插入更新操作在所有节点实时执行。
        主要解决变动不频繁和数据量总体不大的数据,通过type="global"来设置全局表。
      7. 分片规则有取模、枚举、范围、日期、全局序列等。
        分片枚举指的是需要按照省份或者区县来做保存的。
        范围分片缺点是处理热点数据问题。
        全局序列解决分表分库后id不唯一的问题。
        通过设置server.xml的sequenceHandlerType为1(即以数据库的方式)来实现全局序列。
    • 相关阅读:
      《c++ Primer Plus 第6版》读书笔记(4)
      数据结构与算法-希尔排序
      江苏服务器租用风险有哪些?
      解密地理位置模拟攻防之道
      Docker项目部署lnmp+wordpress
      URL与URI小结
      LeetCode刷题day24||回溯算法理论基础&&77. 组合--回溯
      LeetCode2409——统计共同度过的日子数
      C/C++书籍信息系统
      谷粒商城 高级篇 (十三) --------- 异步&线程池
    • 原文地址:https://blog.csdn.net/weixin_49076273/article/details/126634443