分布式数据库中间件 MyCat

Posted by 彭楷淳 on 2021-01-29
Estimated Reading Time 18 Minutes
Words 4.8k In Total
Viewed Times

MyCat 简介


MyCat 是一个功能强大的分布式数据库中间件,是一个实现了 MySQL 协议的 Server,前端人员可以把它看做是一个数据库代理中间件,用 MySQL 客户端工具和命令行访问;而后端人员可以用 MySQL 原生协议与多个 MySQL 服务器通信,也可以用 JDBC 协议与大多数主流数据库服务器通信。可以用作 读写分离分库分表(分片)容灾备份多租户应用开发大数据基础设施,使底层数据架构具备很强的适应性和灵活性。

MyCat 的智能优化模块可以使系统的数据访问瓶颈和热点一目了然,并且可以将这些统计分析数据自动或手工调整后端存储,将不同的表映射到不同存储引擎上,而整个应用的代码可以一行也不用变。

具体应用场景

  1. 读写分离:支持读写分离,主从切换,此配置最简单;
  2. 分库分表:对于超过 1000 万的表进行分片,最大支持 1000 亿的单表分片;
  3. 多租户应用:每个应用一个库,但应用程序只连接 MyCat,使程序不用改造本身,实现多租户化;
  4. 替代 Hbase:用于分析大数据;
  5. 报表系统:借助 MyCat 的分表能力,处理大规模报表的统计;
  6. 海量数据查询:比如 10 亿条频繁查询的记录需要在 3 秒内查询出来结果,除了基于主键的查询,还可能存在范围查询或其他属性查询,此时 MyCat 可能是最简单有效的选择。

MyCat 安装


环境:

  • CentOS7
  • JDK1.8
  • MySQL 01:192.168.127.130:3306
  • MySQL 02:192.168.127.130:3307
  • MySQL 03:192.168.127.130:3308
  • MyCat:192.168.127.130:8066

MyCat 使用 Java 开发,因此,运行 MyCat ,一定要具备 Java 环境,配置 Java 运行环境这个比较容易,网上资料也很多,我就不详细介绍了。

Java 环境安装好之后,首先下载 MyCat:

1
$ wget http://dl.mycat.io/1.6.7.1/Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz

下载完成后,对下载文件进行解压。

1
$ tar -zxvf Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz

解压成功后,会出现一个 mycat 目录,进入到 mycat/conf 目录,对 mycat 进行配置,首先来配置 schema.xml 文件:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

<schema name="hellomycat" checkSQLschema="true" sqlMaxLimit="100">

<table />
</schema>

<dataNode name="dataNode1" dataHost="dataHost1" database="hellomycat_1" />
<dataNode name="dataNode2" dataHost="dataHost2" database="hellomycat_2" />
<dataNode name="dataNode3" dataHost="dataHost3" database="hellomycat_3" />

<dataHost name="dataHost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>

<writeHost
host="192.168.127.130"
url="jdbc:mysql://192.168.127.130:3306?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
user="root" password="123456">

</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.127.130"
url="jdbc:mysql://192.168.127.130:3307?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
user="root" password="123456">

</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.127.130"
url="jdbc:mysql://192.168.127.130:3308?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
user="root" password="123456">

</writeHost>
</dataHost>
</mycat:schema>
  1. 首先在 schema 中指定逻辑库的名字,逻辑库是指 MyCat 中的库,这个库不存储数据,数据存储在 MySQL 中的物理库中。
  2. 逻辑库中配置逻辑表,配置逻辑表时,需要指定 dataNode 节点, dataNode 就是指数据库存储的位置
  3. 配置 dataNodedataNode 指定 dataHost 和物理库的名字。
  4. dataHost 则配置 MySQL 的主机和从机的位置,登录密码等。主机和从机都可以配置多个。

配置完 schema.xml 后 ,接下来配置 server.xml。server.xml 中主要配置 MyCat 的登录用户名和密码,以及需要操作的逻辑库。

1
2
3
4
5
6
7
8
9
<mycat:server xmlns:mycat="http://io.mycat/">

<user name="root">

<property name="password">123456</property>
<property name="schemas">hellomycat</property>
<property name="usingDecrypt">0</property>
</user>
</mycat:server>

配置完成后,接下来就可以启动 MyCat 了 。执行 MyCat 解压目录下的 bin 目录下的 mycat 命令,可以启动 MyCat

1
$ ./bin/mycat start

如果启动后,提示无法创建 mycat.pid 文件,就自己手动创建一个 mycat.pid 文件。启动成功之后,就可以在本地连接 MyCat 了,连接方式和 MySQL 一样,唯一的区别在于端口号不同。

在连接 MyCat 之前,先在 MySQL 物理库中创建 db1db2 以及 db3 三个数据库。

使用 SQLyog 连接 192.168.127.130:8066,也可以在 cmd 命令行登录 MyCat

1
$ mysql -u root -h 192.168.127.130 -P 8066 -p

登录成功后 ,在 MyCat 的窗口中,执行如下命令,创建表:

1
create table t_user (id integer primary key,username varchar(255))

执行成功后,我们会发现物理库中出现了相应的表。 接下来,手动往各个物理库的物理表中存储一条数据,然后在 MyCat 窗口中可以查询到三个库中的三个表中的数据。

问题分析

整个过程不难,但是在第一次配置的过程中还是容易出错,因此我这里还是来说两句,出错了要如何定位。

一般来说,配置 MyCat 出错,问题可能发生在两个阶段。第一个阶段就是客户端连接 MyCat 出错,第二个阶段就是 MyCat 连接 MySQL 出错。

无论你是使用 SQLyog 还是 Navicat ,我们在连接数据库的过程中,都可以先测试连接,很多人卡在这一步。

如果在测试连接的时候就连接不通,说明是 MyCat 的问题,这个时候检查步骤如下:

  1. 首先当然是查看日志信息,看能不能找出端倪
  2. 通过 jps 命令查看 mycat 是否成功启动
  3. 检查 server.xml 中配置是否正确,用户名密码是否输入正确

这是第一种可能的问题,第二种问题就是测试连接没问题,但是测试完后,却连接不上。反映到 Navicat 上,就是测试连接没问题,测完之后,点击连接名要打开连接时,Navicat 就崩了,出现这个问题一般是 MyCat 在连接 MySQL 出问题了,这个时候就要去检查 schema.xml 文件中关于 MySQL 主机和从机的配置是否正确,数据库地址是否正确,用户名密码是否正确。

基本概念


逻辑库

一般来说,对于应用而言,数据库中间件是透明的,应用并不需要去了解中间件复杂的运作过程,中间件对应用来说就是透明的,我们操作中间件就像操作一个普通的 MySQL 一样,这就是 MyCat 的优势之一。

但是我们毕竟操作的不是 MySQL ,而是 MyCat ,MyCat 中的数据库并不真正存储数据,数据还是存储在 MySQL 中,因此,我们可以将 MyCat 看作是一个或者多个数据库集群构成的逻辑库。

逻辑表

逻辑表又有几种不同的划分:

  • 逻辑表:既然有逻辑库,那么就会有逻辑表。因为数据库分片之后,本来存储在一张表中的数据现在被分散到 N 张表中去了,但是在应用程序眼里,还是只有一张表,它也只操作这一张表,这张表并不真正存储数据,数据存储在 N 张物理表中,这个并不真正存储数据的表称之为逻辑表。

  • 分片表:是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据。

  • 非分片表:一个数据库中并不是所有的表都很大,某些表是可以不用进行切分的,非分片是相对分片表来说的,就是那些不需要进行数据切分的表。

  • ER 表:关系型数据库是基于实体关系模型之上,通过其描述了真实世界中事物与关系,Mycat 中的 ER 表即是来源于此。根据这一思路,提出了基于 E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组保证数据 join 不会跨库操作。

    表分组是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的重要一条规则。

  • 全局表

    一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本上很少变动,字典表具有以下几个特性:

  • 变动不频繁

  • 数据量总体变化不大

  • 数据规模不大,很少有超过数十万条记录

对于这类的表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,所以 MyCat 中通过数据冗余来解决这类表的 join ,即所有的分片都有一份数据的拷贝,所有将字典表或者符合字典表特性的一些表定义为全局表。

数据冗余是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的另外一条重要规则。

分片节点

数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点(dataNode)。

节点主机

数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)。

分片规则

前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则,这样按照某种业务规则把数据分到某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数据处理的难度。

MyCat 提供的分片规则有如下几种:

  • 分片枚举
  • 固定分片 hash 算法
  • 范围约定
  • 取模
  • 按日期(天)分片
  • 取模范围约束
  • 截取数字做 hash 求模范围约束
  • 应用指定
  • 截取数字 hash 解析
  • 一致性 hash
  • 按单月小时拆分
  • 范围求模分片
  • 日期范围 hash 分片
  • 冷热数据分片
  • 自然月分片

数据库切分原理


指通过某种特定的条件,将存放在同一个数据库中的数据分散存放到多个数据库上面,以达到分散单台设备负载的效果。

根据切分规则的类型可以分为以下两种切分模式。

  • 垂直切分:最大特点是规则简单,适合各业务之间的的耦合度非常低、相互影响小、业务逻辑非常清晰的系统。在这种系统中,可以很容易 将不同业务模块所使用的的表切到不同的数据库中

  • 水平切分:相对来说复杂一些,因为要 将同一个表中的不同数据切分到不同的数据库中,后期的数据维护也更为复杂一些。

垂直切分

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

一个架构设计好的系统其总体功能通常是由多个功能模块所组成的,而每一个功能模块的数据对应到数据库中就是一个或多个表。而在架构设计中,各个功能模块相互之间的交互点越少和越统一,系统的耦合度就越低,系统各个模块的维护性以及扩展性也就越好,这样的系统也就越容易实现垂直切分。

但是往往系统中有些表难以做到完全的独立,存在跨库 join 的情况,对于这类分库,可以共用一个数据源,业务之间通过接口来调用。

优点:规则明确、业务清晰、更易于整合和扩展、维护简单。

缺点:部分业务表无法 join,需要通过业务接口方式解决,提高系统复杂度;各业务存在单库性能瓶颈,不易于数据扩展和性能提高;事务处理复杂问题。

由于垂直切分是将表按照业务分类切分到不同的单库中,所有导致某些业务表过于庞大,存在单库读写与存储瓶颈,则需要水平切分来解决。

水平切分

水平切分不是将表按照业务分类,而是按照某个字段的某种规则分散到多个库中,每个表中包含一部分数据,如图。

拆分数据需要定义分片规则,拆分的第一原则是找到 拆分维度。比如:从会员的角度来分析,需要查询会员某天某月某个订单,那么就需要按照日期来拆分,不同的数据按照会员 ID 做分组。

优点:拆分规则抽象好;不存在单库数据瓶颈问题;提高系统稳定性和负载能力。

缺点:事务一致性难以解决;数据扩展和维护的难度极大;跨库 join 性能差。

数据库分片规则


这里向大家简单介绍 5 种规则。

global

有一些表,数据量不大,也不怎么修改,主要是查询操作,例如系统配置表,这一类表我们可以使用 global 这种分片规则。global 的特点是,该表会在所有的库中都创建,而且每一个库中都保存了该表的完整数据。具体配置方式,就是在 schema.xml 的 table 节点中添加一个 type 属性,值为 global。配置完成后,重启 mycat

1
$ ./bin/mycat restart

重启完成后,要删除之前已经创建的 t_user 表,然后重新创建表,创建完成后,向表中插入数据,可以看到,db1、db2 以及 db3 中都有数据了。

总结:global 适合于 数据量不大、以查询为主、增删改较少的表。

sharding-by-intfile

sharding-by-intfile 这个是枚举分片,就是在数据表中专门设计一个字段,以后根据这个字段的值来决定数据插入到哪个 dataNode 上。

注意:在配置 sharding-by-intfile 规则时,一定要删除 schema 节点中的 type=”global” ,否则配置不会生效。

配置完成后,还需要指定枚举的数据。枚举的数据可以在 rule.xml 中查看。

img

在 rule.xml 文件中,首先找到 tableRule 的名字为 sharding-by-intfile 的节点,这个节点中定义了两个属性,一个是 columns 表示一会在数据表中定义的枚举列的名字(数据表中一会需要创建一个名为 sharding_id 的列,这个列的值决定了该条数据保存在哪个数据库实例中),这个名字可以自定义;另外一个属性叫做 algorithm ,这是指 sharding-by-intfile 所对应的算法名称。根据这个名称,可以找到具体的算法:

img

还是在 rule.xml 文件中,我们找到了 hash-int ,class 表示这个算法对应的 Java 类的路径。第一个属性 mapFile 表示相关的配置文件,从这个文件名可以看出,这个文件 就在 conf 目录下。

打开 conf 目录下的 partition-hash-int.txt 文件,内容如下:

1
2
3
0=0
1=1
2=2

前面的数字表示枚举的值 ,后面的数字表示 dataNode 的下标,所以前面的数字可以自定义,后面的数字不能随意定义。

配置完成后,重启 MyCat ,然后进行测试:

1
2
3
4
5
6
drop table if exists t_user;
create table t_user (id integer primary key,username varchar(255),sharding_id integer);
insert into t_user(id,username,sharding_id) values(1,'www.antoniopeng.com',0);
insert into t_user(id,username,sharding_id) values(1,'www.antoniopeng.com',1);
insert into t_user(id,username,sharding_id) values(1,'www.antoniopeng.com',2);
select * from t_user;

执行完后,sharding_id 对应值分别为 0 、1 、2 的记录分别插入到 db1 、db2 以及 db3 中。

auto-sharding-long

auto-sharding-long 表示按照既定的范围去存储数据。就是提前规划好某个字段的值在某个范围时,相应的记录存到某个 dataNode 中。

配置方式,首先修改 schema 节点中的 rule 属性值为 auto-sharding-long,然后去 rule.xml 中查看对应的算法了规则相关的配置:

img

可以看到,默认是按照 id 的范围来划分数据的存储位置的,对应的算法就是 rang-long 。

继续查看,可以找到算法对应的类,以及相关的配置文件,这个配置文件也在 conf 目录下,打开该文件:

img

如上配置,表示 当 id 的取值在 0-5之间时,将数据存储到 db1 中,当 id 在 5-10 之间时,存储到 db2 中,当 id 的取值在 10-1500W 之间时,存储到 db3 中。

配置完成后,重启 MyCat ,测试即可。

mod-long

取模:根据表中的某一个字段,做取模操作。根据取模的结果将记录存放在不同的 dataNode 上。这种方式不需要再添加额外字段。 schema 节点中的 rule 属性值为 mod-long,然后去 rule.xml 中配置一下 dataNode 的个数。

img

可以看到,取模的字段是 id ,取模的算法名称是 mod-long ,再看具体的算法:

img

在具体的算法中,配置了 dataNode 的个数为 3。然后保存退出,重启 MyCat,进行测试即可。

sharding-by-murmur

前面介绍的几种方式,都存在一个问题,如果数据库要扩容,之前配置会失效,可能会出现数据库查询紊乱。因此我们要引入一致性 hash 这样一种分片规则,可以解决这个问题。具体配置和前面一样,schema 节点中的 rule 属性值为 sharding-by-murmur。

另外需要注意,在 rule.xml 中修改默认 dataNode 的数量:

img

修改完后,重启 MyCat ,进行测试。

更多干货请移步:https://antoniopeng.com


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !