------理论部分摘自Mycat 权威指南,仅做入门参考-----
一、Mycat应用场景
Mycat 发展到现在,适用的场景已经很丰富,而且不断有新用户给出新的创新性的方案,以下是几个典型的 应用场景:
• 1、单纯的读写分离,此时配置最为简单,支持读写分离,主从切换;
• 2、分表分库,对于超过 1000 万的表进行分片,最大支持 1000 亿的单表分片;
• 3、多租户应用,每个应用一个库,但应用程序只连接 Mycat,从而不改造程序本身,实现多租户化;
• 4、报表系统,借助于 Mycat 的分表能力,处理大规模报表的统计;
• 5、替代 Hbase,分析大数据;
• 6、作为海量数据实时查询的一种简单有效方案,比如 100 亿条频繁查询的记录需要在 3 秒内查询出来结果,除了基于主键的查询,还可能存在范围查询或其他属性查询,此时 Mycat 可能是最简单有效的选择。
二、何为数据切分(分库分表)?
简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机) 上面,以达到分散单台设备负载的效果。
数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式。
一种是按照不同的表(或者 Schema)来切分到不同的数据库(主机)之上,这种切可以称之为数据的垂直(纵向)切分;
另外一种则是根据 表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数 据的水平(横向)切分。
垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低,相互影响很小, 业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到不同的数据库中。 根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰。
水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆分到不同的数据库中, 对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂,后期的数据维护也会更为复杂一些。
数据切分的原则:
第一原则:能不切分尽量不要切分。
第二原则:如果要切分一定要选择合适的切分规则,提前规划好。 第三原则:数据切分尽量通过数据冗余或表分组(Table Group)来降低跨库 Join 的可能。 第四原则:由于数据库中间件对数据 Join 实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量
少使用多表 Join。
三、Mycat 是什么?
Mycat 是数据库中间件,就是介于数据库与应用之间,进行数据处理与交互的中间服务。 由于前面讲的对数据进行分片处理之后,从原有的一个库,被切分为多个分片数据库,所有的分片数据库集群构 成了整个完整的数据库存储。
所以有了数据库中间件,应用只需要集中与业务处理,大量的通用的数据聚合,事务,数据源切换都由中间 件来处理,中间件的性能与处理能力将直接决定应用的读写性能,所以一款好的数据库中间件至关重要。、环境
四、环境准备
- JDK下载
http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html 注:必须 JDK7 或更高版本. - MySQL下载
https://dev.mysql.com/downloads/mysql/
注:MyCAT 支持多种数据库接入,如:MySQL、SQLServer、Oracle、MongoDB 等,推荐使用MySQL 做集群。 - MyCAT 项目主页
https://github.com/MyCATApache/
注:MyCAT 相关源码、文档都可以在此地址下进行下载。
linux 下可以下载 Mycat-server-xxxxx.linux.tar.gz 解压在某个目录下,注意目录不能有空格,在Linux(Unix)下,建议放在 usr/local/Mycat 目录下,如下:
MyCAT 目前主要通过配置文件的方式来定义逻辑库和相关配置:
• MYCAT_HOME/conf/schema.xml 中定义逻辑库,表、分片节点等内容;
• MYCAT_HOME/conf/rule.xml 中定义分片规则;
• MYCAT_HOME/conf/server.xml 中定义用户以及系统相关变量,如端口等。
建议大家选择 1.6.5 版本,毕竟是比较稳定的版本,且 1.6.5 版本 才开始支持单库分表。
1. 修改 server.xml
shift+G 翻到文档末尾,找到以下一段,修改
#特别说明:
#user name及password不用于连接数据库,而是连接mycat 8066管理端口,无需在数据库中创建此用户,可以随意指定
#property name=“schemas” 的value值是自定义的逻辑库名称,不一定是真实的数据库schema name,但是需要与schema.xml中的schema name一致
<!-- mycat的8066 端口连接的用户名,密码,逻辑数据库名 -->
<user name="mycatu1" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">mydb</property>
<privileges check="true">
<schema name="mydb" dml="0110" >
<table name="sbtest11" dml="1111"></table>
</schema>
</privileges>
</user>
2.修改schema.xml
schema 标签用于定义 MyCat 实例中的逻辑库,MyCat 可以有多个逻辑库,每个逻辑库都有自己的相关配 置。可以使用 schema 标签来划分这些不同的逻辑库。
如果不配置 schema 标签,所有的表配置,会属于同一个默认的逻辑库。
$ cat ../conf/schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- "mydb"逻辑库名-->
<schema name="mydb" checkSQLschema="false" sqlMaxLimit="100">
<!-- name: 表名,分表主键,子表,数据节点,分表规则 -->
<table name="sbtest11" primaryKey="id" autoIncrement="true" subTables="sbtest11_$1-3" dataNode="dn1" rule="mod-long" />
</schema>
<!-- database="test”是实际的数据库名称 -->
<dataNode name="dn1" dataHost="localhost1" database="test"/>
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="3"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="192.168.0.156:6001" user="root" password="123456">
<!-- can have multi read hosts -->
<readHost host="hostS1" url="192.168.0.156:6001" user="root" password="123456" />
</writeHost>
</dataHost>
</mycat:schema>
schema name 要和server.xml对应上,
user表设置分表3张 (sbtest11_1, sbtest11_2, sbtest11_3),只设置一个 datanote(即一个物理数据库服务),
rule采用取模模式mod-long。
writeHost 和 readHost设为同一数据库。
balance 属性
负载均衡类型,目前的取值有 3 种:
> balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上。
> balance="1",全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,M2,S1,S2 都参与 select 语句的负载 均衡。
> balance="2",所有读操作都随机的在 writeHost、readhost 上分发。
> balance="3",所有读请求随机的分发到 wiriterHost 对应的 readhost 执行,writerHost 不负担读压力,
注意 balance=3 只在 1.4 及其以后版本有,1.3 没有。
writeType 属性
负载均衡类型,目前的取值有 3 种:
> writeType="0", 所有写操作发送到配置的第一个 writeHost,第一个挂了切到还生存的第二个 writeHost, 重新启动后已切换后的为准,切换记录在配置文件中:dnindex.properties .
> writeType="1",所有写操作都随机的发送到配置的 writeHost,1.5 以后废弃不推荐。
switchType 属 性
> -1 表示不自动切换。
> 1 默认值,自动切换。
> 2 基于 MySQL 主从同步的状态决定是否切换。
3.修改 rule.xml
#cp rule.xml{,.bak}
#cat /dev/null>rule.xml
vi rule.xml
#添加如下内容
#Mycat分片(分库分表)规则, rule.xml文件中默认有全部的分库分表规则,保留需要的即可
$ cat ../conf/rule.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:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
<tableRule name="mod-long">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">3</property>
</function>
</mycat:rule>
此次选用默认的mod-long规则,只需修改count数.
修改property count 等于3(默认值即为3,如果你需要分出更多的表,在此进行修改为你的分表数)。
这样,配置就已经完成了。
四、物理数据库构建
先在MySQL库上,建数据库test,并在此数据库中建4张表,sbtest11、sbtest11_$1-3,且四张表结构相同。
CREATE TABLE `sbtest11` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`),
KEY `index_c` (`c`)
create table sbtest11_1 like sbtest11;
create table sbtest11_2 like sbtest11;
create table sbtest11_3 like sbtest11;
五、启动Mycat
MyCAT 在 Linux 中部署启动时,首先需要在 Linux 系统的环境变量中配置 MYCAT_HOME,操作方式如下:
- vi /etc/profile,在系统环境变量文件中增加
$ cd /usr/local
$ ln -s /opt/module/mycat Mycat
echo 'export MYCAT_HOME=/usr/local/Mycat'>>/etc/profile
- 执行 source /etc/profile 命令,使环境变量生效。
source /etc/profile
如果是在多台 Linux 系统中组建的 MyCAT 集群,那需要在 MyCAT Server 所在的服务器上配置对其他 ip 和主机名的映射,配置方式如下: vi /etc/hosts
例如:我有 4 台机器,配置如下:
IP 主机名:
192.168.100.2 sam_server_1
192.168.100.3 sam_server_2
192.168.100.4 sam_server_3
192.168.100.5 sam_server_4
编辑完后,保存文件。
进入mycat/bin 目录
启动|停止|重启|查看状态:
./mycat start | stop | restart | status
查看logs/下的wrapper.log和mycat.log可以查看运行时问题和异常。
mycat启动日志:
cat ./logs/wrapper.log
mycat 应用日志:
cat ./logs/mycat.log
六、测试
连接mycat, 端口:8066,账号:mycatu1, 密码:123456
使用你在 server.xml文件中配置的用户名和密码
执行sql语句,查看执行过程。
连接进8066,展示的数据库和数据库表都是schema中配置的逻辑库和表
$ mysql -h 192.168.0.156 -P8066 -umycatu1 -p123456
>show databases;
+----------+
| DATABASE |
+----------+
| mydb |
+----------+
1 row in set (0.01 sec)
>show tables;
+----------------+
| Tables in mydb |
+----------------+
| sbtest11 |
+----------------+
1 row in set (0.00 sec)
#8066测试插入数据
(mycatu1@192.168.0.156:mydb09:06:44)>insert into sbtest11(id,k,c,pad) values(1,1,'2','3');
(mycatu1@192.168.0.156:mydb09:08:40)>insert into sbtest11(id,k,c,pad) values(2,1,'2','92467260075-29317133430-40030751771-62719549546-51313121034');
(mycatu1@192.168.0.156:mydb09:08:55)>insert into sbtest11(id,k,c,pad) values(3,1,'2','92467260075-29317133430-40030751771-62719549546-51313121034');
(mycatu1@192.168.0.156:mydb09:30:20)>select * from sbtest11;
+----+---+---+-------------------------------------------------------------+
| id | k | c | pad |
+----+---+---+-------------------------------------------------------------+
| 3 | 1 | 2 | 92467260075-29317133430-40030751771-62719549546-51313121034 |
| 1 | 1 | 2 | 3 |
| 2 | 1 | 2 | 92467260075-29317133430-40030751771-62719549546-51313121034 |
+----+---+---+-------------------------------------------------------------+
3 rows in set (0.18 sec)
#回到test库查看效果
(root@localhost:test09:09:28)>select * from sbtest11;
Empty set (0.00 sec)
(root@localhost:test09:09:46)>select * from sbtest11_1;
+----+---+---+-------------------------------------------------------------+
| id | k | c | pad |
+----+---+---+-------------------------------------------------------------+
| 3 | 1 | 2 | 92467260075-29317133430-40030751771-62719549546-51313121034 |
+----+---+---+-------------------------------------------------------------+
1 row in set (0.00 sec)
(root@localhost:test09:09:51)>select * from sbtest11_2;
+----+---+---+-----+
| id | k | c | pad |
+----+---+---+-----+
| 1 | 1 | 2 | 3 |
+----+---+---+-----+
1 row in set (0.00 sec)
(root@localhost:test09:09:54)>select * from sbtest11_3;
+----+---+---+-------------------------------------------------------------+
| id | k | c | pad |
+----+---+---+-------------------------------------------------------------+
| 2 | 1 | 2 | 92467260075-29317133430-40030751771-62719549546-51313121034 |
+----+---+---+-------------------------------------------------------------+
1 row in set (0.00 sec)
七、debug模式下解析SQL的执行
修改日志记录级别为debug: vi $MYCAT_HOME/conf/log4j2.xml
将默认的info级别调整成debug级别
<asyncRoot level="debug" includeLocation="true">
#连接到8066端口,执行
select * from sbtest11
#$MYCAT_HOME/logs/mycat.log输出
#命令行接收到指令
2019-11-11 21:52:06.402 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.net.FrontendConnection.query(FrontendConnection.java:340)) - ServerConnection [id=1, schema=mydb, host=192.168.0.156, user=mycatu1,txIsolation=3, autocommit=true, schema=mydb, executeSql=show tables] select * from sbtest11
2019-11-11 21:52:06.457 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.server.ServerQueryHandler.query(ServerQueryHandler.java:57)) - ServerConnection [id=1, schema=mydb, host=192.168.0.156, user=mycatu1,txIsolation=3, autocommit=true, schema=mydb, executeSql=select * from sbtest11]select * from sbtest11
2019-11-11 21:52:06.462 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.cache.impl.EnchachePool.get(EnchachePool.java:77)) - SQLRouteCache miss cache ,key:mydbselect * from sbtest11
####由于只配置了一个dn1,而且数据只有在dn1上才有,可以看出sql只被路由到dn1执行
2019-11-11 21:52:06.530 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.cache.impl.EnchachePool.putIfAbsent(EnchachePool.java:60)) - SQLRouteCache add cache ,key:mydbselect * from sbtest11 value:select * from sbtest11, route={
1 -> dn1{SELECT *
FROM sbtest11_1
LIMIT 100}
2 -> dn1{SELECT *
FROM sbtest11_2
LIMIT 100}
3 -> dn1{SELECT *
FROM sbtest11_3
LIMIT 100}
}
2019-11-11 21:52:06.530 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.server.NonBlockingSession.execute(NonBlockingSession.java:126)) - ServerConnection [id=1, schema=mydb, host=192.168.0.156, user=mycatu1,txIsolation=3, autocommit=true, schema=mydb, executeSql=select * from sbtest11]select * from sbtest11, route={
1 -> dn1{SELECT *
FROM sbtest11_1
LIMIT 100}
2 -> dn1{SELECT *
FROM sbtest11_2
LIMIT 100}
3 -> dn1{SELECT *
FROM sbtest11_3
LIMIT 100}
} rrs
2019-11-11 21:52:06.539 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.backend.mysql.nio.handler.MultiNodeQueryHandler.<init>(MultiNodeQueryHandler.java:128)) - execute mutinode query select * from sbtest11
2019-11-11 21:52:06.541 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.backend.mysql.nio.handler.MultiNodeQueryHandler.<init>(MultiNodeQueryHandler.java:164)) - has data merge logic
MySQLConnection [id=14, lastTime=1573480326535, user=root, schema=test, old shema=test, borrowed=true, fromSlaveDB=true, threadId=4292, charset=utf8, txIsolation=3, autocommit=true, attachment=dn1{SELECT *
FROM sbtest11_1
LIMIT 100}, respHandler=io.mycat.backend.mysql.nio.handler.MultiNodeQueryHandler@ec29b0b, host=192.168.0.156, port=6001, statusSync=io.mycat.backend.mysql.nio.MySQLConnection$StatusSync@2c5da9ac, writeQueue=0, modifiedSQLExecuted=false] false 3
... ...
####到这里,已经按照路由规则转给后边真正的数据库执行sql了
2019-11-11 21:52:06.559 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.server.NonBlockingSession.releaseConnection(NonBlockingSession.java:386)) - release connection MySQLConnection [id=14, lastTime=1573480326535, user=root, schema=test, old shema=test, borrowed=true, fromSlaveDB=true, threadId=4292, charset=utf8, txIsolation=3, autocommit=true, attachment=dn1{SELECT *
FROM sbtest11_1
LIMIT 100}, respHandler=io.mycat.backend.mysql.nio.handler.MultiNodeQueryHandler@ec29b0b, host=192.168.0.156, port=6001, statusSync=io.mycat.backend.mysql.nio.MySQLConnection$StatusSync@2c5da9ac, writeQueue=0, modifiedSQLExecuted=false]
2019-11-11 21:52:06.559 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.backend.datasource.PhysicalDatasource.releaseChannel(PhysicalDatasource.java:633)) - release channel MySQLConnection [id=14, lastTime=1573480326535, user=root, schema=test, old shema=test, borrowed=true, fromSlaveDB=true, threadId=4292, charset=utf8, txIsolation=3, autocommit=true, attachment=null, respHandler=null, host=192.168.0.156, port=6001, statusSync=null, writeQueue=0, modifiedSQLExecuted=false]
2019-11-11 21:52:06.571 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.backend.mysql.nio.handler.MultiNodeQueryHandler.okResponse(MultiNodeQueryHandler.java:285)) - received ok response ,executeResponse:false from MySQLConnection [id=13, lastTime=1573480326551, user=root, schema=test, old shema=test, borrowed=true, fromSlaveDB=true, threadId=4291, charset=utf8, txIsolation=3, autocommit=true, attachment=dn1{SELECT *
FROM sbtest11_3
LIMIT 100}, respHandler=io.mycat.backend.mysql.nio.handler.MultiNodeQueryHandler@ec29b0b, host=192.168.0.156, port=6001, statusSync=io.mycat.backend.mysql.nio.MySQLConnection$StatusSync@45b85ea1, writeQueue=0, modifiedSQLExecuted=false]