前面的文章中,我们已经完成了mycat的读写分离的实验,请参考如下文章链接:
[MyCat入门篇-使用案例1:读写分离(part1)]
[MyCat入门篇-使用案例1:读写分离(part2)]
[MyCat入门篇-使用案例1:读写分离(part3)]
[MyCat入门篇-使用案例1:读写分离(part4)]
接下来我们进行mycat和MySQL集成的垂直拆分的实验部分。
配置垂直拆分(分库)
按业务模块垂直拆分
所谓的垂直拆分,就是指把不同业务模块的表,分部放在不同的MySQL数据库服务器上。
例如采用如下的拆分方式:
- 订单模块的表放在一个MySQL服务器上。
- 物流模块的表放在另外一个MySQL服务器上。
- 用户模块的表再放在另外一个MySQL服务器上。
这样来实现不同业务模块的表分别放在不同的MySQL实例上,来分摊整个数据库的压力。
整体的这个概念就是把不同的模块的表分开存放在不同的MySQL数据库实例中,我们有时候也称之为分库的操作。
按数据库垂直拆分
在一些多租户的场景想,有时候我们也可以基于数据库级别的垂直拆分。
多租户是每一个租户MySQL数据库实例中创建一个单独的数据库(也就是
schema
),用数据库来隔离各个租户之间的表。在各个
schema
下,这些表虽然在数目和表结构上都是相同的,但是他们分别隶属于不同的租户。
比如我之前遇到这样一个项目,他们的业务场景是这样的:他们有很多家门店,全国各地都有甚至国外也有他们的门店。项目一开始是在一两家门店上线部署。后来计划把这个项目推广到所有门店。
这个时候,为了避免所有的门店数据都在同一台MySQL数据库实例上进行读写的操作,我们就采用了多租户的方式来进行推广部署。
-
编号为1-50的门店,它们的数据库
schema
放在M1节点上 -
编号为51-100的门店,它们的数据库
schema
放在M2节点上 -
编号为101-150的门店,它们的数据库
schema
放在M3节点上
以次类推,采用这样的方式来减轻对MySQL数据库服务器的读写压力。然后前面部署2台mycat服务器来做数据库的路由层。
这也是一种垂直拆分的方式,只不过不是在一个数据库(
schema
)下面按照业务模块进行拆分表,而是根据整个数据库级别去垂直拆分。
注意事项
垂直拆分的概念我们理解之后,下面我结合我们搭建的MySQL主从同步的集群环境,来准备我们的垂直拆分的实验案例。
注意:
垂直拆分和前面的配置的双主双从的读写分离网络拓扑图不能存在。也就是说我们不能直接基于上面的读写分离的拓扑直接来配置垂直拆分。
原因有以下几点:
- 读写分离是一个主库实例提供写的服务,一个从库实例提供读的服务,所以要求两个数据库实例中的所有的表数目和结构都是一样的。
- 垂直拆分是根据业务模块的划分,把某一个业务模块的表和其他业务模块区分开,单独存放在某一个数据库实例中,此时这个数据库实例中的表的数目和结构和其他数据库实例中的表的数目和实例都可能不一样。
-
如果在双主双从,主主复制的拓扑图中,所有的数据库和表在每一个数据库实例中都是相同的,就是说
M1,M2,S1,S2
上所有的表结构和数量还有数据库的数目都是相同的。这个和垂直拆分的目的是想违背的。
综上所述,我们不能直接基于
M1->S1,M2->S2,M1<->M2
,这样的网络拓扑来直接配置垂直拆分。
当然我们有两种方式来开展我们的垂直拆分的实验。
-
把
M1<->M2
直接的互相复制的链路切断。只保留
M1->S1,M2->S2
的链路。这样我们可以把
M1-S1
这一组读写分离当做用户库来使用,这两个实例中,只存储用户的相关表。另外一组
M2->S2
的读写分离当做订单库来使用,这两个实例中,只存储订单相关的表。这样就使用实现垂直拆分两个业务模块的表分别存储在不同的数据库实例中。
具体网络拓扑如下所示:
-
保持原有的
M1->S1,M2->S2,M1<->M2
网络拓扑架构,只不过是在
M1,M2
的
my.cnf
配置文件中,指定需要做同步的数据库schema名称,而不是把
M1,M2
中所有的数据库schema全部做主从同步。然后在
M1、M2
中都创建一个名称不是
mysql_db
的数据库schema来进行垂直拆分的实验。
在
M1、M2
的
my.cnf
中增加如下配置,增加完成之后需要重启
M1
和
M2
上面的MySQL服务。#设置需要复制的数据库 binlog-do-db=mysql_db
在完成了
M1
和
M2
上面的
my.cnf
配置文件的修改之后,重启了
M1
和
M2
两个MySQL服务之后,
M1
和
M2
之间互相复制的时候,只会复制
mysql_db
这个一个数据库下面的数据内容,其他创建的数据库将不会在
M1
和
M2
之间互相复制。
此时,我们就是可以使用新建的数据库来进行垂直拆分的实验了。
我们接下来采用第1种方式来做垂直拆分的实验。在此之前,我们需要做如下2步的操作。
-
先登录到
M1
或
M2
节点上,然后执行如下的SQL语句,把前面我们进行读写分离测试的时候使用的数据库删掉。然后再重新创建名称为
mysql_db
的数据库。
drop database mysql_db;
create database mysql_db default charset utf8mb4;
-
停止
M1<->M2
之间互相复制的链路,在
M1
和
M2
上分别执行如下SQL语句,来停止
M1
和
M2
的复制链路。
stop slave;
reset slave all;
配置server.xml文件
注意:mycat的
server.xml
配置文件我们不需要修改了,因为我们做读写分离实验的时候,已经配置过。当时的配置已经满足现在做垂直拆分的需求,所以这里不再修改
server.xml
配置文件。
配置schema.xml文件
-
对
schema.xml
配置文件的修改如下:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="mycat_db" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
<!--如果一个表名称没有在这里显示的声明它所属的dataNode,那么它将会使用上面配置的dn1。
下面的'order_info'、'order_info_detail'表是垂直拆分的配置,表会创建在'ordernode'节点上。 -->
<table name="order_info" dataNode="ordernode" />
<table name="order_info_detail" dataNode="ordernode" />
</schema>
<dataNode name="dn1" dataHost="userhost" database="mysql_db" />
<dataNode name="ordernode" dataHost="orderhost" database="mysql_db" />
<dataHost name="userhost" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql"
dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="172.19.0.11:3306" user="root" password="root">
<readHost host="hostS1" url="172.19.0.12:3306" user="root" password="root" />
</writeHost>
</dataHost>
<dataHost name="orderhost" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql"
dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM2" url="172.19.0.21:3306" user="root" password="root">
<readHost host="hostS2" url="172.19.0.22:3306" user="root" password="root" />
</writeHost>
</dataHost>
</mycat:schema>
-
schema.xml的截图如下:
经过如上的配置之后,只要在配置文件中声明的表,在创建的时候时候,读写的时候,才会去对应的
ordernode
节点,也就我们配置的
M2、S2
节点上去操作,而没做任何声明的表,其他的表
都会去
dn1
节点也就是
M1、S1
上去操作。
重启MyCat容器
-
修改完成mycat的配置文件之后,重启mycat容器。
重启mycat容器命令如下:
docker restart mycat-mycat1
验证垂直拆分结果
验证的大致步骤如下:
-
通过mycat命令行登录到mycat连接服务。
-
然后通过mycat的命令行,去创建
order_info
和
order_info_history
两张表,然后再任意创建表
user_info
和
test_tab
。 -
再新开窗口分别登录到
M1、S2
和
M2、S2
4个节点上去查看数据库
mysql_db
下面的表有分别有哪些。最终结果应该是:-
order_info
和
order_info_history
只在M2和S2上存在,之所以在S2上也有,是因为有一个主从复制的链路
M2->S2
。 -
user_info
和
test_tab
只在M1和S2上存在,之所以在S1上也有,是因为有一个主从复制的链路
M1->S1
。 - 再新建其他的表,也只会在M1和S1上存在。
-
-
然后通过mycat的命令行,分别向
order_info
和
order_info_history
,还有
user_info
和
test_tab
插入几行数据,验证是否可以正常插入。 -
在mycat的命令行中,才分别查询
order_info
和
order_info_history
,还有
user_info
和
test_tab
表中的数据是否正常显示。
- 验证期间涉及到的DDL SQL语句如下:
如下的SQL语句都是在mycat的命令行终端运行
-- 创建user相关的表
create table user_info(id int primary key auto_increment, name varchar(16), hostname varchar(16));
create table test_tab(id int primary key auto_increment, name varchar(16), hostname varchar(16));
-- 创建order相关的表
create table order_info(id int primary key auto_increment, customer_code int, status_code varchar(16), hostname varchar(16));
create table order_info_detail(id int primary key auto_increment, order_id int, detail_desc varchar(16), hostname varchar(16));
- 验证期间涉及到的DML SQL语句如下:
如下的SQL语句都是在mycat的命令行终端运行
-- user相关的表插入测试数据
insert into user_info(name,hostname) values('A',@@hostname);
insert into user_info(name,hostname) values('B',@@hostname);
insert into user_info(name,hostname) values('C',@@hostname);
insert into test_tab(name,hostname) values('x',@@hostname);
insert into test_tab(name,hostname) values('y',@@hostname);
insert into test_tab(name,hostname) values('z',@@hostname);
-- order相关的表插入测试数据
insert into ORDER_INFO(customer_code,status_code,hostname) values(111,'AA',@@hostname);
insert into ORDER_INFO(customer_code,status_code,hostname) values(222,'BB',@@hostname);
insert into ORDER_INFO(customer_code,status_code,hostname) values(333,'CC',@@hostname);
insert into ORDER_INFO_DETAIL(order_id,detail_desc,hostname) values(2,'AA_DESC',@@hostname);
insert into ORDER_INFO_DETAIL(order_id,detail_desc,hostname) values(4,'BB_DESC',@@hostname);
insert into ORDER_INFO_DETAIL(order_id,detail_desc,hostname) values(6,'CC_DESC',@@hostname);
- 创建表之前的检查结果如下:
- 创建表之后的检查结果如下:
- 插入数据之后的通过mycat命令行检查结果如下:
- 插入数据之后的在MySQL各个节点命令行检查结果如下:
- 验证垂直拆分后的结果
从上图可以看出,垂直拆分和读写分离都是正常的。
order
类型的表都在
M2
上,
user
类型的表都在
M1
上。
遇到的问题:
-
所有的SQL都是小写字母执行的,但是在通过mycat创建表之后,凡是在
schema.xml
配置文件中显示声明垂直拆分到指定节点的表,最后在指定的节点创建的表名称都是大写字母。
然后通过mycat查询的时候,习惯性的去使用小写字母指定表名称去查询,就会出现表不存在的错误提示,这个我能理解:是因为在mycat后面的真正的MySQL实例中表名称是大写,但是当通过mycat执行删除表的语句的时候,也同样使用了小写字母的表名称,却又不提示表不存在了的问题了,表被成的删除了。如下所示:mysql> select * from order_info_detail; ERROR 1146 (HY000): Table 'mysql_db.order_info_detail' doesn't exist mysql> drop table order_info_detail; Query OK, 0 rows affected (0.02 sec) mysql> --如下的SQL可以说明当前的窗口是mycat的命令行窗口执行的上面的语句。 mysql> select @@hostname; +--------------+ | @@hostname | +--------------+ | slave2.mysql | | slave1.mysql | +--------------+ 2 rows in set (0.01 sec) mysql>
这应该是mycat一个待解决的bug吧。创建表的时候,把小写的表名称进行的to_upper的操作,但是在删除表的时候,又没有进行同样的转换。我查了一下资料,网上有些方式是通过修改MySQL的配置文件,让其忽略大小写敏感这个功能。但是我个人感觉这个不是很好的方式,可以说这算是一个妥协的方式。因为 在某些业务场景下我们是不能把MySQL的这个功能关闭的。所以,还是找时间看一下mycat的源码,把这个修复一下,重新编译安装一次。
-
针对上面的表名称大小写的问题,其实我们可以从另外一个角度去分析一下:当我们第一次部署一个数据库的时候,建表的DDL语句应该在哪里去执行?是通过
mycat
去做执行?还直接到对应的数据库节点上去执行?
对应到我们的这个例子就是:是在
mycat
上创建我们的
ORDER_INFO
和
ORDER_INFO_DETAIL
还是去
M2
上面去创建?因为我们已经明确知道每一个表被垂直拆分到哪一个节点上,所以我们直接去
M2
创建更好。这样小写的表名称将会被创建在
M2
上,然后我验证了一下,通过mycat,使用小写的表名称进行读写操作就是没有问题的。
而我们这里之所以通过
mycat
去创建表,很大的一个目的是为了验证
mycat
是否可以真正做到表的垂直拆分 。
到这里,mycat和MySQL集成配置垂直拆分的使用案例就完成了。接下来我会继续分享mycat和MySQL水平拆分的使用案例,
敬请期待…
微信搜索“coder-home”或扫一扫下面的二维码,
关注公众号,第一时间了解更多干货分享,还有各类视频教程资源。扫描它,带走我