史上最全mycat【一文看懂】
mycat有三种常见的应用场景,每种场景具体的细节又不相同,如下所示:
①读写分离
读写分离实在主从复制的基础上运行的,在配置文件配置相应的节点实现,通过mycat主库负责读,从库负责写,而主库与从库之间会进行主从复制(主库接收到写的请求,首先会记录在binlarylog日志中,由从机去读取发生io并记录在relaylog里面,最终从机再将数据读取到自己的数据库里面,可以会产生一定的时间差)
②数据分片
垂直分库:通过mycat将数据库里面的表根据业务的需求存放在不同的主机里面,比如我有一个支付系统,需要将客户的信息与订单信息以及详情信息分离开来,就需要在两台主机上配置同样的数据库,当java请求过来的时候,寻找自己对应的数据库.
水平分表:指的是mycat通过数据库的字段定制分离规则,将数据插入到不同数据库的相同数据表里面,java请求过来,有mycat帮助我们分离.
③作为整合数据源使用,使得不同的数据库寻找自己对应类型的数据库.
原理:mycat原理最重要的就是一个拦截,他拦截了我们java程序发过来的sql请求,并对sql进行分片分析,缓存分析以及读写分离分离,然后将请求发往后端的真实数据库,并将结果做适当的处理进行返回.让程序员不知道自己使用的是mysql还是mycat.
是什么
Mycat是数据库中间件。
MyCat由来
Cobar属于阿里B2B事业群,始于2008年,在阿里服役3年多,接管3000+个MySQL数据库的schema,集群日处理在线SQL请求50亿次以上。由于Cobar发起人的离职,Cobar停止维护。
Mycat是开源社区在阿里cobar基础上进行二次开发,解决了cobar存在的问题,并且加入了许多新的功能在其中。
Mycat的官网
作用
读写分离
数据分片
垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)
多数据源整合
原理
Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL 语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
这种方式把数据库的分布式从代码中解耦出来,程序员察觉不出来后台使用Mycat还是MySQL。
安装启动
安装
解压后即可使用
![]()
解压缩文件拷贝到linux下 /usr/local/
三个配置文件
①schema.xml:定义逻辑库,表、分片节点等内容
②rule.xml:定义分片规则
③server.xml:定义用户以及系统相关变量,如端口等
配置
修改配置文件server.xml
修改配置文件schema.xml
删除<schema>标签间的表信息,
<dataNode>标签只留一个,
<dataHost>标签只留一个,
<writeHost><readHost>只留一对
| <?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> </schema> <dataNode name="dn1" dataHost="host1" database="mytestdb" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="0" 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.200.166:3306" user="root" password="root"> <!-- can have multi read hosts --> <readHost host="hostS1" url="192.168.200.168:3306" user="root" password="root" /> </writeHost> </dataHost> </mycat:schema> |
|---|
验证数据库访问情况
启动mycat:
./mycat console
jvm 1 | MyCAT Server startup successfully. see logs in logs/mycat.log 表示启动成功!
Mycat作为数据库中间件要和数据库部署在不同机器上,所以要验证远程访问情况。
启动程序
①控制台启动 :去mycat/bin 目录下执行 ./mycat console
②后台启动 :去mycat/bin 目录下 ./mycat start
为了能第一时间看到启动日志,方便定位问题,我们选择①控制台启动。
登录
登录后台管理窗口
此登录方式用于管理维护Mycat
| mysql -umycat -p123456 -P 9066 -h 192.168.200.168 #常用命令如下: show database Show @@help 查看帮助命令 |
|---|
登录数据窗口
此登录方式用于通过Mycat查询数据,我们选择这种方式访问Mycat
| mysql -umycat -p123456 -P 8066 -h 192.168.200.168 show databases; use TESTDB; show tables; select * from mytbl;#查询的哪个库?{应该是主机} 修改从机的数据,再次查询! |
|---|
搭建读写分离
一主一从读写分离模式。
3.1 搭建MySQL数据库主从复制
MySQL主从复制原理
主机配置
| 修改配置文件:vim /etc/my.cnf #主服务器唯一ID server-id=1 #启用二进制日志 log-bin=mysql-bin # 设置不要复制的数据库(可设置多个) binlog-ignore-db=mysql binlog-ignore-db=information_schema #设置需要复制的数据库 binlog-do-db=需要复制的主数据库名字 #设置logbin格式 binlog_format=STATEMENT |
|---|
binlog日志三种格式
1.Statement:每一条会修改数据的sql都会记录在binlog中。
优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。(相比row能节约多少性能与日志量,这个取决于应用的SQL情况,正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量,但是考虑到如果带条件的update操作,以及整表删除,alter表等操作,ROW格式会产生大量日志,因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况,其所产生的日志量会增加多少,以及带来的IO性能问题。)
缺点:由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行时候相同的结果。另外mysql 的复制,像一些特定函数功能,slave可与master上要保持一致会有很多相关问题(如sleep()函数, last_insert_id(),以及user-defined functions(udf)会出现问题).
2.Row:不记录sql语句上下文相关信息,仅保存哪条记录被修改。
优点: binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题
缺点:所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如一条update语句,修改多条记录,则binlog中每一条修改都会有记录,这样造成binlog日志量会很大,特别是当执行alter table之类的语句的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中。
3.Mixedlevel: 是以上两种level的混合使用,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种.新版本的MySQL中队row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录。至于update或者delete等修改数据的语句,还是会记录所有行的变更。
从机配置
| 修改配置文件:vim /etc/my.cnf #从服务器唯一ID server-id=2 #启用中继日志 relay-log=mysql-relay |
|---|
主机、从机重启MySQL服务
主机从机都关闭防火墙
在主机上建立帐户并授权slave
| #在主机MySQL里执行授权命令 GRANT REPLICATION SLAVE ON . TO 'slave'@'%' IDENTIFIED BY '123456'; flush privileges; #查询master的状态 show master status; #记录下File和Position的值 #执行完此步骤后不要再操作主服务器MySQL,防止主服务器状态值变化 |
|---|
在从机上配置需要复制的主机
| #复制主机的命令 CHANGE MASTER TO MASTER_HOST='主机的IP地址', MASTER_USER='slave', MASTER_PASSWORD='123456', MASTER_LOG_FILE='mysql-bin.具体数字',MASTER_LOG_POS=具体值; #启动从服务器复制功能 start slave; #查看从服务器状态 show slave status\G; #下面两个参数都是Yes,则说明主从配置成功! # Slave_IO_Running: Yes # Slave_SQL_Running: Yes |
|---|
主机新建库、新建表、insert记录,从机复制
如何停止从服务复制功能
| stop slave; 功能说明:停止I/O 线程和SQL线程的操作。 |
|---|
如何重新配置主从
| reset slave; 用于删除功能说明:SLAVE数据库的relaylog日志文件,并重新启用新的relaylog文件; reset master; 功能说明:删除所有的binglog日志文件,并将日志索引文件清空,重新开始所有新的日志文件。用于第一次进行搭建主从库时,进行主库binlog初始化工作; |
|---|
3.2 修改Mycat的配置文件schema.xml
- 之前的配置已分配了读写主机,是否已实现读写分离?
| 默认:balance="0" |
|---|
- 修改<dataHost>的balance属性,通过此属性配置读写分离的类型
| 负载均衡类型,目前的取值有4 种: (1)balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上。 (2)balance="1",全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,M2,S1,S2 都参与 select 语句的负载均衡。 (3)balance="2",所有读操作都随机的在 writeHost、readhost 上分发。 (4)balance="3",所有读请求随机的分发到 readhost 执行,writerHost 不负担读压力 |
|---|
- 为了能看到读写分离的效果,把balance设置成2,会在两个主机间切换查询 (2只限于测试,生产环境请选择1或3)
| … <dataHost name="host1" maxCon="1000" minCon="10" balance="2" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> … |
|---|
3.3 重启Mycat
./mycat console
多次执行查询语句即可看到效果
select * from mytbl ;
垂直拆分——分库
一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同 的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:
系统被切分成了,用户,订单交易,支付几个模块。
如何划分
一个问题:在两台主机上的两个数据库中的表,能否关联查询?
答案:不可以关联查询。
分库的原则:有紧密关联关系的表应该在一个库里,相互没有关联关系的表可以分到不同的库里。
| #客户表 rows:20万 CREATE TABLE customer( id INT AUTO_INCREMENT, NAME VARCHAR(200), PRIMARY KEY(id) ); #订单表 rows:600万 CREATE TABLE orders( id INT AUTO_INCREMENT, order_type INT, customer_id INT, amount DECIMAL(10,2), PRIMARY KEY(id) ); #订单详细表 rows:600万 CREATE TABLE orders_detail( id INT AUTO_INCREMENT, detail VARCHAR(2000), order_id INT, PRIMARY KEY(id) ); #订单状态字典表 rows:20条 CREATE TABLE dict_order_type( id INT AUTO_INCREMENT, order_type VARCHAR(200), PRIMARY KEY(id) ); |
|---|
以上四个表如何分库?客户表分在一个数据库,另外三张都需要关联查询,分在另外一个数据库。
实现分库
停止mycat,停止主从,修改schema配置文件
在从机上执行 mysql> stop slave;
| … <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <table name="customer" dataNode="dn2" ></table> </schema> <dataNode name="dn1" dataHost="host1" database="orders" /> <dataNode name="dn2" dataHost="host2" database="orders" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="0" 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.200.166:3306" user="root" password="root"> </writeHost> </dataHost> <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM2" url="192.168.200.168:3306" user="root" password="root"> </writeHost> </dataHost> … |
|---|
新增两个空白库
分库操作不是在原来的老数据库上进行操作,需要准备两台机器分别安装新的数据库
| #在数据节点dn1、dn2上分别创建数据库orders CREATE DATABASE orders; #四张表如何建立,建立在什么位置? order 以及其他两张表建立dn1 ,customer dn2 |
|---|
启动Mycat
| ./mycat console |
|---|
访问Mycat进行分库
| #访问Mycat mysql -umycat -p123456 -h 192.168.200.168 -P 8066 #切换到TESTDB #创建4张表 执行上述创建表的sql 语句! #查看表信息,可以看到成功分库 |
|---|


相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分 到一个数据库,而另外的某些行又切分到其他的数据库中,如图:
实现分表
选择要拆分的表
MySQL单表存储数据条数是有瓶颈的,单表达到1000万条数据就达到了瓶颈,会影响查询效率,需要进行水平拆分(分表)进行优化。
例如:例子中的orders、orders_detail都已经达到600万行数据,需要进行分表优化。
分表字段
以orders表为例,可以根据不同自字段进行分表
| 编号 | 分表字段 | 效果 |
|---|---|---|
| 1 | id,createtime (主键、或创建时间) | 查询订单注重时效,历史订单被查询的次数少,如此分片会造成一个节点访问多,一个访问少,不平均。 |
| 2 | customer_id(客户id) | 根据客户id去分,两个节点访问平均,一个客户的所有订单都在同一个节点 |
停止mycat修改配置文件schema.xml
| #为orders表设置数据节点为dn1、dn2,并指定分片规则为mod_rule(自定义的名字) <table name="orders" dataNode="dn1,dn2" rule="mod_rule" ></table> #如下图 |
|---|
停止mycat修改配置文件rule.xml
| #在rule配置文件里新增分片规则mod_rule,并指定规则适用字段为customer_id, #还有选择分片算法mod-long(对字段求模运算),customer_id对两个节点求模,根据结果分片 #新增配置算法mod-long参数count为2,两个节点 <tableRule name="mod_rule"> <rule> <columns>customer_id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule> … 在原有的配置中将 3--->2即可! <function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!-- how many data nodes --> <property name="count">2</property> </function> |
|---|
在数据节点dn2上建orders表
| CREATE TABLE orders( id INT AUTO_INCREMENT, order_type INT, customer_id INT, amount DECIMAL(10,2), PRIMARY KEY(id) ); |
|---|
重启Mycat配置生效,将配置文件重新导入
访问Mycat灌数据实现分片
| #在mycat里向orders表插入数据,INSERT时字段不能省略 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); #在mycat、dn1、dn2中查看orders表数据,分表成功 |
|---|
Mycat 的分片“join”
Orders订单表已经进行分表操作了,和它关联的orders_detail订单详情表如何进行join查询。
我们要对orders_detail也要进行分片操作。Join的原理如下图:
E-R表
子表的记录与所关联的父表记录存放在同一个数据分片上
| #修改schema.xml配置文件 … <table name="orders" dataNode="dn1,dn2" rule="mod_rule" > <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" /> </table> … #在dn2创建orders_detail表 CREATE TABLE orders_detail( id INT AUTO_INCREMENT, detail VARCHAR(2000), order_id INT, PRIMARY KEY(id) ); #导入配置文件,重启Mycat #访问Mycat向orders_detail表插入数据 INSERT INTO orders_detail(id,detail,order_id) VALUES(1,'detail1',1); INSERT INTO orders_detail(id,detail,order_id) VALUES(2,'detail1',2); INSERT INTO orders_detail(id,detail,order_id) VALUES(3,'detail1',3); INSERT INTO orders_detail(id,detail,order_id) VALUES(4,'detail1',4); INSERT INTO orders_detail(id,detail,order_id) VALUES(5,'detail1',5); INSERT INTO orders_detail(id,detail,order_id) VALUES(6,'detail1',6); #在mycat、dn1、dn2中运行两个表join语句 select o.*,od.detail from orders o inner join orders_detail od on o.id=od.order_id; |
|---|
全局表
在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,考虑到字典表具有以下几个特性:
- 变动不频繁
- 数据量总体变化不大
- 数据规模不大,很少有超过数十万条记录
鉴于此,Mycat 定义了一种特殊的表,称之为“全局表”,全局表具有以下特性:
- 全局表的插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性
- 全局表的查询操作,只从一个节点获取
- 全局表可以跟任何一个表进行 JOIN 操作
将字典表或者符合字典表特性的一些表定义为全局表,则从另外一个方面,很好的解决了数据 JOIN 的难题。通过全局表+基于 E-R 关系的分片策略,Mycat 可以满足 80%以上的企业应用开发!
| #修改schema.xml配置文件 … <table name="orders" dataNode="dn1,dn2" rule="mod_rule" > <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" /> </table> <table name="dict_order_type" dataNode="dn1,dn2" type="global" ></table> … #在dn2创建dict_order_type表 CREATE TABLE dict_order_type( id INT AUTO_INCREMENT, order_type VARCHAR(200), PRIMARY KEY(id) ); #导入配置文件,重启Mycat #访问Mycat向dict_order_type表插入数据 INSERT INTO dict_order_type(id,order_type) VALUES(101,'type1'); INSERT INTO dict_order_type(id,order_type) VALUES(102,'type2'); #在Mycat、dn1、dn2中查询表数据 |
|---|
全局序列
在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。
为此,Mycat 提供了全局 sequence,并且提供了包含本地配置和数据库配置等多种实现方式
本地文件
此方式 Mycat 将 sequence 配置到文件中,当使用到 sequence 中的配置后,Mycat 会更下
classpath 中的 sequence_conf.properties 文件中 sequence 当前的值。
/usr/local/mycat/conf/sequence_conf.properties
优点:本地加载,读取速度较快
缺点:抗风险能力差,Mycat所在主机宕机后,无法读取本地文件。
时间戳方式
全局序列ID= 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加) 换算成十进制为 18 位数的 long 类型,每毫秒可以并发 12 位二进制的累加。
优点:配置简单
缺点:18位ID过长
自主生成全局序列
可在java项目里自己生成全局序列,如下:
根据业务逻辑组合
可以利用 redis的单线程原子性 incr来生成序列
但,自主生成需要单独在工程中用java代码实现,增加了分布式项目的复杂性
数据库方式★
- 建库序列脚本
| #在dn1主机上创建全局序列表 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; # 查询数据 SELECT * FROM MYCAT_SEQUENCE; #清空数据 TRUNCATE TABLE MYCAT_SEQUENCE; # truncate : 表示直接情况所有数据,不留任何痕迹!使用回滚无效! #创建全局序列所需函数:官方提供 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 $$ 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 $$ 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 $$ #初始化序列表记录 INSERT INTO MYCAT_SEQUENCE(NAME,current_value,increment) VALUES ('ORDERS', 400000,100); |
|---|

- 修改Mycat配置
| #修改sequence_db_conf.properties vim sequence_db_conf.properties #意思是 ORDERS这个序列在dn1这个节点上,具体dn1节点是哪台机子,请参考schema.xml #修改server.xml vim server.xml 16行#全局序列类型:0-本地文件,1-数据库方式,2-时间戳方式。此处应该修改成1。 #重启Mycat |
|---|

- 验证全局序列
| #登录Mycat,插入数据 insert into orders(id,amount,customer_id,order_type) values(next value for MYCATSEQ_ORDERS,1000,101,102); #查询数据 #重启Mycat后,再次插入数据,再查询(模拟Mycat备机上线) |
|---|
总结:
- 利用数据库一个表来进行计数累加。但是并不是每次生成序列都读写数据库,这样效率太低。
- Mycat会预加载一部分号段到Mycat的内存中,这样大部分读写序列都是在内存中完成的。
- 如果内存中的号段用完了 Mycat会再向数据库要一次。
- 问:如果Mycat崩溃了 ,内存中的序列岂不是都没了?
- 是的。如果是这样,那么Mycat启动后会向数据库申请新的号段,原有号段会弃用。
- 也就是说如果Mycat重启,那么损失是当前的号段没用完的号码,但是不会因此出现主键重复。
更多推荐







所有评论(0)