Mycat 高可用
目前Mycat 没有实现对多Mycat 集群的支持,可以暂时使用HAProxy 来做负载
思路:HAProxy 对Mycat 进行负载。Keepalived 实现VIP。
Mycat 注解
注解的作用
当关联的数据不在同一个节点的时候,Mycat 是无法实现跨库join 的。
举例:
如果直接在150 插入主表数据,151 插入明细表数据,此时关联查询无法查询出来。
-- 150 节点插入
INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (9, 1000003, 3, 1,
'2019-9-25 11:35:49', '2019-9-25 11:35:49');
-- 151 节点插入
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (9, 20180001,
85114752, 19.99, 1, 1, 1);
在mycat 数据库查询,直接查询没有结果。
select a.order_id,b.goods_id from order_info a, order_detail b where a.order_id = b.order_id;
Mycat 作为一个中间件,有很多自身不支持的SQL 语句,比如存储过程,但是这些语句在实际的数据库节点上是可以执行的。有没有办法让Mycat 做一层透明的代理转发,直接找到目标数据节点去执行这些SQL 语句呢?
那我们必须要有一种方式告诉Mycat 应该在哪个节点上执行。这个就是Mycat 的注解。我们在需要执行的SQL 语句前面加上一段代码,帮助Mycat 找到我们的目标节点。
注解的用法
注解的形式是:
/!mycat: sql=注解SQL 语句/
注解的使用方式是:
/!mycat: sql=注解SQL 语句/ 真正执行的SQL
使用时将= 号后的"注解SQL 语句" 替换为需要的SQL 语句即可。
使用注解有一些限制,或者注意的地方:
- select
如果需要确定分片,则使用能确定分片的注解,比如
/!mycat: sql=select * from users where user_id=1/
如果要在所有分片上执行则可以不加能确定分片的条件 - insert
使用insert 的表作为注解SQL,必须能确定到某个分片原始SQL 插入的字段必须包括分片字段非分片表(只在某个节点上):必须能确定到某个分片 - delete 使用delete 的表作为注解SQL
- update 使用update 的表作为注解SQL
使用注解并不额外增加MyCat 的执行时间;从解析复杂度以及性能考虑,注解SQL 应尽量简单,因为它只是用来做路由的。
注解可以帮我们解决什么问题呢?
注解使用示例
- 创建表或存储过程
customer.id=1 全部路由到146
-- 存储过程
/*!mycat: sql=select * from customer where id =1 */ CREATE PROCEDURE test_proc() BEGIN END ;
-- 表
/*!mycat: sql=select * from customer where id =1 */ CREATE TABLE test2(id INT);
- 特殊语句自定义分片
Mycat 本身不支持insert select,通过注解支持
/*!mycat: sql=select * from customer where id =1 */ INSERT INTO test2(id) SELECT id FROM order_detail;
- 多表ShareJoin
/*!mycat:catlet=io.mycat.catlets.ShareJoin */
select a.order_id,b.goods_id from order_info a, order_detail b where a.order_id = b.order_id;
- 读写分离
读写分离: 配置Mycat 读写分离后,默认查询都会从读节点获取数据,但是有些场景需要获取实时数据,如果从读节点获取数据可能因延时而无法实现实时,Mycat 支持通过注解/balance/ 来强制从写节点(write host)查询数据。
/*balance*/ select a.* from customer a where a.id=6666;
- 读写分离数据库选择(1.6 版本之后)
/*!mycat: db_type=master */ select * from customer;
/*!mycat: db_type=slave */ select * from customer;
/*#mycat: db_type=master */ select * from customer;
/*#mycat: db_type=slave */ select * from customer;
注解支持的'! '不被mysql 单库兼容
注解支持的'#'不被MyBatis 兼容
随着Mycat 的开发,更多的新功能正在加入。
注解原理
Mycat 在执行SQL 之前会先解析SQL 语句,在获得分片信息后再到对应的物理节点上执行。如果SQL 语句无法解析,则不能被执行。如果语句中有注解,则会先解析注解的内容获得分片信息,再把真正需要执行的SQL 语句发送对对应的物理节点上。
所以我们在使用主机的时候,应该清楚地知道目标SQL 应该在哪个节点上执行,注解的SQL 也指向这个分片,这样才能使用。如果注解没有使用正确的条件,会导致原始SQL 被发送到所有的节点上执行,造成数据错误。
分片策略详解
Mycat 权威指南.pdf Page 116
分片的目标是将大量数据和访问请求均匀分布在多个节点上,通过这种方式提升数
据服务的存储和负载能力。
Mycat 分片策略详解
总体上分为连续分片和离散分片,还有一种是连续分片和离散分片的结合,例如先范围后取模。
比如范围分片(id 或者时间)就是典型的连续分片,单个分区的数量和边界是确定的。离散分片的分区总数量和边界是确定的,例如对key 进行哈希运算,或者再取模。
关键词:范围查询、热点数据、扩容
连续分片优点:
1)范围条件查询消耗资源少(不需要汇总数据)
2)扩容无需迁移数据(分片固定)
连续分片缺点:
1)存在数据热点的可能性
2)并发访问能力受限于单一或少量DataNode(访问集中)
离散分片优点:
1)并发访问能力增强(负载到不同的节点)
2)范围条件查询性能提升(并行计算)
离散分片缺点:
1)数据扩容比较困难,涉及到数据迁移问题
2)数据库连接消耗比较多
连续分片
- 范围分片
id
rang-long
autopartition-long.txt
# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2
特点:容易出现冷热数据
- 按自然月分片
建表语句
CREATE TABLE `sharding_by_month` (
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
逻辑表
分片规则
create_time
qs-partbymonth
分片算法
yyyy-MM-dd
2019-10-01
2019-12-31
columns 标识将要分片的表字段,字符串类型,与dateFormat 格式一致。
algorithm 为分片函数。
dateFormat 为日期字符串格式。
sBeginDate 为开始日期。
sEndDate 为结束日期
注意:节点个数要大于月份的个数
测试语句
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-10-16', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-10-27', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-11-04', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-11-11', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-12-25', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-12-31', database());
另外还有按天分片(可以指定多少天一个分片)、按小时分片
离散分片
- 枚举分片
将所有可能出现的值列举出来,指定分片。例如:全国34 个省,要将不同的省的数据存放在不同的节点,可用枚举的方式。
建表语句:
CREATE TABLE `sharding_by_intfile` (
`age` int(11) NOT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
逻辑表:
分片规则:
sharding_id
hash-int
分片算法:
partition-hash-int.txt
0
0
type:默认值为0,0 表示Integer,非零表示String。
PartitionByFileMap.java,通过map 来实现。
策略文件:partition-hash-int.txt
16=0
17=1
18=2
插入数据测试:
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (16, database());
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (17, database());
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (18, database());
特点:适用于枚举值固定的场景。
- 一致性哈希
一致性hash 有效解决了分布式数据的扩容问题。
建表语句:
CREATE TABLE `sharding_by_murmur` (
`id` int(10) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
逻辑表
分片规则
id
qs-murmur
分片算法
0
3
160
测试语句
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (1, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (2, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (3, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (4, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (5, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (6, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (7, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (8, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (9, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (10, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (11, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (12, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (13, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (14, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (15, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (16, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (17, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (18, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (19, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (20, database());
特点:可以一定程度减少数据的迁移。
- 十进制取模分片
根据分片键进行十进制求模运算。
sid
mod-long
3
特点:分布均匀,但是迁移工作量比较大
- 固定分片哈希
这是先求模得到逻辑分片号,再根据逻辑分片号直接映射到物理分片的一种散列算法。
建表语句:
CREATE TABLE `sharding_by_long` (
`id` int(10) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
逻辑表
分片规则
id
qs-sharding-by-long
平均分成8 片(%1024 的余数,1024=128*8):
8
128
- partitionCount 为指定分片个数列表。
- partitionLength 为分片范围列表。
第二个例子:
两个数组,分成不均匀的3 个节点(%1024 的余数,1024=2256+1512):
2,1
256,512
3 个节点,对1024 取模余数的分布
测试语句
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (222, database());
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (333, database());
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (666, database());
特点:在一定范围内id 是连续分布的。
- 取模范围分片
逻辑表
建表语句
CREATE TABLE `sharding_by_pattern` (
`id` varchar(20) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
分片规则
user_id
sharding-by-pattern
分片算法
100
0
partition-pattern.txt
patternValue 取模基数,这里设置成100
partition-pattern.txt,一共3 个节点
id=19%100=19,在dn1;
id=222%100=22,dn2;
id=371%100=71,dn3
# id partition range start-end ,data node index
###### first host configuration
1-20=0
21-70=1
71-100=2
0-0=0
测试语句
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (19, database());
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (222, database());
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (371, database());
特点:可以调整节点的数据分布。
- 范围取模分片
建表语句
CREATE TABLE `sharding_by_rang_mod` (
`id` bigint(20) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
逻辑表
分片规则
id
qs-rang-mod
分片算法
partition-range-mod.txt
partition-range-mod.txt
# range start-end ,data node group size
0-20000=1
20001-40000=2
解读:先范围后取模。Id 在20000 以内的,全部分布到dn1。Id 在20001-40000的,%2 分布到dn2,dn3。
插入数据:
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (666, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (6667, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (16666, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (21111, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (22222, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (23333, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (24444, database());
特点:扩容的时候旧数据无需迁移
- 其他分片规则
应用指定分片PartitionDirectBySubString
日期范围哈希PartitionByRangeDateHash
冷热数据分片PartitionByHotDate
也可以自定义分片规则: extends AbstractPartitionAlgorithm implements RuleAlgorithm。
切分规则的选择
步骤:
1、找到需要切分的大表,和关联的表
2、确定分片字段(尽量使用主键),一般用最频繁使用的查询条件
3、考虑单个分片的存储容量和请求、数据增长(业务特性)、扩容和数据迁移问题。
例如:按照什么递增?序号还是日期?主键是否有业务意义?
一般来说,分片数要比当前规划的节点数要大。
总结:根据业务场景,合理地选择分片规则。
举例:
3.7 亿的数据怎么分表?我是不是分成3 台服务器?
1、一年内到达多少?两年内到达多少?(数据的增长速度)?
答:一台设备每秒钟往3 张表各写入一条数据,一共4 台设备。每张表一天86400*4=345600 条。每张表一个月10368000 条。
分析:增长速度均匀,可以用日期切分,每个月分一张表。
2、什么业务?所有的数据都会访问,还是访问新数据为主?
答:访问新数据为主,但是所有的数据都可能会访问到。
3、表结构和表数据是什么样的?一个月消耗多少空间?
答:字段不多,算过了,三年数据量有3.7 亿,30G。
分析:30G 没必要分库,浪费机器。
4、访问量怎么样?并发压力大么?
答:并发有一点吧
分析:如果并发量不大,不用分库,只需要在单库分表。不用引入Mycat 中间件了。如果要自动路由的话可以用Sharding-JDBC,否则就是自己拼装表名。
5、3 张表有没有关联查询之类的操作?
答:没有。
分析:还是拼装表名简单一点。
Mycat 离线扩缩容
当我们规划了数据分片,而数据已经超过了单个节点的存储上线,或者需要下线节点的时候,就需要对数据重新分片。
Mycat 自带的工具
准备工作
1、mycat 所在环境安装mysql 客户端程序。
2、mycat 的lib 目录下添加mysql 的jdbc 驱动包。
3、对扩容缩容的表所有节点数据进行备份,以便迁移失败后的数据恢复。
步骤
以取模分片表sharding-by-mod 缩容为例。
1、复制schema.xml、rule.xml 并重命名为newSchema.xml、newRule.xml 放于conf 目录下。
2、修改newSchema.xml 和newRule.xml 配置文件为扩容缩容后的mycat配置参数(表的节点数、数据源、路由规则)。
注意:
只有节点变化的表才会进行迁移。仅分片配置变化不会迁移。
newSchema.xml
改成(减少了一个节点):
newRule.xml 修改count 个数
2
3、修改conf 目录下的migrateTables.properties 配置文件,告诉工具哪些表需要进行扩容或缩容,没有出现在此配置文件的schema 表不会进行数据迁移,格式:
注意:
1)不迁移的表,不要修改dn 个数,否则会报错。
2)ER 表,因为只有主表有分片规则,子表不会迁移。
catmall=sharding-by-mod
4、dataMigrate.sh 中这个必须要配置
通过命令"find / -name mysqldump" 查找mysqldump 路径为
"/usr/bin/mysqldump",指定#mysql bin 路径为"/usr/bin/"
#mysql bin 路径
RUN_CMD="$RUN_CMD -mysqlBin= /usr/bin/"
5、停止mycat 服务
6、执行执行bin/ dataMigrate.sh 脚本注意:必须要配置Java 环境变量,不能用openjdk
7 、脚本执行完成, 如果最后的数据迁移验证通过, 就可以将之前的newSchema.xml 和newRule.xml 替换之前的schema.xml 和rule.xml 文件,并重启mycat 即可。
注意事项:
1)保证分片表迁移数据前后路由规则一致(取模——取模)。
2)保证分片表迁移数据前后分片字段一致。
3)全局表将被忽略。
4)不要将非分片表配置到migrateTables.properties 文件中。
5)暂时只支持分片表使用MySQL 作为数据源的扩容缩容。
migrate 限制比较多,还可以使用mysqldump。
mysqldump 方式
mysqldump -uroot -p123456 -h127.0.0.1 -P3306 -c -t --skip-extended-insert gpcat > mysql-1017.sql
-c 代表带列名
-t 代表只要数据,不要建表语句
--skip-extended-insert 代表生成多行insert(mycat childtable 不支持多行插入ChildTable multi insert not provided)
Mycat 导入
mysql -uroot -p123456 -h127.0.0.1 -P8066 catmall < mysql-1017.sql
Mycat 导出
mysqldump -h192.168.8.151 -uroot -p123456 -P8066 -c -t --skip-extended-insert catmall customer > mycat-cust.sql
其他导入方式:
load data local infile '/mycat/customer.txt' into table customer;
source sql '/mycat/customer.sql';
核心流程总结
官网的架构图:
启动
1、MycatServer 启动,解析配置文件,包括服务器、分片规则等
2、创建工作线程,建立前端连接和后端连接
执行SQL
1、前端连接接收MySQL 命令
2、解析MySQL,Mycat 用的是Druid 的DruidParser
3、获取路由
4、改写MySQL,例如两个条件在两个节点上,则变成两条单独的SQL
例如select * from customer where id in(5000001, 10000001);
改写成:
select * from customer where id = 5000001;(dn2 执行)
select * from customer where id = 10000001;(dn3 执行)
又比如多表关联查询,先到各个分片上去获取结果,然后在内存中计算
5、与后端数据库建立连接
6、发送SQL 语句到MySQL 执行
7、获取返回结果
8、处理返回结果,例如排序、计算等等
9、返回给客户端
源码下载与调试环境搭建
- 下载源代码,导入工程
git clone https://github.com/MyCATApache/Mycat-Server
- 配置
schema.xml
select user()
- 表结构
本地数据库创建db1、db2、db3 数据库,全部执行建表脚本
CREATE TABLE `company` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT '',
`market_value` bigint(20) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `hotnews` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(64) DEFAULT '',
`content` varchar(512) DEFAULT '0',
`time` varchar(8) DEFAULT '',
`cat_name` varchar(10) DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `travelrecord` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`city` varchar(32) DEFAULT '',
`time` varchar(8) DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
逻辑表配置
travelrecord 表配置
id
rang-long
autopartition-long.txt
hotnews 表配置
id
mod-long
3
company 表配置
- debug 方式启动
debug 方式启动main 方法
Mycat-Server-1.6.5-RELEASE\src\main\java\io\mycat\MycatStartup.java - 连接本机Mycat 服务
测试语句
insert into travelrecord(`id`, `city`, `time`) values(1, '长沙', '20191020');
insert into hotnews(`title`, `content`) values('咕泡', '盆鱼宴');
insert into company(`name`, `market_value`) values('spring', 100);
-
调试入口
33
6.3.7 调试入口
连接入口:
io.mycat.net.NIOAcceptor#accept
SQL 入口:
io.mycat.server.ServerQueryHandler#query
Step Over 可以看到上一层的调用——学自咕泡学院