mycat:数据库中间件
由于mycat的团队过于恶心,所以不建议使用mycat,可以转为使用shardding JDBC
为什么要用mycat? mycat解决一下三个问题
- 解决了Java应用与数据紧耦合的问题
- 缓解了高访问量高并发的压力
- 解决了读写请求数据不一致的问题
mycat主要做的事
-
读写分离
-
数据分片:垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)
-
多数据源整合
mycat原理
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主从复制 (多主多从)。这里搭建二主二从
我们这里是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主从配置
<?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="testdb" />
<dataHost name="host1" maxCon="1000" minCon="10" balance="1" 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.107.135:3306" user="mysqlu1" password="12345678">
<!-- can have multi read hosts -->
<readHost host="hostS1" url="192.168.107.136:3306" user="mysqlu1" password="12345678" />
</writeHost>
<writeHost host="hostM2" url="192.168.107.129:3306" user="mysqlu1" password="12345678">
<!-- can have multi read hosts -->
<readHost host="hostS2" url="192.168.107.130:3306" user="mysqlu1" password="12345678" />
</writeHost>
</dataHost>
</mycat:schema>
相关配置解释
# 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服务器,数据依然可以插入,依然可以查询数据,主服务器重启后,该台服务器依然会同步最新数据,只是现在这个服务器的角色肯定是主写服务器的备用服务器,不管它之前是主写服务器还是主服务器的备用服务器。
垂直拆分-分库
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 这里schema配置的意思是 操作customer表的时候 那么就走dn2节点查询,否则走dn1节点 -->
<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.107.135:3306" user="mysqlu1" password="12345678">
</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.107.129:3306" user="mysqlu1" password="12345678">
</writeHost>
</dataHost>
</mycat:schema>
我们现在先在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控制台,可以看到相应的表已经建立在相应的数据上了,并且可以在相应的数据库上新增数据 ,查询数据等等,就这样分库成功了。
水平拆分-分表
关于分表策略: 对于我们orders表来说 如果以id来分表的话,查询订单注重时效,历史订单被查询的次数少,如此分片会造成一个节点访问多,一个访问少,不平均。如果以customer_id来分的话,根据客户id去分,两个节点访问平均,一个客户的所有订单都在同一个节点,数据相对平均。其他典型分片策略有根据时间来分等等,总之分片是很重要的东西。
修改schema.xml,增加以下配置
<!-- 这里配置的orders表是进行水平拆分的,orders的数据将会放到dn1和dn2两个节点上,数据的分配策略是 mod_rule -->
<table name="orders" dataNode="dn1,dn2" rule="mod_rule" ></table>
schema.xml全配置文件如下
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 这里schema配置的意思是 操作customer表的时候 那么就走dn2节点查询,否则走dn1节点 -->
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
<table name="customer" dataNode="dn2" ></table>
<!-- 这里配置的orders表是进行水平拆分的,orders的数据将会放到dn1和dn2两个节点上,数据的分配策略是 mod_rule -->
<table name="orders" dataNode="dn1,dn2" rule="mod_rule" ></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.107.135:3306" user="mysqlu1" password="12345678">
</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.107.129:3306" user="mysqlu1" password="12345678">
</writeHost>
</dataHost>
</mycat:schema>
修改配置文件rule.xml,在rule配置文件里新增分片规则mod_rule,并指定规则适用字段为customer_id,还要选择分片算法mod-long(对字段求模运算),customer_id对两个节点求模,根据结果分片
<tableRule name="mod_rule">
<rule>
<columns>customer_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
</function>
现在我们在数据节点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查询
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的关系来分配。
<table name="orders" dataNode="dn1,dn2" rule="mod_rule" >
<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
</table>
在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 全局表
<table name="dict_order_type" dataNode="dn1,dn2" type="global" ></table>
在 dn2 创建 dict_order_type 表。 在Mycat、dn1、dn2中查询表数据
常用的分片规则
1 取模:此规则为对分片字段求摸运算。也是水平分表最常用规则。
2 分片枚举
通过在配置文件中配置可能的枚举 id,自己配置分片,本规则适用于特定的场景,比如有些业务
需要按照省份或区县来做保存,而全国省份区县固定的,这类业务使用本条规则。
修改schema.xml配置文件
<table name="orders_ware_info" dataNode="dn1,dn2" rule="sharding_by_intfile" ></table>
修改rule.xml配置文件
<tableRule name="sharding_by_intfile">
<rule>
<columns>areacode</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txt</property>
<property name="type">1</property>
<property name="defaultNode">0</property>
</function>
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配置文件
<table name="payment_info" dataNode="dn1,dn2" rule="auto_sharding_long" ></table>
修改rule.xml配置文件
<tableRule name="auto_sharding_long">
<rule>
<columns>order_id</columns>
<algorithm>rang-long</algorithm>
</rule>
</tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txt</property>
<property name="defaultNode">0</property>
</function>
修改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配置文件
<table name="login_info" dataNode="dn1,dn2" rule="sharding_by_date" ></table>
修改rule.xml配置文件
<tableRule name="sharding_by_date">
<rule>
<columns>login_date</columns>
<algorithm>shardingByDate</algorithm>
</rule>
</tableRule>
<function name="shardingByDate" class="io.mycat.route.function.PartitionByDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2019-01-01</property>
<property name="sEndDate">2019-01-04</property>
<property name="sPartionDay">2</property>
</function>
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 来生成序列。