mycat:数据库中间件
由于mycat的团队过于恶心,所以不建议使用mycat,可以转为使用shardding JDBC
为什么要用mycat? mycat解决一下三个问题
- 解决了Java应用与数据紧耦合的问题
- 缓解了高访问量高并发的压力
- 解决了读写请求数据不一致的问题
mycat主要做的事
-
读写分离
-
数据分片:垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)
-
多数据源整合
mycat原理
Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL 语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。这种方式把数据库的分布式从代码中解耦出来,程序员察觉不出来后台使用 Mycat 还是MySQL。
mycat安装
上传压缩安装包到服务器上
然后解压到/usr/local目录下
tar -zxvf Mycat-server-1.6.7.1-release-20190627191042-linux.tar.gz -C /usr/local/
去/usr/local/mycat/conf目录下编辑 server.xml 和schema.xml文件。
注意:这里的TESTDB指的是mycat逻辑库,并不是真正的库
确保两台机器都安装好mysql并且可以远程登录
mysql -umysqlu1 -p12345678 -h 192.168.107.135 -P 3306
mysql -umysqlu1 -p12345678 -h 192.168.107.135 -P 3306
启动mycat
- 控制台启动 :去 /usr/local/mycat/bin 目录下执行 ./mycat console
- 后台启动 :去 /usr/local/mycat/bin 目录下 ./mycat start
登录mycat
-
登录后台管理窗口(这个方法用于管理维护mycat)
mysql -umycat -p123456 -P 9066 -h 192.168.107.135
-
登录数据窗口(这个方式用于通过mycat查询数据)
mysql -umycat -p123456 -P 8066 -h 192.168.107.135
搭建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=testdb
#设置logbin格式
binlog_format=STATEMENT
上面的logbin格式格外重要,分三种模式 STATEMENT,ROW,MIXED。各有优缺点,具体可以去网上看https://www.cnblogs.com/xingyunfashi/p/8431780.html
修改从机配置文件
修改配置文件:vim /etc/my.cnf
#从服务器唯一ID
server-id=2
#启用中继日志
relay-log=mysql-relay
重启主机和从机的mysql服务
systemctl restart mysqld
查看重启后的mysql状态
systemctl status mysqld
在主机上创建一个用户,用于从机来复制数据
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' IDENTIFIED BY '12345678';
(命令解释:创建一个在所有库所有表都有主从复制权限的远程访问用户 ,用户名为slave,密码为123123)
查看主机master状态,在此之后 主机别操作了,防止主机状态值变化。
show master status;
记下File 和 Position的值
在从机上配置需要复制的主机,(进入mysql从机命令行 输入以下命令)
CHANGE MASTER TO MASTER_HOST='192.168.107.135',
MASTER_USER='slave',
MASTER_PASSWORD='12345678',
MASTER_LOG_FILE='mysql-bin.mysql-bin.000002',MASTER_LOG_POS=154;
启动从服务器复制功能
start slave;
查看服务器状态,下面两个都是yes才表示成功,如果有No,则下面的error行会报错原因。
show slave status\G;
如果从机已经配置过主机,那么我们可以reset 主机;
先 stop slave; 然后reset master; 然后再执行上面的语句。
现在我们在主机里新建testdb库(仅仅是testdb库 建表 建数据,其他不行 因为前面配置了只复制testdb这个库),然后看看从机是否有响相应的复制数据。
配置mycat读写分离,负载均衡类型,目前的取值有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 不负担读压力
这里的配置,如果是一主一从,配置3就可以了,如果是多主(多主之间互为主备)多从,配置1就行了
这里我们可以验证mycat 的select路由功能了
搭建mysql主从复制 (多主多从)。这里搭建二主二从
一个主机 m1 用于处理所有写请求,它的从机 s1 和另一台主机 m2 还有它的从机 s2 负责所有读请求。当 m1 主机宕机后,m2 主机负责写请求,m1、m2 互为备机,这里m1 和m2 的地位是一样的。架构图如下
我们这里是mysql服务器情况是
135 master1
129 master2
136 slave1
130 slave2
136 复制 135, 130 复制 129,129 和 135互相复制
依次关闭四台mysql服务,编辑mysql配置 vim /etc/my.cnf
master1的配置如下
#主服务器唯一ID
server-id=1
#启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
#设置需要复制的数据库
binlog-do-db=testdb
#设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
#表示自增长字段每次递增的量,指自增字段的起始值,其默认值是1,取值范围是1 .. 65535
auto-increment-increment=2
# 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是1 .. 65535
auto-increment-offset=1
master2的配置如下
#主服务器唯一ID
server-id=3 #启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
#设置需要复制的数据库
binlog-do-db=testdb
#设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
#表示自增长字段每次递增的量,指自增字段的起始值,其默认值是1,取值范围是1 .. 65535
auto-increment-increment=2
# 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是1 .. 65535
auto-increment-offset=2 # 这里从2开始,不能和master1重复
slave1配置
#从服务器唯一ID
server-id=2
#启用中继日志
relay-log=mysql-relay
slave2配置
#从服务器唯一ID
server-id=4
#启用中继日志
relay-log=mysql-relay
双主机、双从机重启 mysql 服务,主机从机都关闭防火墙。在两台主机建立授权slave账户
GRANT REPLICATION SLAVE ON . TO 'slave'@'%' IDENTIFIED BY '12345678';
查看m1 ,m2 状态,记下 File和Position
在两台从机配置 执行复制主机的动作。
#136 复制 135
CHANGE MASTER TO MASTER_HOST='192.168.107.135',
MASTER_USER='slave',
MASTER_PASSWORD='12345678',
MASTER_LOG_FILE='mysql-bin.mysql-bin.000005',MASTER_LOG_POS=1254;
#在130 复制 129
CHANGE MASTER TO MASTER_HOST='192.168.107.129',
MASTER_USER='slave',
MASTER_PASSWORD='12345678',
MASTER_LOG_FILE='mysql-bin.mysql-bin.000001',MASTER_LOG_POS=6271;
两台从服务器均执行 start slave ; 然后查看slave状态,均为YES则表示设置成功。
两主互相复制
# 129 复制 135
CHANGE MASTER TO MASTER_HOST='192.168.107.135',
MASTER_USER='slave',
MASTER_PASSWORD='12345678',
MASTER_LOG_FILE='mysql-bin.mysql-bin.000005',MASTER_LOG_POS=1254;
# 135复制 129
CHANGE MASTER TO MASTER_HOST='192.168.107.129',
MASTER_USER='slave',
MASTER_PASSWORD='12345678',
MASTER_LOG_FILE='mysql-bin.mysql-bin.000001',MASTER_LOG_POS=6271;
现在可以在主库中 建库 建表 建数据,验证双主双从是否配置成功了。
配置mycat双主双从读写分离
相比之前的一主一从配置,我们这里需要增加一对host主从配置
select user()
相关配置解释
# balance="1": 全部的readHost与stand by writeHost参与select语句的负载均衡。
#writeType="0": 所有写操作发送到配置的第一个writeHost,第一个挂了切到还生存的第二个
#writeType="1",所有写操作都随机的发送到配置的 writeHost,1.5 以后废弃不推荐
#writeHost,重新启动后以切换后的为准,切换记录在配置文件中:dnindex.properties 。
#switchType="1": 1 默认值,自动切换。 -1 表示不自动切换, 2 基于 MySQL 主从同步的状态决定是否切换。
验证mycat读写分离,登录mycat,从查询结果可以看出来,mycat查询是在2从一备份主之一执行查询的。
mysql -umycat -p123456 -P 8066 -h 192.168.107.135
验证可用性:任意关闭一台主mysql服务器,数据依然可以插入,依然可以查询数据,主服务器重启后,该台服务器依然会同步最新数据,只是现在这个服务器的角色肯定是主写服务器的备用服务器,不管它之前是主写服务器还是主服务器的备用服务器。
这种配置有一个问题,当任意一台主服务器挂掉的时候(比如m2),那么对于mycat来说 其实就等于挂掉两台服务器,因为s2也相当于也挂了,因为s2 找不到它的m2。 这毫无疑问肯定是不合理的。这里如果对s2自动做主从切换就好了 就是redis哨兵一样。
垂直拆分-分库
一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面。
要明确的是:在mysql的join操作中,如果两个表位于不同主机上的库中,那么join操作是不可执行的,如果位于同一个主机的不同库中 join是可以执行的, 我们这里后续指的 "分库","不同库" 等概念, 指的就是位于不同主机上的库,分库的总体原则是有业务关联关系的表应该放在一个库里,相互没有关联关系的表放在不同库里。
这里我们准备四张表 customer 客户表,orders 订单表,orders_detail 订单详情表,dict_order_type 订单字典表,我们客户表放一个库里,其他的表放一个库里。
mycat 配置如下
select user()
select user()
我们现在先在135和129服务器上新建orders数据库
CREATE DATABASE orders;
启动mycat 并访问mycat
/usr/local/mycat/bin/mycat console
mysql -umycat -p123456 -P 8066 -h 192.168.107.135
在mycat命令行下执行建库操作,建库语句如下
#客户表
CREATE TABLE customer(
id INT AUTO_INCREMENT,
NAME VARCHAR(200),
PRIMARY KEY(id)
);
#订单表
CREATE TABLE orders(
id INT AUTO_INCREMENT,
order_type INT,
customer_id INT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
#订单详细表
CREATE TABLE orders_detail(
id INT AUTO_INCREMENT,
detail VARCHAR(2000),
order_id INT,
PRIMARY KEY(id)
);
#订单状态字典表
CREATE TABLE dict_order_type(
id INT AUTO_INCREMENT,
order_type VARCHAR(200),
PRIMARY KEY(id)
);
然后切换到各自mysql控制台,可以看到相应的表已经建立在相应的数据上了,并且可以在相应的数据库上新增数据 ,查询数据等等,就这样分库成功了。
水平拆分-分表
MySQL单表存储数据条数是有瓶颈的,单表达到1000万条数据就达到了瓶颈,会影响查询效率,需要进行水平拆分(分表)进行优化。我们以orders表为例子来分表。
关于分表策略: 对于我们orders表来说 如果以id来分表的话,查询订单注重时效,历史订单被查询的次数少,如此分片会造成一个节点访问多,一个访问少,不平均。如果以customer_id来分的话,根据客户id去分,两个节点访问平均,一个客户的所有订单都在同一个节点,数据相对平均。其他典型分片策略有根据时间来分等等,总之分片是很重要的东西。
修改schema.xml,增加以下配置
schema.xml全配置文件如下
select user()
select user()
修改配置文件rule.xml,在rule配置文件里新增分片规则mod_rule,并指定规则适用字段为customer_id,还要选择分片算法mod-long(对字段求模运算),customer_id对两个节点求模,根据结果分片
customer_id
mod-long
2
现在我们在数据节点dn2上建orders表,因为之前只有dn1有orders表。在mycat命令行插入数据(字段不能省略)
-- 这里必须指明字段列(id,order_type,customer_id,amount) 不能省略,否则mycat不能识别
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中查询数据
因为数据是从两个库中来的,所以顺序和我们之前的单库不一样,我们制定排序字段就可以了。到此分表已经成功。
mycat join查询
上面的orders表已经分开了,那么与其相关的orders_detail表怎么办呢?假设我们现在执行一条关联sql(如下)。那会不会有问题?答案是肯定有问题,mycat经过分片分析在dn1和dn2节点都进行join查询。因为其中一个节点没有orders_detail 表,所以必然报错,最终结果也就是报错。
SELECT * FROM orders t1 LEFT JOIN orders_detail t2 WHERE t1.id = t2.order_id
mycat 解决上面问题的方法是 使用ER表。Mycat 借鉴了 NewSQL 领域的新秀 Foundation DB 的设计思路,Foundation DB 创新性的提出了 Table Group 的概念,其将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了JION 的效率和性能问 题,根据这一思路,提出了基于 E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。
更改schema.xml配置文件 将orders_detail的主表设置为orders,根据orders的id与orders_detail的id的关系来分配。
在dn2上新建orders_detail表,然后在mycat上插入数据
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语句,能看到对应的结果,其中mycat的结果是结果总集,是各个节点的数据总和。
Select o.*,od.detail from orders o inner join orders_detail od on o.id=od.order_id;
mycat 全局表
在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较 棘手的问题,考虑到字典表具有以下几个特性:
① 变动不频繁
② 数据量总体变化不大
③ 数据规模不大,很少有超过数十万条记录
鉴于此,Mycat 定义了一种特殊的表,称之为“全局表”,全局表具有以下特性:
① 全局表的插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性
② 全局表的查询操作,只从一个节点获取
③ 全局表可以跟任何一个表进行 JOIN 操作
修改 schema.xml
在 dn2 创建 dict_order_type 表。 在Mycat、dn1、dn2中查询表数据
常用的分片规则
1 取模:此规则为对分片字段求摸运算。也是水平分表最常用规则。
2 分片枚举
通过在配置文件中配置可能的枚举 id,自己配置分片,本规则适用于特定的场景,比如有些业务
需要按照省份或区县来做保存,而全国省份区县固定的,这类业务使用本条规则。
修改schema.xml配置文件
修改rule.xml配置文件
areacode
hash-int
partition-hash-int.txt
1
0
mapFile 表示 算法对应文件
type 表示文件数据类型,0表示整数类型,其他表示String
defaultNode表示 默认节点:小于 0 表示不设置默认节点,大于等于 0 表示设置默认节点,设置默认节点如果碰到不识别的枚举值,就让它路由到默认节点,如不设置不识别就报错
修改partition-hash-int.txt配置文件
110=0
120=1
在mycat上建表,并且插入数据
CREATE TABLE orders_ware_info
(
`id` INT AUTO_INCREMENT comment '编号',
`order_id` INT comment '订单编号',
`address` VARCHAR(200) comment '地址',
`areacode` VARCHAR(20) comment '区域编号',
PRIMARY KEY(id)
);
INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (1,1,'北京','110');
INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (2,2,'天津','120');
INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (3,2,'上海','130');
在Mycat、dn1、dn2可以看到数据分片效果
3 范围约定
此分片适用于,提前规划好分片字段某个范围属于哪个分片。
修改schema.xml配置文件
修改rule.xml配置文件
order_id
rang-long
autopartition-long.txt
0
修改autopartition-long.txt配置文件,以order_id区分,0-100的数据放0节点,101-200放1节点
0-100=0
101-200=1
mycat下,建表 插入数据 验证即可
CREATE TABLE payment_info
(
`id` INT AUTO_INCREMENT comment '编号',
`order_id` INT comment '订单编号',
`payment_status` INT comment '支付状态',
PRIMARY KEY(id)
);
INSERT INTO payment_info (id,order_id,payment_status) VALUES (1,101,0);
INSERT INTO payment_info (id,order_id,payment_status) VALUES (2,102,1);
INSERT INTO payment_info (id,order_id ,payment_status) VALUES (3,103,0);
INSERT INTO payment_info (id,order_id,payment_status) VALUES (4,104,1);
4 按日期分片
此规则为按天分片。设定时间格式、范围
修改schema.xml配置文件
修改rule.xml配置文件
login_date
shardingByDate
yyyy-MM-dd
2019-01-01
2019-01-04
2
columns:分片字段,algorithm:分片函数
dateFormat :日期格式
sBeginDate :开始日期
sEndDate:结束日期,则代表数据达到了这个日期的分片后循环从开始分片插入
sPartionDay :分区天数,即默认从开始日期算起,分隔 2 天一个分区
重启mycat 建表 加数据 ,验证
CREATE TABLE login_info
(
`id` INT AUTO_INCREMENT comment '编号',
`user_id` INT comment '用户编号',
`login_date` date comment '登录日期',
PRIMARY KEY(id)
);
INSERT INTO login_info(id,user_id,login_date) VALUES (1,101,'2019-01-01');
INSERT INTO login_info(id,user_id,login_date) VALUES (2,102,'2019-01-02');
INSERT INTO login_info(id,user_id,login_date) VALUES (3,103,'2019-01-03');
INSERT INTO login_info(id,user_id,login_date) VALUES (4,104,'2019-01-04');
INSERT INTO login_info(id,user_id,login_date) VALUES (5,103,'2019-01-05');
INSERT INTO login_info(id,user_id,login_date) VALUES (6,104,'2019-01-06');
5 全局序列
在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。为此,Mycat 提供了全局 sequence,并且提供了包含本地配置和数据库配置等多种实现方式
- 方式1 本地文件
此方式 Mycat 将 sequence 配置到文件中,当使用到 sequence 中的配置后,Mycat 会更下classpath 中的 sequence_conf.properties 文件中 sequence 当前的值。
① 优点:本地加载,读取速度较快
② 缺点:抗风险能力差,Mycat 所在主机宕机后,无法读取本地文件。 - 方式2 数据库方式
利用数据库一个表 来进行计数累加。但是并不是每次生成序列都读写数据库,这样效率太低。Mycat 会预加载一部分号段到 Mycat 的内存中,这样大部分读写序列都是在内存中完成的。如果内存中的号段用完了 Mycat 会再向数据库要一次。 - 方式3 时间戳方式
全局序列ID= 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加) 换算成十进制为 18 位数的long 类型,每毫秒可以并发 12 位二进制的累加。
① 优点:配置简单
② 缺点:18 位 ID 过长 - 方式4 自主生成全局序列 推荐这种方式比较好
① 根据业务逻辑组合
② 可以利用 redis 的单线程原子性 incr 来生成序列。