个人学习

分布式数据库中间件 MyCat

2020-01-07  本文已影响0人  Lucie_xxm

概述

Mycat 是什么?从定义和分类来看,它是一个开源的分布式数据库系统,是一个实现了 MySQL 协议的 Server,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生(Native) 协议与多个 MySQL 服务器通信,也可以用 JDBC 协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为 N 个小表,存储在后端 MySQL 服务器里或者其他数据库里

什么是分库分表(数据切分)

简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果

数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式。一种是按照不同的表(或者 Schema)来切分到不同的数据库(主机)之上,这种切可以称之为数据的垂直(纵向)切分;另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分

垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰

水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂,后期的数据维护也会更为复杂一些

垂直切分

一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:

系统被切分成了,用户,订单交易,支付几个模块。一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一个功能模块所需要的数据对应到数据库中就是一个或者多个表。而在架构设计中,各个功能模块相互之间的交互点越统一越少,系统的耦合度就越低,系统各个模块的维护性以及扩展性也就越好。这样的系统,实现数据的垂直切分也就越容易

但是往往系统之有些表难以做到完全的独立,存在这扩库 join 的情况,对于这类的表,就需要去做平衡,是数据库让步业务,共用一个数据源,还是分成多个库,业务之间通过接口来做调用。在系统初期,数据量比较少,或者资源有限的情况下,会选择共用数据源,但是当数据发展到了一定的规模,负载很大的情况,就需要必须去做分割

一般来讲业务存在着复杂 join 的场景是难以切分的,往往业务独立的易于切分。 如何切分,切分到何种程度是考验技术架构的一个难题

由于垂直切分是按照业务的分类将表分散到不同的库,所以有些业务表会过于庞大,存在单库读写与存储瓶颈,所以就需要水平拆分来做解决

水平切分

相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,如图:


拆分数据就需要定义分片规则。关系型数据库是行列的二维模型,拆分的第一原则是找到拆分维度。 比如:从会员的角度来分析,商户订单交易类系统中查询会员某天某月某个订单,那么就需要按照会员结合日期来拆分,不同的数据按照会员 ID 做分组,这样所有的数据查询 join 都会在单库内解决;如果从商户的角度来讲,要查询某个商家某天所有的订单数,就需要按照商户 ID 做拆分;但是如果系统既想按会员拆分,又想按商家数据,则会有一定的困难。如何找到合适的分片规则需要综合考虑衡量。几种典型的分片规则包括:

如图,切分原则都是根据业务找到适合的切分规则分散到不同的库,下面用用户 ID求模举例

数据源管理方案

绝大部分人在面对上面这两种解决思路的时候都会倾向于选择第二种,尤其是系统不断变得庞大复杂的时候。确实,这是一个非常正确的选择,虽然短期内需要付出的成本可能会相对更大一些,但是对整个系统的扩展性来说,是非常有帮助的

数据库中间件 MyCat

MyCat 是一个强大的数据库中间件,不仅仅可以用作读写分离、以及分表分库、容灾备份,而且可以用于多租户应用开发、云平台基础设施、让你的架构具备很强的适应性和灵活性,借助于即将发布的 MyCat 智能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或手工调整后端存储,将不同的表映射到不同存储引擎上,而整个应用的代码一行也不用改变

应用场景

环境准备

部署方式全部基于 Docker

部署 3 台 MySQL 容器

案例分片方案按照自定义数字范围分片方式(auto-sharding-long)进行数据库分片,其规则要求需要 3 台 MySQL,docker-compose.yml 配置如下

version: '3.1'
services:
  mysql-1:
    image: mysql
    container_name: mysql-1
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
    ports:
      - 3306:3306
    volumes:
      - ./data:/var/lib/mysql
version: '3.1'
services:
  mysql-2:
    image: mysql
    container_name: mysql-2
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
    ports:
      - 3307:3306
    volumes:
      - ./data:/var/lib/mysql
version: '3.1'
services:
  mysql-3:
    image: mysql
    container_name: mysql-3
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
    ports:
      - 3308:3306
    volumes:
      - ./data:/var/lib/mysql

部署 MyCat 数据库中间件

git clone https://github.com/dekuan/docker.mycat.git
cd docker.mycat
docker-compose build
# 注意:配置完成后再启动
docker-compose up -d

配置 MyCat 数据库分片

<mycat:server xmlns:mycat="http://io.mycat/">
    <!-- Mycat 数据库用户名 -->
    <user name="root">
        <!-- Mycat 数据库密码 -->
        <property name="password">123456</property>

        <!-- Mycat 数据库名 -->
        <property name="schemas">myshop</property>

        <!-- 是否使用加密的密码,0 表示不使用加密的密码 -->
        <property name="usingDecrypt">0</property>
    </user>
</mycat:server>
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

        <schema name="myshop" checkSQLschema="true" sqlMaxLimit="100">
                <!-- 需要分片的表
                     在节点 dataNode1, dataNode2, dataNode3 上分片
                     分片规则是 auto-sharding-long 即连续分片规则之自定义数字范围分片
                     对应的分片规则配置文件在 config/mycat/rule.xml
                -->
                <table
                                name="tb_admin"
                                primaryKey="id"
                                dataNode="dataNode1,dataNode2,dataNode3"
                                rule="auto-sharding-long"/>
        </schema>

        <!-- 数据节点 dataNode1,对应的主机 dataHost1, 对应是数据库 myshop_1 -->
        <dataNode name="dataNode1" dataHost="dataHost1" database="myshop_1" />
        <dataNode name="dataNode2" dataHost="dataHost2" database="myshop_2" />
        <dataNode name="dataNode3" dataHost="dataHost3" database="myshop_3" />

        <!-- 主机 dataHost1 -->
        <dataHost name="dataHost1" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <!-- MySQL 数据库的连接串 -->
                <writeHost
                                host="192.168.141.206"
                                url="jdbc:mysql://192.168.141.206:3306?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        <!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
                </writeHost>
        </dataHost>
        <dataHost name="dataHost2" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <writeHost
                                host="192.168.141.206"
                                url="jdbc:mysql://192.168.141.206:3307?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        <!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
                </writeHost>
        </dataHost>
        <dataHost name="dataHost3" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <writeHost
                                host="192.168.141.206"
                                url="jdbc:mysql://192.168.141.206:3308?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        <!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
                </writeHost>
        </dataHost>
        
</mycat:schema>
<!-- 第 32 行 -->
<tableRule name="auto-sharding-long">
    <rule>
        <!-- 指定分片表列名 -->
        <columns>id</columns>
        <!-- 指定分片函数与 function 对应 -->
        <algorithm>rang-long</algorithm>
    </rule>
</tableRule>
<!-- 第 105 行 -->
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
    <!-- 函数中的 mapFile 代表规则配置文件的路径,config/mycat/autopartition-long.txt -->
    <property name="mapFile">autopartition-long.txt</property>
</function>

# range start-end ,data node index
# K=1000,M=10000.
# ID 0-5000000 保存在 dataNode1
0-500M=0
# ID 5000000-10000000 保存在 dataNode2
500M-1000M=1
# ID 10000000-15000000 保存在 dataNode3
1000M-1500M=2

测试 MyCat 数据分片

分别创建数据库

分别在 3 台 MySQL 数据库上创建 myshop_1myshop_2myshop_3,按照刚才创建 MySQL 容器的序号创建

通过 MyCat 操作数据库

使用 SQLyog 之类的客户端工具连接 MyCat 数据库,默认端口号为 8066

创建测试数据

create table tb_admin (id int not null primary key,name varchar(100),sharding_id int not null);
insert into tb_admin(id, name,sharding_id) values(1000000, 'lixiaohong', 0);
insert into tb_admin(id, name,sharding_id) values(6000000, 'lixiaolu', 1);
insert into tb_admin(id, name,sharding_id) values(7000000, 'pgone', 1);
insert into tb_admin(id, name,sharding_id) values(11000000, 'jianailiang', 2);

检验测试结果

按照上面的配置规则

id1000000 的数据应该写入 dataNode1myshop_1.tb_admin 表中
id6000000 的数据应该写入 dataNode2myshop_2.tb_admin 表中
id7000000 的数据应该写入 dataNode2myshop_2.tb_admin 表中
id11000000 的数据应该写入 dataNode3myshop_3.tb_admin 表中

至此这说明分片成功了

上一篇下一篇

猜你喜欢

热点阅读