MyCat应用实战
下载Mycat
https://github.com/MyCATApache/Mycat-Server/releases
Mycat基础分库
基础配置
- 修改server.xml
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">TESTDB</property>
<property name="defaultSchema">TESTDB</property>
<!--No MyCAT Database selected 错误前会尝试使用该schema作为schema,不设置则为null,报错 -->
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<schema name="TESTDB" dml="0110" >
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges>
-->
</user>
可以修改用户名和密码以及逻辑库名。另外将全局主键改为文件的方式,将下面的1改为0.
<property name="sequenceHandlerType">1</property>
- 修改schema.xml
修改了数据库名,数据库用户密码
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
<table name="position" primaryKey="id" dataNode="dn1,dn2" rule="sharding-by-intfile" autoIncrement="true" >
</table>
</schema>
<dataNode name="dn1" dataHost="localhost1" database="ds_0" />
<dataNode name="dn2" dataHost="localhost1" database="ds_1" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306" user="root"
password="0490218292">
</writeHost>
</dataHost>
</mycat:schema>
- 配置rule.xml
rule.xml里定义了很多分片规则,我们选择最简单的取模分片mod-long,只需要将这个名称配置到schema.xml里就行了。另外因为我们只有2个节点,所以取余的数量需要将3改为2。
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">3</property>
</function>
将mod-long配置到rule里。
<table name="position" primaryKey="id" dataNode="dn1,dn2" rule="mod-long" autoIncrement="true" >
</table>
启动
在mycat/bin目录下,用管理员身份启动cmd,执行如下命令:
mycat install
mycat start
启动完成后,就可以进行访问了。
命令行方式:
mysql -uroot -p123456 -h127.0.0.1 -P8066
测试
插入数据测试:
INSERT INTO POSITION (id,NAME,salary,city) VALUES(1,"lagou3","1222","sichuan");
INSERT INTO POSITION (id,NAME,salary,city) VALUES(2,"lagou3","1222","sichuan");
INSERT INTO POSITION (id,NAME,salary,city) VALUES(3,"lagou3","1222","sichuan");
INSERT INTO POSITION (id,NAME,salary,city) VALUES(4,"lagou3","1222","sichuan");
数据是否分布到了ds_0、ds_1两个库中。
查询:
select * from POSITION;
看能否查出两个库中的所有数据。
碰到的问题
问题1:因为我是mysql8,所以在mycat/lib中用mysql-connector-java-8.0.11.jar替换了原有的.
问题2:The server time zone value '???ú±ê×??±??' is unrecognized or represents more than one time zone
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="0490218292">
</writeHost>
url配置里加上了时区。
此外mycat还有如下命令:
启动命令:./mycat start
停止命令:./mycat stop
重启命令:./mycat restart
查看状态:./mycat status
全局序列ID
全局序列id有5种方式可以选择。
在server.xml里有如下配置:
<property name="sequenceHandlerType">0</property>
- 0 代表本地文件方式
- 1 代表数据库方式生成
- 2 代表使用本地时间戳方式
- 3 表示基于ZK与本地配置的分布式ID生成器
- 4 使用zookeeper递增方式生成
1. 本地文件方式
本地主键的配置文件是conf/sequence_conf.properties。
#default global sequence
GLOBAL.HISIDS=
GLOBAL.MINID=10001
GLOBAL.MAXID=20000
GLOBAL.CURID=10000
# self define sequence
COMPANY.HISIDS=
COMPANY.MINID=1001
COMPANY.MAXID=2000
COMPANY.CURID=1000
CUSTOMER.HISIDS=
CUSTOMER.MINID=1001
CUSTOMER.MAXID=2000
CUSTOMER.CURID=1000
ORDER.HISIDS=
ORDER.MINID=1001
ORDER.MAXID=2000
ORDER.CURID=1000
HOTNEWS.HISIDS=
HOTNEWS.MINID=1001
HOTNEWS.MAXID=2000
HOTNEWS.CURID=1000
我们默认在进行插入的时候就会使用mycat的全局主键生成,那么上面有那么多的配置项,具体取哪一个配置项呢?
这个配置的前缀是跟表名对应的。举例说明:
<table name="position" primaryKey="id" dataNode="dn1,dn2" rule="mod-long" autoIncrement="true" >
</table>
比如这个表,就需要配置如下:
POSITION.HISIDS=
POSITION.MINID=1
POSITION.MAXID=20000
POSITION.CURID=0
这样,在我们不指定id进行插入时就可以取我们配置的序列号了。
INSERT INTO POSITION (NAME,salary,city) VALUES("lagou3","1222","sichuan");
当然也可以显示指定取哪一个序列号配置:
INSERT INTO POSITION (id,NAME,salary,city) VALUES('next value for MYCATSEQ_GLOBAL',"lagou3","1222","sichuan");
MYCATSEQ_ 后面连接的就是配置前缀名称。
2. 数据库方式
配置文件是conf/sequence_db_conf.properties,里面需要配置db的id相关的配置信息存在哪个库中。
#sequence stored in datanode
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1
此处的dn1是逻辑库名。
我们需要在dn1对应的数据库中新建表,并且插入数据。Mycat给我们提供了建表等语句(conf/dbseq.sql)。我们执行即可。
increment字段表示一次从数据库取几个值。比如设置为10的话,就是一次取10个id供应用端使用。
3. 时间戳方式
配置文件是conf/sequence_time_conf.properties。
#sequence depend on TIME
WORKID=01
DATAACENTERID=01
全局表配置
只需要在schema.xml的配置中加上type=global就行了
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
<table name="city" primaryKey="id" dataNode="dn1,dn2" autoIncrement="true" type="global" >
</table>
</schema>
读写分离
通过writeHost 和readHost 实现
<dataHost name="localhost2" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="0490218292">
<readHost host="hostS1" url="jdbc:mysql://192.168.56.115:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="123456">
</readHost>
</writeHost>
</dataHost>
或者
<dataHost name="localhost2" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="0490218292">
</writeHost>
<writeHost host="hostS1" url="jdbc:mysql://192.168.56.115:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="123456">
</writeHost>
</dataHost>
两种方式对比:
- 第一种配置方式,如果写节点挂了,读节点不可用。
- 第二种配置方式(查询的时候取第二个writeHost),如果其中一个写节点挂了,另一个不受影响。
如果设置全库的读写分离,schema.xml配置如下:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn3">
</schema>
<dataNode name="dn3" dataHost="localhost2" database="crm" />
<dataHost name="localhost2" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="0490218292">
<readHost host="hostS1" url="jdbc:mysql://192.168.56.115:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="123456">
</readHost>
</writeHost>
</dataHost>
</mycat:schema>
此时
如果设置某些表(示例中是dept)读写分离,配置如下:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
<table name="position" primaryKey="id" dataNode="dn1,dn2" rule="mod-long" autoIncrement="true" >
</table>
<table name="city" primaryKey="id" dataNode="dn1,dn2" autoIncrement="true" type="global" >
</table>
<table name="dept" primaryKey="id" dataNode="dn3" autoIncrement="true" ruleRequired="false">
</table>
</schema>
<dataNode name="dn1" dataHost="localhost1" database="ds_0" />
<dataNode name="dn2" dataHost="localhost1" database="ds_1" />
<dataNode name="dn3" dataHost="localhost2" database="crm" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="0490218292">
</writeHost>
</dataHost>
<dataHost name="localhost2" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="0490218292">
<readHost host="hostS1" url="jdbc:mysql://192.168.56.115:3306?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8" user="root"
password="123456">
</readHost>
</writeHost>
</dataHost>
</mycat:schema>
强制路由
通过mycat的注释实现:
/*#mycat:db_type=slave*/ SELECT * FROM dept;
/*#mycat:db_type=master*/ SELECT * FROM dept;
1.6版本后,还有如下注释:
/*!mycat:sql=sql */ 指定真正执行的SQL
/*!mycat:schema=schema1 */ 指定走那个schema
/*!mycat:datanode=dn1 */ 指定sql要运行的节点
/*!mycat:catlet=io.mycat.catlets.ShareJoin */ 通过catlet支持跨分片复杂SQL实现以及存
储过程支持等
主从延时切换
switchType参数:
- -1:表示不自动切换
- 1:表示自动切换
- 2:表示基于Mysql主从同步状态决定是否切换
- 3:基于Mysql cluster集群切换机制
1.4版本后开始支持MySQL主从复制状态绑定的读写分离机制,让读操作更加安全可靠。需要配置内容如下:
MyCAT心跳检查语句配置为 show slave status,dataHost上定义两个新属性:switchType="2"与slaveThreshold="100"
此时意味着开启 MySQL 主从复制状态绑定的读写分离与切换机制,Mycat心
跳机制通过检测 show slave status 中的 "Seconds_Behind_Master", "Slave_IO_Running",
"Slave_SQL_Running" 三个字段来确定当前主从同步的状态以及 Seconds_Behind_Master 主从复制时延, 当Seconds_Behind_Master > slaveThreshold时,读写分离筛选器会过滤掉此Slave机器,防止读到很久之前的旧数据,而当主节点宕机后,切换逻辑会检查Slave上的Seconds_Behind_Master是否为0,为0时则表示主从同步,可以安全切换,否则不会切换
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0"
dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
<heartbeat>show slave status </heartbeat>
<!-- can have multi write hosts -->
<writeHost host="M1" url="localhost:3306" user="root" password="123456">
</writeHost>
<writeHost host="S1" url="localhost:3316" user="root"
</dataHost>
1.4.1开始支持MySQL集群模式:
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0"
dbType="mysql" dbDriver="native" switchType="3" >
<heartbeat> show status like ‘wsrep%’</heartbeat>
<writeHost host="M1" url="localhost:3306" user="root"password="123456">
</writeHost>
<writeHost host="S1"url="localhost:3316"user="root"password="123456" >
</writeHost>
</dataHost>
XA事务使用
Mycat 从 1.6.5 版本开始支持标准XA分布式事务,考虑到MySQL5.7之前版本XA有bug,所以推荐最佳搭配XA功能使用 MySQL 5.7 版本。
Mycat实现XA标准分布式事务,Mycat作为XA事务协调者角色,即使事务过程中Mycat宕机挂掉,由于Mycat会记录事务日志,所以Mycat恢复后会进行事务的恢复善后处理工作。考虑到分布式事务的性能开销比较大,所以只推荐在全局表的事务以及其他一些对一致性要求比较高的场景。
使用示例:
- XA事务需要设置手动提交
set autocommit=0;
- 开启XA事务
set xa=on;
- 执行相应的SQL语句部分
insert into city(id,name,province) values(200,'chengdu','sichuan');
update position set salary='300000' where id<5;
- 提交或回滚事务
commit;
rollback;
如果其中一个sql语句失败,整体都会回滚。
保证Repeatable Read
mycat 有一个特性,就是开事务之后,如果不运行 update/delete/select for update 等更新类语句SQL 的话,不会将当前连接与当前 session 绑定。如下图所示:
这样做的好处是可以保证连接可以最大限度的复用,提升性能。但是回导致两次查询的结果不一致。违背了MySQL可重复读的特性。Mycat给我们提供了一个配置来解决这个问题。
server.xml 的 system 配置了 strictTxIsolation=true)(默认是false) 的时候,就会严格遵守隔离级别。配置后的连接如下图所示: