在分布式专题的开篇,我们曾提到,如今成熟的互联网架构,对于分库分表的应用必不可少!
上一节,我们主要讲述了数据库性能优化方案,本节我们就来认识一下MyCat!
MyCat共计分为三节,分别是:
本节重点:
➢ 掌握 Mycat 详细配置含义 ➢ 了解 Mycat 监控与日志查看
官网 Mycat 概要介绍 入门指南
历史:从阿里cobar 升级而来,由开源组织维护,2.0 正在开发中。 定位:运行在应用和数据库之间,可以当做一个 MysqL 服务器使用,实现对 MysqL 数据库的分库分表,也可以通过JDBC 支持其他的数据库。
Mycat 的关键特性(官网首页)
这里以上节的实验配置为例:
Linux1:192.168.200.111 、端口3306为MysqL服务 Linux2:192.168.200.112、端口3306为MysqL服务 Linux3:192.168.200.113、端口3306为MysqL服务、端口8066为MyCat服务
当我们直接访问192.168.200.113机器8066端口的时候,通过Mycat配置文件生成的逻辑表就可以实现对于不同数据库的管理,资源分配与访问。
下载、解压Mycat(有Windows 版本,可以在本地数据库测试) http://dl.mycat.io/
主要的配置文件server.xml、schema.xml、rule.xml 和具体的分片配置文件。
server.xml
schema.xml
rule.xml
坑非常多,配置错误会导致无法启动,这个时候要看日志! 注意备份,不知道什么时候就跑不起来了……
包含系统配置信息。 system 标签:例如字符集、线程数、心跳、分布式事务开关等等。 user 标签:配置登录用户和权限。
<user name="root" defaultAccount="true"> <property name="password">123456</property> <property name="schemas">catmall</property> </user>
mycat 对密码加密:
java -cp xxx-mycat.jar io.mycat.util.DecryptUtil 0:root:123456
https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_schema schema 在MysqL 里面跟数据库是等价的。 schema.xml 包括逻辑库、表、分片规则、分片节点和数据源,可以定义多个 schema。这里面有三个主要的标签(table、datanode、dataHost):
表名和库名最好都用小写 定义了逻辑表,以及逻辑表分布的节点和分片规则:
<schema name="catmall" checksqlschema="false" sqlMaxLimit="100"> <!-- 范围分片 --> <table name="customer" primaryKey="id" datanode="dn1,dn2,dn3" rule="rang-long-cust" /> <!-- 取模分片 --> <table name="order_info" datanode="dn1,dn2,dn3" rule="mod-long-order" > <!-- ER 表 --> <childTable name="order_detail" primaryKey="id" joinKey="order_id" parentKey="order_id"/> </table> <!-- 全局表 --> <table name="student" primaryKey="sid" type="global" datanode="dn1,dn2,dn3" /> </schema>
primaryKey
datanode
<datanode name="dn1" dataHost="host1" database="cat" />
数据节点与物理数据库的对应关系。
配置物理主机的信息,readhost 是从属于writehost 的。
<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="localhost:3306" user="root" password="123456"> <!-- can have multi read hosts --> <readHost host="hostS2" url="192.168.8.146:3306" user="root" password="xxx"/> </writeHost> <writeHost host="hostS1" url="localhost:3316" user="root" password="123456"/> <!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> --> </dataHost>
balance:负载的配置,决定select 语句的负载
writeType:读写分离的配置,决定update、delete、insert 语句的负载
switchType:主从切换配置
show slave status
show status like 'wsrep%'
定义了分片规则和算法
分片规则:
<tableRule name="rang-long-cust"> <rule> <columns>id</columns> <algorithm>func-rang-long-cust</algorithm> </rule> </tableRule>
分片算法:
<function name="func-rang-long-cust" class="io.mycat.route.function.A@R_404_6122@artitionByLong"> <property name="mapFile">rang-long-cust.txt</property> </function>
分片配置:rang-long-cust.txt
10001-20000=1 0-10000=0 20001-100000=2
https://www.cnblogs.com/leeSmall/p/9551038.html
Mycat 也支持 ZK 配置( 用于管理配置和生成全局 ID ) , 执行 bin 目录下 init_zk_data.sh,会自动将 zkconf 下的所有配置文件上传到 ZK(先拷贝到这个目录)。
cd /usr/local/soft/mycat/conf cp *.txt *.xml *.properties zkconf/ cd /usr/local/soft/mycat/bin ./init_zk_data.sh
启 用 ZK 配 置 : mycat/conf/myid.properties
mycat/conf/myid.properties
loadZk=true zkURL=127.0.0.1:2181 clusterId=010 myid=01001 clusterSize=1 clusterNodes=mycat_gp_01 #server booster ; booster install on db same server,will reset all minCon to 2 type=server boosterDataHosts=dataHost1
注意如果执行 init_zk_data.sh 脚本报错的话,代表未写入成功,此时不要启用 ZK 配置并重启,否则本地文件会被覆盖。 启动时如果 loadzk=true 启动时,会自动从zk 下载配置文件覆盖本地配置。 在这种情况下如果修改配置,需要先修改conf 目录的配置,copy 到zkconf,再执行上传。
进入mycat/bin 目录(注意要先启动物理数据库)
explain 可以用来看路由结果,现在我们
在三个数据库中建表:customer、order_info、order_detail、student。(sql脚本请参考文末)
customer
order_info
order_detail
student
配置数据节点
<datanode name="dn1" dataHost="host1" database="cat"/> <datanode name="dn2" dataHost="host2" database="cat"/> <datanode name="dn3" dataHost="host3" database="gpcat"/> <dataHost balance="0" maxCon="1000" minCon="10" name="host1" writeType="0" switchType="1" slaveThreshold="100" dbType="MysqL" dbDriver="native"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.200.111:3306" password="123456" user="root"/> </dataHost> <dataHost balance="0" maxCon="1000" minCon="10" name="host2" writeType="0" switchType="1" slaveThreshold="100" dbType="MysqL" dbDriver="native"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.200.112:3306" password="123456" user="root"/> </dataHost> <dataHost balance="0" maxCon="1000" minCon="10" name="host3" writeType="0" switchType="1" slaveThreshold="100" dbType="MysqL" dbDriver="native"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.200.113:3306" password="123456" user="root"/> </dataHost>
<tableRule name="rang-long-cust"> <rule> <columns>id</columns> <algorithm>rang-long-cust</algorithm> </rule> </tableRule>
<function name="rang-long-cust" class="io.mycat.route.function.A@R_404_6122@artitionByLong"> <property name="mapFile">rang-long-cust.txt</property> </function>
INSERT INTO `customer` (`id`, `name`) VALUES (6666, '赵先生'); INSERT INTO `customer` (`id`, `name`) VALUES (7777, '钱先生'); INSERT INTO `customer` (`id`, `name`) VALUES (16666, '孙先生'); INSERT INTO `customer` (`id`, `name`) VALUES (17777, '李先生'); INSERT INTO `customer` (`id`, `name`) VALUES (26666, '周先生'); INSERT INTO `customer` (`id`, `name`) VALUES (27777, '吴先生');
<tableRule name="mod-long-order"> <rule> <columns>order_id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <property name="count">3</property> </function>
INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (1, 1000001, 1, 2, '2021-1-23 14:35:37', '2021-1-23 14:35:37'); INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (2, 1000002, 1, 2, '2021-1-24 14:35:37', '2021-1-24 14:35:37'); INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (3, 1000003, 3, 1, '2021-1-25 11:35:49', '2021-1-25 11:35:49');
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (3, 20210001, 85114752, 19.99, 1, 1, 1); INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (1, 20210002, 25411251, 1280.00, 1, 1, 0); INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (1, 20210003, 62145412, 288.00, 1, 1, 2); INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (2, 20210004, 21456985, 399.00, 1, 1, 2); INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (2, 20210005, 21457452, 1680.00, 1, 1, 2); INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (2, 20210006, 65214789, 9999.00, 1, 1, 3);
<table name="student" datanode="dn1,dn2,dn3" primaryKey="sid" type="global"/>
INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (1, ' 黑 白 ', '166669999'); INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (2, 'AV 哥 ', '466669999'); INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (3, '最强菜鸟', '368828888'); INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (4, '加载中', '655556666'); INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (5, '猫老公', '265286999'); INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (6, '一个人的精彩', '516895555');
Mycat 全局序列实现方式主要有 4 种:本地文件方式、数据库方式、本地时间戳算法、ZK。也可以自定义业务序列。 注意获取全局ID 的前缀都是:MYCATSEQ_
配置文件server.xml sequnceHandlerType 值:
<property name="sequnceHandlerType">0</property>
文件方式,配置conf/sequence_conf.properties
CUSTOMER.HISIDS= CUSTOMER.MINID=10000001 CUSTOMER.MAXID=20000000 CUSTOMER.CURID=10000001
语法:select next value for MYCATSEQ_CUSTOMER
select next value for MYCATSEQ_CUSTOMER
INSERT INTO `customer` (`id`, `name`) VALUES (next value for MYCATSEQ_CUSTOMER, 'zhangsan');
优点:本地加载,读取速度较快。 缺点:当 Mycat 重新发布后,配置文件中的 sequence 需要替换。Mycat 不能做集群部署。
sequence
<property name="sequnceHandlerType">1</property>
配置: sequence_db_conf.properties 把这张表创建在 146 上,所以是dn1
#sequence stored in datanode GLOBAL=dn1 CUSTOMER=dn1
在第一个数据库节点上创建MYCAT_SEQUENCE 表:
DROP TABLE IF EXISTS MYCAT_SEQUENCE; CREATE TABLE MYCAT_SEQUENCE ( name VARCHAR(50) NOT NULL, current_value INT NOT NULL, increment INT NOT NULL DEFAULT 1, remark varchar(100), PRIMARY KEY(name)) ENGINE=InnoDB;
注:可以在schema.xml 配置文件中配置这张表,供外部访问。
<table name="mycat_sequence" datanode="dn1" autoIncrement="true" primaryKey="id"></table>
创建存储过程——获取当前sequence 的值
DROP FUNCTION IF EXISTS `mycat_seq_currval`; DELIMITER ;; CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_currval`(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET latin1 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 ;
创建存储过程,获取下一个sequence
DROP FUNCTION IF EXISTS `mycat_seq_nextval`; DELIMITER ;; CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_nextval`(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET latin1 DETERMINISTIC BEGIN UPDATE MYCAT_SEQUENCE SET current_value = current_value + increment WHERE name = seq_name; RETURN mycat_seq_currval(seq_name); END ;; DELIMITER ;
创建存储过程,设置sequence
DROP FUNCTION IF EXISTS `mycat_seq_setval`; DELIMITER ;; CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_setval`(seq_name VARCHAR(50), value INTEGER) RETURNS varchar(64) CHARSET latin1 DETERMINISTIC BEGIN UPDATE MYCAT_SEQUENCE SET current_value = value WHERE name = seq_name; RETURN mycat_seq_currval(seq_name); END ;; DELIMITER ;
插入记录
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment,remark) VALUES ('GLOBAL', 1, 100,''); INSERT INTO MYCAT_SEQUENCE(name,current_value,increment,remark) VALUES ('ORDERS', 1, 100,'订单表使用');
测试
select next value for MYCATSEQ_ORDERS
ID= 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加) ,长度为 18 位
<property name="sequnceHandlerType">2</property>
配置文件sequence_time_conf.properties
#sequence depend on TIME WORKID=01 DATAACENTERID=01
验证:select next value for MYCATSEQ_GLOBAL
修改conf/myid.properties 设置loadZk=true(启动时会从 ZK 加载配置,一定要注意备份配置文件,并且先用bin/init_zk_data.sh,把配置文件写入到ZK)
<property name="sequnceHandlerType">3</property>
配置文件:sequence_distributed_conf.properties
# 代表使用zk INSTANCEID=ZK # 与myid.properties 中的CLUSTERID 设置的值相同 CLUSTERID=010
复制配置文件
cd /usr/local/soft/mycat/conf cp *.txt *.xml *.properties zkconf/ chown -R zkconf/ cd /usr/local/soft/mycat/bin ./init_zk_data.sh
在schema.xml 的table 标签上配置 autoIncrement=“true”,不需要获取和指定序列的情况下,就可以使用全局ID 了。
连接到管理端口 9066,注意必须要带 IP
MysqL -uroot -h127.0.0.1 -p123456 -P9066
全部命令:
MysqL>show @@help;
https://github.com/MyCATApache/Mycat-download/tree/master/mycat-web-1.0
Mycat-eye 是mycat 提供的一个监控工具,它依赖于ZK。本地必须要运行一个ZK,必须先启动ZK。 参考:https://gper.club/articles/7e7e7f7ff7g59gc3g64
下载mycat-web
cd /usr/local/soft wget http://dl.mycat.io/mycat-web-1.0/Mycat-web-1.0-SNAPSHOT-20210102153329-linux.tar.gz tar -xzvf Mycat-web-1.0-SNAPSHOT-20210102153329-linux.tar.gz
启动mycat-web
cd mycat-web nohup ./start.sh &
停止:kill start.jar 相关的进程 访 问 端 口 8082
http://192.168.200.113:8082/mycat/
mycat server.xml 配置
<!-- 1 为开启实时统计、0 为关闭 --> <property name="usesqlStat">1</property>
重启mycat 服务生效
log4j 的level 配置要改成debug
wrapper 日志:mycat 启动,停止,添加为服务等都会记录到此日志文件,如果系统环境配置错误或缺少配置时,导致 Mycat 无法启动,可以通过查看 wrapper.log 定位具体错误原因。
mycat.log 为 mycat 主要日志文件,记录了启动时分配的相关 buffer 信息,数据源连接信息,连接池,动态类加载信息等等。 在conf/log4j2.xml 文件中进行相关配置,如保留个数,大小,字符集,日志文件大小等。 以select 为例。
CREATE TABLE `customer` ( `id` int(11) DEFAULT NULL, `name` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `order_info` ( `order_id` int(11) NOT NULL COMMENT '订单ID', `uid` int(11) DEFAULT NULL COMMENT '用户ID', `nums` int(11) DEFAULT NULL COMMENT '商品数量', `state` int(2) DEFAULT NULL COMMENT '订单状态', `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `order_detail` ( `order_id` int(11) NOT NULL COMMENT '订单号', `id` int(11) NOT NULL COMMENT '订单详情', `goods_id` int(11) DEFAULT NULL COMMENT '货品ID', `price` decimal(10,2) DEFAULT NULL COMMENT '价格', `is_pay` int(2) DEFAULT NULL COMMENT '支付状态', `is_ship` int(2) DEFAULT NULL COMMENT '是否发货', `status` int(2) DEFAULT NULL COMMENT '订单详情状态', PRIMARY KEY (`order_id`,`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `student` ( `sid` int(8) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `qq` varchar(255) DEFAULT NULL, PRIMARY KEY (`sid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
更多架构知识,欢迎关注本套Java系列文章:Java架构师成长之路
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。