MyCat分片

垂直拆分

概述

image-20210528141341546

一种是按照不同的表(或者Schema)来切分到不同的数据库(主机)之上,这种切分可以称之为数据的垂直(纵向)切分。

案例场景

1575725341210

在业务系统中,有以下表结构,但是由于用户与订单每天都会产生大量的数据,单台服务器的数据存储及处理能力是有限的,可以对数据库表进行拆分,原有的数据库表:

1575726526012

准备工作

  1. 准备三台数据库实例
1
2
3
192.168.192.157
192.168.192.158
192.168.192.159
  1. 在三台数据库实例中建库建表,即将准备好的三个SQL脚本, 分别导入到三台MySQL实例中 。
image-20210523093331110

首先上传文件:put ‘’ 上传文件路径 ‘’ ,如下:

1
put D:/tmp/user.sql

之后登录MySQL数据库之后, 使用source命令导入 :

1575730157557

schema.xml的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

<schema name="ITCAST_DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="tb_areas_city" dataNode="dn1" primaryKey="id" />
<table name="tb_areas_provinces" dataNode="dn1" primaryKey="id" />
<table name="tb_areas_region" dataNode="dn1" primaryKey="id" />
<table name="tb_user" dataNode="dn1" primaryKey="id" />
<table name="tb_user_address" dataNode="dn1" primaryKey="id" />

<table name="tb_goods_base" dataNode="dn2" primaryKey="id" />
<table name="tb_goods_desc" dataNode="dn2" primaryKey="goods_id" />
<table name="tb_goods_item_cat" dataNode="dn2" primaryKey="id" />

<table name="tb_order_item" dataNode="dn3" primaryKey="id" />
<table name="tb_order_master" dataNode="dn3" primaryKey="order_id" />
<table name="tb_order_pay_log" dataNode="dn3" primaryKey="out_trade_no" />
</schema>


<dataNode name="dn1" dataHost="host1" database="user_db" />
<dataNode name="dn2" dataHost="host2" database="goods_db" />
<dataNode name="dn3" dataHost="host3" database="order_db" />


<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.192.157:3306" user="root" password="123456"></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>
<writeHost host="hostM2" url="192.168.192.158:3306" user="root" password="123456"></writeHost>
</dataHost>

<dataHost name="host3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM3" url="192.168.192.159:3306" user="root" password="123456"></writeHost>
</dataHost>

</mycat:schema>

server.xml的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">ITCAST_DB</property>
</user>

<user name="test">
<property name="password">123456</property>
<property name="schemas">ITCAST_DB</property>
</user>

<user name="user">
<property name="password">123456</property>
<property name="schemas">ITCAST_DB</property>
<property name="readOnly">true</property>
</user>

测试

  1. 查询数据(重新登录MyCat)

    • 首先查看MyCat逻辑库以及逻辑表是否创建:

      1
      2
      3
      show databases;
      use ITCAST_DB;
      show tables;

      image-20210523101000454

    • 之后在三个数据库节点上分别查询数据是否存在,经过查询可知,三台数据库节点上数据均存在。

      1
      2
      3
      select * from tb_goods_base;
      select * from tb_user;
      select * from tb_order_master;

      image-20210523100436778

  2. 插入数据(直接操作MyCat执行插入)

1
insert  into tb_user_address(id,user_id,province_id,city_id,town_id,mobile,address,contact,is_default,notes,create_date,alias) values (null,'java00001',NULL,NULL,NULL,'13900112222','钟楼','张三','0',NULL,NULL,NULL)
1
insert  into tb_order_item(id,item_id,goods_id,order_id,title,price,num,total_fee,pic_path,seller_id) values (null,19,149187842867954,3,'3G 6','1.00',5,'5.00',NULL,'qiandu')

image-20210523095853063

image-20210523095946558

  1. 测试跨分片的查询
1
2
SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o , tb_areas_provinces p , tb_areas_city c , tb_areas_region r
WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;

当运行上述的SQL语句时,MyCat会报错,原因是因为当前SQL语句涉及到跨域的join操作,要解决跨库操作就需要将区域表定义为全局表。

image-20210523100155075

全局表配置

  1. 将数据节点user_db中的关联的字典表 tb_areas_provinces,tb_areas_city , tb_areas_region中的数据备份 :
1
2
3
mysqldump -uroot -pitcast user_db tb_areas_provinces  > provinces;
mysqldump -uroot -pitcast user_db tb_areas_city > city;
mysqldump -uroot -pitcast user_db tb_areas_region > region;

image-20210523100742655

  1. 将备份的表结构及数据信息,远程同步到其他两个数据节点的数据库中:
1
2
3
4
5
6
7
8
scp city root@192.168.192.158:/root
scp city root@192.168.192.159:/root

scp provinces root@192.168.192.158:/root
scp provinces root@192.168.192.159:/root

scp region root@192.168.192.158:/root
scp region root@192.168.192.159:/root

image-20210523101207804

image-20210523101315455

  1. 在三台节点中分别导入到对应的数据库中:
1
2
3
mysql -uroot -p goods_db < city
mysql -uroot -p goods_db < provinces
mysql -uroot -p goods_db < region
  1. MyCat逻辑表中的配置:
1
2
3
<table name="tb_areas_city" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_provinces" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_region" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
  1. 重启MyCat:
1
bin/mycat restart
  1. 测试:再次执行相同的连接查询,是可以正常查询出对应的数据的 :
1
2
SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o , tb_areas_provinces p , tb_areas_city c , tb_areas_region r
WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;

image-20210523101948201

而当我们对Mycat全局表进行增删改的操作时,其他节点主机上的后端MySQL数据库中的数据是会同步变化的。

水平拆分

概述

根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分。

1577151764698

案例场景

1577152000136

在业务系统中, 有一张表(日志表), 业务系统每天都会产生大量的日志数据 , 单台服务器的数据存储及处理能力是有限的, 可以对数据库表进行拆分, 原有的数据库表拆分成以下表 :

1577152168606

准备工作

  1. 准备三台数据库实例
1
2
3
192.168.192.157
192.168.192.158
192.168.192.159
  1. 在三台数据库实例中创建数据
1
create database log_db DEFAULT CHARACTER SET utf8;

schema.xml的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

<schema name="LOG_DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="tb_log" dataNode="dn1,dn2,dn3" primaryKey="id" rule="mod-long" />
</schema>

<dataNode name="dn1" dataHost="host1" database="log_db" />
<dataNode name="dn2" dataHost="host2" database="log_db" />
<dataNode name="dn3" dataHost="host3" database="log_db" />


<dataHost name="host1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.192.157:3306" user="root" password="123456"></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>
<writeHost host="hostM2" url="192.168.192.158:3306" user="root" password="123456"></writeHost>
</dataHost>

<dataHost name="host3" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM3" url="192.168.192.159:3306" user="root" password="123456"></writeHost>
</dataHost>

</mycat:schema>

server.xml的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">LOG_DB</property>
</user>

<user name="test">
<property name="password">123456</property>
<property name="schemas">LOG_DB</property>
</user>

<user name="user">
<property name="password">123456</property>
<property name="schemas">LOG_DB</property>
<property name="readOnly">true</property>
</user>

测试

  1. 重启MyCat,查看逻辑表与逻辑库:

    image-20210523112335350

  2. 在MyCat数据库中执行建表语句,之后可以在三台数据库节点看到已经生成的tb_log表;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE `tb_log` (
`id` bigint(20) NOT NULL COMMENT 'ID',
`model_name` varchar(200) DEFAULT NULL COMMENT '模块名',
`model_value` varchar(200) DEFAULT NULL COMMENT '模块值',
`return_value` varchar(200) DEFAULT NULL COMMENT '返回值',
`return_class` varchar(200) DEFAULT NULL COMMENT '返回值类型',
`operate_user` varchar(20) DEFAULT NULL COMMENT '操作用户',
`operate_time` varchar(20) DEFAULT NULL COMMENT '操作时间',
`param_and_value` varchar(500) DEFAULT NULL COMMENT '请求参数名及参数值',
`operate_class` varchar(200) DEFAULT NULL COMMENT '操作类',
`operate_method` varchar(200) DEFAULT NULL COMMENT '操作方法',
`cost_time` bigint(20) DEFAULT NULL COMMENT '执行方法耗时, 单位 ms',
`source` int(1) DEFAULT NULL COMMENT '来源 : 1 PC , 2 Android , 3 IOS',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  1. MyCat中插入数据,并查询数据:
1
2
3
4
5
6
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('1','user','insert','success','java.lang.String','10001','2020-02-26 18:12:28','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','insert','10',1);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('2','user','insert','success','java.lang.String','10001','2020-02-26 18:12:27','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','insert','23',1);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('3','user','update','success','java.lang.String','10001','2020-02-26 18:16:45','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','update','34',1);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('4','user','update','success','java.lang.String','10001','2020-02-26 18:16:45','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','update','13',2);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('5','user','insert','success','java.lang.String','10001','2020-02-26 18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.controller.UserController','insert','29',3);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('6','user','find','success','java.lang.String','10001','2020-02-26 18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.controller.UserController','find','29',2);

image-20210523112730316

  1. 根据分片规则rule=”mod-long”可知:

    image-20210523113047041

在数据库节点157上进行查询:

image-20210523113219503

分片规则

MyCat的分片规则配置在conf目录下的rule.xml文件中定义 。

环境准备 :

  1. schema.xml中的内容做好备份,并配置逻辑库:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<schema name="PARTITION_DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="" dataNode="dn1,dn2,dn3" rule=""/>
</schema>


<dataNode name="dn1" dataHost="host1" database="partition_db" />
<dataNode name="dn2" dataHost="host2" database="partition_db" />
<dataNode name="dn3" dataHost="host3" database="partition_db" />


<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.192.157:3306" user="root" password="123456"></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>
<writeHost host="hostM2" url="192.168.192.158:3306" user="root" password="123456"></writeHost>
</dataHost>

<dataHost name="host3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM3" url="192.168.192.159:3306" user="root" password="123456"></writeHost>
</dataHost>
  1. 在MySQL的三个节点的数据库中,创建数据库partition_db:
1
create database partition_db DEFAULT CHARACTER SET utf8mb4;

注意:下面对分片规则的测试,每次修改测试都需要重启 MyCat 服务器。

取模分片

1
2
3
4
5
6
7
8
9
10
<tableRule name="mod-long">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">3</property>
</function>

配置说明 :

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
count 数据节点的数量

范围分片

根据指定的字段及其配置的范围与数据节点的对应情况, 来决定该数据属于哪一个分片, 配置如下:

1
2
3
4
5
6
7
8
9
10
11
<tableRule name="auto-sharding-long">
<rule>
<columns>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 配置如下:

1
2
3
4
5
6
7
# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2

#含义为 : 0 - 500 万之间的值 , 存储在0号数据节点 ; 500万 - 1000万之间的数据存储在1号数据节点 ; 1000万 - 1500 万的数据节点存储在2号节点 。

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
mapFile 对应的外部配置文件
type 默认值为0 ,0 表示Integer , 1 表示String
defaultNode 默认节点
默认节点的所用:枚举分片时,如果碰到不识别的枚举值, 就让它路由到默认节点 ; 如果没有默认值,碰到不识别的则报错 。

测试:

配置:

1
<table name="tb_log" dataNode="dn1,dn2,dn3" rule="auto-sharding-long"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1. 创建表
CREATE TABLE `tb_log` (
id bigint(20) NOT NULL COMMENT 'ID',
operateuser varchar(200) DEFAULT NULL COMMENT '姓名',
operation int(2) DEFAULT NULL COMMENT '1: insert, 2: delete, 3: update , 4: select',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


2. 插入数据
insert into tb_log (id,operateuser ,operation) values(1,'Tom',1);
insert into tb_log (id,operateuser ,operation) values(2,'Cat',2);
insert into tb_log (id,operateuser ,operation) values(3,'Rose',3);
insert into tb_log (id,operateuser ,operation) values(4,'Coco',2);
insert into tb_log (id,operateuser ,operation) values(5,'Lily',1);
#上面数据会被插入到157节点上

insert into tb_log (id,operateuser ,operation) values(5000001,'Tom',1);
#这条数据被插入到158节点上

insert into tb_log (id,operateuser ,operation) values(10000001,'Tom',1);
#这条数据被插入到159节点上

insert into tb_log (id,operateuser ,operation) values(15000001,'Tom',1);
#这条数据被插入到157节点上,因为在3个节点上都没查到它的范围,因此默认插入到默认节点157

枚举分片

通过在配置文件中配置可能的枚举值,指定数据分布到不同数据节点上,本规则适用于按照省份或状态拆分数据等业务,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
<tableRule name="sharding-by-intfile">
<rule>
<columns>status</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">0</property>
<property name="defaultNode">0</property>
</function>

partition-hash-int.txt ,内容如下 :

1
2
3
1=0
2=1
3=2

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
mapFile 对应的外部配置文件
type 默认值为0,0 表示Integer , 1 表示String
defaultNode 默认节点;小于0 标识不设置默认节点 , 大于等于0代表设置默认节点 ;
默认节点的所用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点;如果没有默认值,碰到不识别的则报错 。

测试:

配置:

1
<table name="tb_user" dataNode="dn1,dn2,dn3" rule="sharding-by-enum-status"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 创建表
CREATE TABLE `tb_user` (
id bigint(20) NOT NULL COMMENT 'ID',
username varchar(200) DEFAULT NULL COMMENT '姓名',
status int(2) DEFAULT '1' COMMENT '1: 未启用, 2: 已启用, 3: 已关闭',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


2. 插入数据
insert into tb_user (id,username ,status) values(1,'Tom',1);
insert into tb_user (id,username ,status) values(2,'Cat',2);
insert into tb_user (id,username ,status) values(3,'Rose',3);
insert into tb_user (id,username ,status) values(4,'Coco',2);
insert into tb_user (id,username ,status) values(5,'Lily',1);
#status为1插入到第一个节点上,status为2插入到第二个节点上,status为3插入到第三个节点上
#当插入status为4时,找不到对应节点,会被插入到默认节点中

范围求模算法

该算法是先进行范围分片, 计算出分片组 , 再进行组内求模。

优点: 综合了范围分片和求模分片的优点。 分片组内使用求模可以保证组内的数据分布比较均匀, 分片组之间采用范围分片可以兼顾范围分片的特点。

缺点: 在数据范围是固定值(非递增值)时,存在不方便扩展的情况,例如将 dataNode Group size 从 2 扩展为 4 时,需要进行数据迁移才能完成 ; 如图所示:

image-20200110193319982

配置如下:

1
2
3
4
5
6
7
8
9
10
11
<tableRule name="auto-sharding-rang-mod">
<rule>
<columns>id</columns>
<algorithm>rang-mod</algorithm>
</rule>
</tableRule>

<function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
<property name="mapFile">autopartition-range-mod.txt</property>
<property name="defaultNode">0</property>
</function>

autopartition-range-mod.txt 配置格式 :

1
2
3
4
5
#range  start-end , data node group size
0-500M=1
500M1-2000M=2

#在上述配置文件中,等号前面的范围代表一个分片组,等号后面的数字代表该分片组所拥有的分片数量。

配置说明:

属性 描述
columns 标识将要分片的表字段名
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
mapFile 对应的外部配置文件
defaultNode 默认节点 ; 未包含以上规则的数据存储在defaultNode节点中, 节点从0开

测试:

配置:

1
<table name="tb_stu" dataNode="dn1,dn2,dn3" rule="auto-sharding-rang-mod"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1. 创建表
CREATE TABLE `tb_stu` (
id bigint(20) NOT NULL COMMENT 'ID',
username varchar(200) DEFAULT NULL COMMENT '姓名',
status int(2) DEFAULT '1' COMMENT '1: 未启用, 2: 已启用, 3: 已关闭',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


2. 插入数据
insert into tb_stu (id,username ,status) values(1,'Tom',1);
insert into tb_stu (id,username ,status) values(2,'Cat',2);
insert into tb_stu (id,username ,status) values(3,'Rose',3);
insert into tb_stu (id,username ,status) values(4,'Coco',2);
insert into tb_stu (id,username ,status) values(5,'Lily',1);
#上面执行的语句数据存储在第一个节点上(192.168.192.157

insert into tb_stu (id,username ,status) values(5000001,'Roce',1);
insert into tb_stu (id,username ,status) values(5000002,'Jexi',2);
insert into tb_stu (id,username ,status) values(5000003,'Mini',1);
#上面的id为5000002的数据由于5000002%2=0,则存储在第二个节点上(158),而id为50000015000003这两条数据(由于 5000001\50000003%2=1)存储在第三个节点上(159)。

固定分片hash算法

该算法类似于十进制的求模运算,但是为二进制的操作,例如,取 id 的二进制低 10 位 与 1111111111 进行位 & 运算。

最小值:

image-20200112180630348

最大值:

image-20200112180643493

优点: 这种策略比较灵活,可以均匀分配也可以非均匀分配,各节点的分配比例和容量大小由partitionCount和partitionLength两个参数决定。

缺点:和取模分片类似。

配置如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
<tableRule name="sharding-by-long-hash">
<rule>
<columns>id</columns>
<algorithm>func1</algorithm>
</rule>
</tableRule>

<function name="func1" class="org.opencloudb.route.function.PartitionByLong">
<property name="partitionCount">2,1</property>
<property name="partitionLength">256,512</property>
</function>

#在示例中配置的分片策略,希望将数据水平分成3份,前两份各占 25%,第三份占 50%。

配置说明:

属性 描述
columns 标识将要分片的表字段名
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
partitionCount 分片个数列表
partitionLength 分片范围列表

约束 :

  1. 分片长度 : 默认最大2^10 , 为 1024 ;

  2. count、length的数组长度必须是一致的 ;

  3. 两组数据的对应情况: (partitionCount[0]partitionLength[0])=(partitionCount[1]partitionLength[1]);

  4. 以上分为三个分区:0-255,256-511,512-1023。

测试:

配置:

1
<table name="tb_brand" dataNode="dn1,dn2,dn3" rule="sharding-by-long-hash"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. 创建表
CREATE TABLE `tb_brand` (
id int(11) NOT NULL COMMENT 'ID',
name varchar(200) DEFAULT NULL COMMENT '名称',
firstChar char(1) COMMENT '首字母', PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


2. 插入数据
insert into tb_brand (id,name ,firstChar) values(1,'七匹狼','Q');
insert into tb_brand (id,name ,firstChar) values(529,'八匹狼','B');
insert into tb_brand (id,name ,firstChar) values(1203,'九匹狼','J');
insert into tb_brand (id,name ,firstChar) values(1205,'十匹狼','S');
insert into tb_brand (id,name ,firstChar) values(1719,'六匹狼','L');
#通过上面数据的hash值的后十位与 1111111111 进行位 & 运可知,id为1,1203,1205的数据存储在157节点上,而id为529,1719的数据 存储在159节点上,158节点上没有数据。

取模范围算法

该算法先进行取模,然后根据取模值所属范围进行分片。

优点:可以自主决定取模后数据的节点分布。

缺点:dataNode 划分节点是事先建好的,需要扩展时比较麻烦。

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
<tableRule name="sharding-by-pattern">
<rule>
<columns>id</columns>
<algorithm>sharding-by-pattern</algorithm>
</rule>
</tableRule>

<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern">
<property name="mapFile">partition-pattern.txt</property>
<property name="defaultNode">0</property>
<property name="patternValue">96</property>
</function>

partition-pattern.txt 配置如下:

1
2
3
4
5
0-32=0
33-64=1
65-96=2

#在mapFile配置文件中, 1-32即代表id%96后的分布情况。如果在1-32, 则在分片0上 ; 如果在33-64, 则在分片1上 ; 如果在65-96, 则在分片2上。

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
mapFile 对应的外部配置文件
defaultNode 默认节点 ; 如果id不是数字, 无法求模, 将分配在defaultNode上
patternValue 求模基数

测试:

配置:

1
<table name="tb_mod_range" dataNode="dn1,dn2,dn3" rule="sharding-by-pattern"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. 创建表
CREATE TABLE `tb_mod_range` (
id int(11) NOT NULL COMMENT 'ID',
name varchar(200) DEFAULT NULL COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


2. 插入数据
insert into tb_mod_range (id,name) values(1,'Test1');
insert into tb_mod_range (id,name) values(2,'Test2');
insert into tb_mod_range (id,name) values(3,'Test3');
insert into tb_mod_range (id,name) values(4,'Test4');
insert into tb_mod_range (id,name) values(5,'Test5');
#上面的数据均存储在第一个节点上(157

insert into tb_mod_range (id,name) values(34,'Test34');
insert into tb_mod_range (id,name) values(36,'Test36');
#上面两条数据(34/36%9633-64之间)存储在第二个节点上(158

insert into tb_mod_range (id,name) values(67,'Test67');
#上面两条数据(67/36%9665-96之间)存储在第三个节点上(159

注意 : 取模范围算法只能针对于数字类型进行取模运算;如果是字符串则无法进行取模分片。

字符串hash求模范围算法

与取模范围算法类似,该算法支持数值、符号、字母取模,首先截取长度为 prefixLength 的子串,在对子串中每一个字符的 ASCII 码求和,然后对求和值进行取模运算(sum%patternValue),就可以计算出子串的分片数。

优点:可以自主决定取模后数据的节点分布。

缺点:dataNode 划分节点是事先建好的,需要扩展时比较麻烦。

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
<tableRule name="sharding-by-prefixpattern">
<rule>
<columns>username</columns>
<algorithm>sharding-by-prefixpattern</algorithm>
</rule>
</tableRule>

<function name="sharding-by-prefixpattern" class="io.mycat.route.function.PartitionByPrefixPattern">
<property name="mapFile">partition-prefixpattern.txt</property>
<property name="prefixLength">5</property>
<property name="patternValue">96</property>
</function>

partition-prefixpattern.txt 配置如下:

1
2
3
4
5
6
7
8
9
# range start-end ,data node index
# ASCII
# 48-57=0-9
# 64、65-90=@、A-Z
# 97-122=a-z
###### first host configuration
0-32=0
33-64=1
65-96=2

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
mapFile 对应的外部配置文件
prefixLength 截取的位数; 将该字段获取前prefixLength位所有ASCII码的和, 进行求模sum%patternValue ,获取的值,在通配范围内的即分片数 。
patternValue 求模基数

如 :

1
2
3
4
5
6
7
8
9
10
11
12
13
字符串 :
gf89f9a

截取字符串的前5位进行ASCII的累加运算 :
g - 103
f - 102
8 - 56
9 - 57
f - 102

sum求和 : 103 + 102 + + 56 + 57 + 102 = 420
求模 : 420 % 96 = 36

附录 ASCII码表 :

1577267028771

测试:

配置:

1
<table name="tb_u" dataNode="dn1,dn2,dn3" rule="sharding-by-prefixpattern"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 创建表
CREATE TABLE `tb_u` (
username varchar(50) NOT NULL COMMENT '用户名',
age int(11) default 0 COMMENT '年龄',
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


2. 插入数据
insert into tb_u (username,age) values('Test100001',18);
insert into tb_u (username,age) values('Test200001',20);
insert into tb_u (username,age) values('Test300001',19);
insert into tb_u (username,age) values('Test400001',25);
insert into tb_u (username,age) values('Test500001',22);
#上面五条记录全部存储在第三个节点中(159)。

应用指定算法

由运行阶段由应用自主决定路由到那个分片,直接根据字符子串(必须是数字)计算分片号,配置如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
<tableRule name="sharding-by-substring">
<rule>
<columns>id</columns>
<algorithm>sharding-by-substring</algorithm>
</rule>
</tableRule>

<function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString">
<property name="startIndex">0</property> <!-- zero-based -->
<property name="size">2</property>
<property name="partitionCount">3</property>
<property name="defaultPartition">0</property>
</function>

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
startIndex 字符子串起始索引
size 字符长度
partitionCount 分区(分片)数量
defaultPartition 默认分片(在分片数量定义时, 字符标示的分片编号不在分片数量内时,使用默认分片)

示例说明 :

id=05-100000002,在此配置中代表根据id中从 startIndex=0,开始,截取siz=2位数字即05,05就是获取的分区,如果没传默认分配到defaultPartition 。

测试:

配置:

1
<table name="tb_app" dataNode="dn1,dn2,dn3" rule="sharding-by-substring"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 创建表
CREATE TABLE `tb_app` (
id varchar(10) NOT NULL COMMENT 'ID',
name varchar(200) DEFAULT NULL COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


2. 插入数据
insert into tb_app (id,name) values('00-00001','Testx00001');
insert into tb_app (id,name) values('01-00001','Test100001');
insert into tb_app (id,name) values('01-00002','Test200001');
insert into tb_app (id,name) values('02-00001','Test300001');
insert into tb_app (id,name) values('02-00002','TesT400001');
#第一个数据存储在第一个节点(157),第二、三个数据存储在第二个节点(158),第四、五个数据存储在第三个节点(159)。

字符串hash解析算法

截取字符串中的指定位置的子字符串, 进行hash算法, 算出分片 , 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
<tableRule name="sharding-by-stringhash">
<rule>
<columns>name</columns>
<algorithm>sharding-by-stringhash</algorithm>
</rule>
</tableRule>

<function name="sharding-by-stringhash" class="io.mycat.route.function.PartitionByString">
<property name="partitionLength">512</property> <!-- zero-based -->
<property name="partitionCount">2</property>
<property name="hashSlice">0:2</property>
</function>

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
partitionLength hash求模基数 ; length*count=1024 (出于性能考虑)
partitionCount 分区数
hashSlice hash运算位 , 根据子字符串的hash运算 ; 0 代表 str.length() , -1 代表 str.length()-1 , 大于0只代表数字自身 ; 可以理解为substring(start,end),start为0则只表示0。

测试:

配置:

1
<table name="tb_strhash" dataNode="dn1,dn2,dn3" rule="sharding-by-stringhash"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 创建表
create table tb_strhash(
name varchar(20) primary key,
content varchar(100)
)engine=InnoDB DEFAULT CHARSET=utf8mb4;

2. 插入数据
INSERT INTO tb_strhash (name,content) VALUES('T1001', UUID());
INSERT INTO tb_strhash (name,content) VALUES('ROSE', UUID());
INSERT INTO tb_strhash (name,content) VALUES('JERRY', UUID());
INSERT INTO tb_strhash (name,content) VALUES('CRISTINA', UUID());
INSERT INTO tb_strhash (name,content) VALUES('TOMCAT', UUID());
#第三、四个数据存储在第一个节点,其他三条数据存储在第二个节点中。

原理:

image-20200112234530612

一致性hash算法

一致性hash算法有效的解决了分布式数据的扩容问题 , 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>

<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property>
<property name="count">3</property><!-- -->
<property name="virtualBucketTimes">160</property>
<!-- <property name="weightMapFile">weightMapFile</property> -->
<!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property> -->
</function>

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
seed 创建murmur_hash对象的种子,默认0
count 要分片的数据库节点数量,必须指定,否则没法分片
virtualBucketTimes 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍;virtualBucketTimes*count就是虚拟结点数量 ;
weightMapFile 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替
bucketMapPath 用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西

测试:

配置:

1
<table name="tb_order" dataNode="dn1,dn2,dn3" rule="sharding-by-murmur"/>

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1. 创建表
create table tb_order(
id int(11) primary key,
money int(11),
content varchar(200)
)engine=InnoDB ;

2. 插入数据
INSERT INTO tb_order (id,money,content) VALUES(1, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(212, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(312, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(412, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(534, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(621, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(754563, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(8123, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(91213, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(23232, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(112321, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(21221, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(112132, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(12132, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(124321, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(212132, 100 , UUID());
#在大数据情况下,数据会被均匀的分配到各个节点。

日期分片算法

按照日期来分片:

1
2
3
4
5
6
7
8
9
10
11
12
13
<tableRule name="sharding-by-date">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-date</algorithm>
</rule>
</tableRule>

<function name="sharding-by-date" class="io.mycat.route.function.PartitionByDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2020-01-01</property>
<property name="sEndDate">2021-12-31</property>
<property name="sPartionDay">10</property>
</function>

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
dateFormat 日期格式
sBeginDate 开始日期
sEndDate 结束日期,如果配置了结束日期,则代码数据到达了这个日期的分片后,会重复从开始分片插入
sPartionDay 分区天数,默认值 10 ,从开始日期算起,每个10天一个分区

注意:配置规则的表的 dataNode 的分片,必须和分片规则数量一致,例如 2021-01-01 到 2021-12-31 ,每10天一个分片,一共需要37个分片。

单月小时算法

单月内按照小时拆分,最小粒度是小时,一天最多可以有24个分片,最小1个分片,下个月从头开始循环,每个月末需要手动清理数据。

配置如下:

1
2
3
4
5
6
7
8
9
10
<tableRule name="sharding-by-hour">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-hour</algorithm>
</rule>
</tableRule>

<function name="sharding-by-hour" class="io.mycat.route.function.LatestMonthPartion">
<property name="splitOneDay">24</property>
</function>

配置说明:

属性 描述
columns 标识将要分片的表字段 ; 字符串类型(yyyymmddHH), 需要符合JAVA标准
algorithm 指定分片函数与function的对应关系
splitOneDay 一天切分的分片数

自然月分片算法

使用场景为按照月份列分区,每个自然月为一个分片,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
<tableRule name="sharding-by-month">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-month</algorithm>
</rule>
</tableRule>

<function name="sharding-by-month" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2021-01-01</property>
<property name="sEndDate">2021-12-31</property>
</function>

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
dateFormat 日期格式
sBeginDate 开始日期
sEndDate 结束日期,如果配置了结束日期,则代码数据到达了这个日期的分片后,会重复从开始分片插入

日期范围hash算法

其思想和范围求模分片一样,先根据日期进行范围分片求出分片组,再根据时间hash使得短期内数据分布的更均匀。

优点 : 可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题。

注意 : 要求日期格式尽量精确些,不然达不到局部均匀的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
<tableRule name="range-date-hash">
<rule>
<columns>create_time</columns>
<algorithm>range-date-hash</algorithm>
</rule>
</tableRule>

<function name="range-date-hash" class="io.mycat.route.function.PartitionByRangeDateHash">
<property name="dateFormat">yyyy-MM-dd HH:mm:ss</property>
<property name="sBeginDate">2021-01-01 00:00:00</property>
<property name="groupPartionSize">6</property>
<property name="sPartionDay">10</property>
</function>

配置说明:

属性 描述
columns 标识将要分片的表字段
algorithm 指定分片函数与function的对应关系
class 指定该分片算法对应的类
dateFormat 日期格式 , 符合Java标准
sBeginDate 开始日期 , 与 dateFormat指定的格式一致
groupPartionSize 每组的分片数量
sPartionDay 代表多少天为一组

MyCat高级

MyCat 性能监控

MyCat-web简介

Mycat-web 是 Mycat 可视化运维的管理和监控平台,弥补了 Mycat 在监控上的空白,帮 Mycat 分担统计任务和配置管理任务。Mycat-web 引入了 ZooKeeper 作为配置中心,可以管理多个节点。Mycat-web 主要管理和监控 Mycat 的流量、连接、活动线程和内存等,具备 IP 白名单、邮件告警等模块,还可以统计 SQL 并分析慢 SQL 和高频 SQL 等。为优化 SQL 提供依据。

1577358192118

MyCat-web下载

下载地址 : http://dl.mycat.io/

1577369790112

Mycat-web安装配置

安装
  1. 安装Zookeeper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
A. 上传安装包 
alt + p -----> put D:\tmp\zookeeper-3.4.11.tar.gz

B. 解压
tar -zxvf zookeeper-3.4.11.tar.gz -C /usr/local/

C. 创建数据存放目录
mkdir data

D. 修改配置文件名称并配置
mv zoo_sample.cfg zoo.cfg

E. 配置数据存放目录
dataDir=/usr/local/zookeeper-3.4.11/data

F. 启动Zookeeper
bin/zkServer.sh start
  1. 安装Mycat-web
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
A. 上传安装包 
alt + p --------> put D:\tmp\Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz

B. 解压
tar -zxvf Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz -C /usr/local/

C. 目录介绍
drwxr-xr-x. 2 root root 4096 Oct 19 2015 etc ----> jetty配置文件
drwxr-xr-x. 3 root root 4096 Oct 19 2015 lib ----> 依赖jar包
drwxr-xr-x. 7 root root 4096 Jan 1 2017 mycat-web ----> mycat-web项目
-rwxr-xr-x. 1 root root 116 Oct 19 2015 readme.txt
-rwxr-xr-x. 1 root root 17125 Oct 19 2015 start.jar ----> 启动jar
-rwxr-xr-x. 1 root root 381 Oct 19 2015 start.sh ----> linux启动脚本

D. 启动
sh start.sh

E. 访问
http://192.168.192.147:8082/mycat

如果Zookeeper与Mycat-web不在同一台服务器上,需要设置Zookeeper的地址,在/usr/local/mycat-web/mycat-web/WEB-INF/classes/mycat.properties文件中配置:

1577370960657

配置
image-20210529204135052

image-20210529204251314

Mycat-web之MyCat性能监控

在 Mycat-web 上可以进行 Mycat 性能监控,例如:内存分析、流量分析、连接分析、活动线程分析等等。 如下图:

  1. MyCat内存分析:MyCat的内存分析,反映了当前的内存使用情况与历史时间段的峰值、平均值。

image-20210529204428768

  1. MyCat流量分析:MyCat流量分析统计了历史时间段的流量峰值、当前值、平均值,是MyCat数据传输的重要指标, In代表输入, Out代表输出。

1577373861622

  1. MyCat连接分析:MyCat连接分析,反映了MyCat的连接数 。

1577374030291

  1. MyCat TPS分析:MyCat TPS 是并发性能的重要参数指标,指系统在每秒内能够处理的请求数量。 MyCat TPS的值越高,代表MyCat单位时间内能够处理的请求就越多,并发能力也就越高。

1577374126073

  1. MyCat活动线程分析反映了MyCat线程的活动情况。

  2. MyCat缓存队列分析,反映了当前在缓存队列中的任务数量。

Mycat-web之MySQL性能监控指标

  1. MySQL配置

image-20210529204711577

  1. MySQL监控指标:可以通过MySQL服务监控检测每一个MySQL节点的运行状态,包含缓存命中率 、增删改查比例、流量统计、慢查询比例、线程、临时表等相关性能数据。

1577374588708

Mycat-web之SQL监控

  1. SQL 统计

1577374982024

  1. SQL表分析

1577375016852

  1. SQL监控

1577375043787

  1. 高频SQL

1577375072881

  1. 慢SQL统计

1577375100383

  1. SQL解析

1577375162928

MyCat 读写分离

MySQL主从复制原理

复制是指将主数据库的DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。MySQL支持一台主库同时向多台从库进行复制,从库同时也可以作为其他从服务器的主库,实现链状复制。

MySQL主从复制的原理如下 :

image-20200103093716416

从上图来看,复制分成三步:

  • Master 主库在事务提交时,会把数据变更作为时间 Events 记录在二进制日志文件 Binlog 中;

  • 主库推送二进制日志文件 Binlog 中的日志事件到从库的中继日志 Relay Log ;

  • slave重做中继日志中的事件,将改变反映它自己的数据。

MySQL 复制的优点:

  • 主库出现问题,可以快速切换到从库提供服务。
  • 可以在从库上执行查询操作,从主库中更新,实现读写分离,降低主库的访问压力。
  • 可以在从库中执行备份,以避免备份期间影响主库的服务。

MySQL一主一从搭建

准备的两台机器:

MySQL IP 端口号
Master 192.168.192.157 3306
Slave 192.168.192.158 3306
master
  1. 在master 的配置文件(/usr/my.cnf)中,配置如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#mysql 服务ID,保证整个集群环境中唯一
server-id=1

#mysql binlog 日志的存储路径和文件名
log-bin=/var/lib/mysql/mysqlbin

#设置logbin格式
binlog_format=STATEMENT

#是否只读,1 代表只读, 0 代表读写
read-only=0

#忽略的数据, 指不需要同步的数据库
#binlog-ignore-db=mysql

#指定同步的数据库
binlog-do-db=db01
  1. 执行完毕之后,需要重启MySQL:
1
service mysql restart ;
  1. 创建同步数据的账户,因为MySQL进行主从复制的时候需要一个同步账户(Salve节点通过这个同步账户读取Master节点上的二进制日志),并且进行授权操作:
1
2
3
grant replication slave on *.* to 'itcast'@'192.168.192.158' identified by '123456';	

flush privileges;
  1. 查看master状态:
1
show master status;

image-20200103102209631

字段含义:

1
2
3
File : 从哪个日志文件开始推送日志文件 
Position : 从哪个位置开始推送日志
Binlog_Ignore_DB : 指定不需要同步的数据库
slave
  1. 在MySQL集群的slave节点配置文件/usr/my.cnf中,配置如下内容:
1
2
3
4
5
6
7
8
#mysql服务端ID,唯一
server-id=2

#指定binlog日志
log-bin=/var/lib/mysql/mysqlbin

#启用中继日志
relay-log=mysql-relay
  1. 执行完毕之后,需要重启MySQL:
1
service mysql restart;
  1. 执行如下指令 :
1
2
#指定当前从库对应的主库的IP地址,用户名,密码,从哪个日志文件开始的那个位置开始同步推送日志。
change master to master_host= '192.168.192.157', master_user='itcast', master_password='123456', master_log_file='mysqlbin.000001', master_log_pos=413;
  1. 开启同步操作
1
2
3
start slave;

show slave status;

image-20200103144903105

  1. 停止同步操作
1
stop slave;
验证主从同步
  1. 在主库中创建数据库,创建表,并插入数据 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create database db01;

user db01;

create table user(
id int(11) not null auto_increment,
name varchar(50) not null,
sex varchar(1),
primary key (id)
)engine=innodb default charset=utf8;

insert into user(id,name,sex) values(null,'Tom','1');
insert into user(id,name,sex) values(null,'Trigger','0');
insert into user(id,name,sex) values(null,'Dawn','1');
  1. 在从库中查询数据,进行验证 :

    • 在从库中,可以查看到刚才创建的数据库:

    • 在该数据库中,查询user表中的数据:

      image-20200103103049675

MyCat一主一从读写分离

读写分离原理

image-20200103140249789

读写分离:简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。

通过MyCat即可轻易实现上述功能,不仅可以支持MySQL,也可以支持Oracle和SQL Server。MyCat控制后台数据库的读写分离,负载均衡由schema.xml文件datahost标签的balance属性控制。

读写分离配置

配置如下:

  • 检查MySQL的主从复制是否运行正常 ;

  • 修改MyCat 的conf/schema.xml 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100">
<table name="user" dataNode="dn1" primaryKey="id"/>
</schema>

<dataNode name="dn1" dataHost="localhost1" database="db01" />

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.192.157:3306" user="root" password="123456">
<readHost host="hostS1" url="192.168.192.158:3306" user="root" password="123456" />
</writeHost>
</dataHost>
</mycat:schema>
  • 修改conf/server.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
</user>

<user name="test">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
</user>

<user name="user">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
<property name="readOnly">true</property>
</user>
  • 配置完毕之后,重启MyCat服务:

    1
    bin/mycat start
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
属性含义说明:

checkSQLschema
当该值设置为true时, 如果我们执行语句"select * from test01.user ;" 语句时, MyCat则会把schema字符去掉 , 可以避免后端数据库执行时报错 ;


balance
负载均衡类型, 目前取值有4种:

balance="0" : 不开启读写分离机制 , 所有读操作都发送到当前可用的writeHost上.

balance="1" : 全部的readHost 与 stand by writeHost (备用的writeHost) 都参与select 语句的负载均衡,简而言之,就是采用双主双从模式(M1 --> S1 , M2 --> S2, 正常情况下,M2,S1,S2 都参与 select 语句的负载均衡。);

balance="2" : 所有的读写操作都随机在writeHost , readHost上分发

balance="3" : 所有的读请求随机分发到writeHost对应的readHost上执行, writeHost不负担读压力 ;balance=3 只在MyCat1.4 之后生效 .
验证读写分离

当balance的值为默认值0时,查询MyCat中的逻辑表的数据变化:

1
balance="0"

在MyCat上执行下面查询:

1
select * from user

通过日志信息发现,查询操作在主节点上执行:

image-20210529205519221

将原来balance的默认值0修改为1,并重启MyCat,查询MyCat中的逻辑表的数据变化:

1
balance="1"

在MyCat上执行查询操作,通过日志信息发现,查询操作在从节点上执行:

image-20210529205536395

之后在MyCat上执行下面的插入操作:

1
insert into user values(5,'Cat2','1');

image-20210529205549932

结论:当设置balance=1时,所有的读请求在从节点上,而插入、更新等操作走主节点。

MySQL双主双从搭建

架构

一个主机 Master1 用于处理所有写请求,它的从机 Slave1 和另一台主机 Master2 还有它的从机 Slave2 负责所有读请求。当 Master1 主机宕机后,Master2 主机负责写请求,Master1 、Master2 互为备机。架构图如下:

image-20200103170452653

双主双从配置

准备的机器如下:

编号 角色 IP地址 端口号
1 Master1 192.168.192.157 3306
2 Slave1 192.168.192.158 3306
3 Master2 192.168.192.159 3306
4 Slave2 192.168.192.160 3306

首先需要断掉之前的主从关系,执行下面两条语句:

1
2
stop salve;
reset master;

1. 双主机配置

  • Master1配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#主服务器唯一ID
server-id=1

#启用二进制日志
log-bin=mysql-bin

# 设置不要复制的数据库(可设置多个)
# binlog-ignore-db=mysql
# binlog-ignore-db=information_schema

#设置需要复制的数据库
binlog-do-db=db02
binlog-do-db=db03
binlog-do-db=db04

#设置logbin格式
binlog_format=STATEMENT

# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
  • Master2配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#主服务器唯一ID
server-id=3

#启用二进制日志
log-bin=mysql-bin

# 设置不要复制的数据库(可设置多个)
#binlog-ignore-db=mysql
#binlog-ignore-db=information_schema

#设置需要复制的数据库
binlog-do-db=db02
binlog-do-db=db03
binlog-do-db=db04

#设置logbin格式
binlog_format=STATEMENT

# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates

2. 双从机配置

  • Slave1配置:
1
2
3
4
5
#从服务器唯一ID
server-id=2

#启用中继日志
relay-log=mysql-relay
  • Salve2配置:
1
2
3
4
5
#从服务器唯一ID
server-id=4

#启用中继日志
relay-log=mysql-relay

3. 双主机、双从机重启 mysql 服务

1
service mysql restart

4. 主机从机都关闭防火墙

1
service iptables stop

5. 在两台主机上建立帐户并授权 slave

1
2
3
4
#在主机MySQL里执行授权命令
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%' IDENTIFIED BY '123456';

flush privileges;

查询Master1的状态 :

image-20200104090901765

查询Master2的状态 :

image-20200104090922386

6. 在从机上配置需要复制的主机:Slave1 复制 Master1,Slave2 复制 Master2。

slave1 指令:

1
2
3
4
CHANGE MASTER TO MASTER_HOST='192.168.192.157',
MASTER_USER='itcast',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=399;

slave2 指令:

1
2
3
4
CHANGE MASTER TO MASTER_HOST='192.168.192.159',
MASTER_USER='itcast',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=399;

7. 启动两台从服务器复制功能 , 查看主从复制的运行状态:

1
2
3
start slave;

show slave status\G;

image-20200104091917814

image-20200104091948213

8. 两个主机互相复制:Master2 复制 Master1,Master1 复制 Master2。

Master1 执行指令:

1
2
3
4
CHANGE MASTER TO MASTER_HOST='192.168.192.159',
MASTER_USER='itcast',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=399;

Master2 执行指令:

1
2
3
4
CHANGE MASTER TO MASTER_HOST='192.168.192.157',
MASTER_USER='itcast',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=399;

9. 启动两台主服务器复制功能 , 查看主从复制的运行状态:

1
2
3
start slave;

show slave status\G;

image-20200104092654432

image-20200104092741892

10. 验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
create database db03;

use db03;

create table user(
id int(11) not null auto_increment,
name varchar(50) not null,
sex varchar(1),
primary key (id)
)engine=innodb default charset=utf8;

insert into user(id,name,sex) values(null,'Tom','1');
insert into user(id,name,sex) values(null,'Trigger','0');
insert into user(id,name,sex) values(null,'Dawn','1');
#上面三条语句在157节点上执行,通过查询各个节点数据可知,上面的数据信息,全部同步到158/159/160三个节点上。

insert into user(id,name,sex) values(null,'Jack Ma','1');
insert into user(id,name,sex) values(null,'Coco','0');
insert into user(id,name,sex) values(null,'Jerry','1');
#而这里的上面三条的插入语句在159节点上执行,通过查询各个节点数据可知,上面的数据信息,也全部同步到157/158/160三个节点上。

在Master1上创建数据库:

image-20200104095232047

在Master1上创建表 :

image-20200104095521070

11. 停止从服务复制功能

1
stop slave;

12. 重新配置主从关系

1
2
stop slave;
reset master;

MyCat双主双从读写分离

配置

修改<dataHost>的 balance属性,通过此属性配置读写分离的类型 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100">
<table name="user" dataNode="dn1" primaryKey="id"/>
</schema>

<dataNode name="dn1" dataHost="localhost1" database="db03" />

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.192.157:3306" user="root" password="itcast">
<readHost host="hostS1" url="192.168.192.158:3306" user="root" password="123456" />
</writeHost>

<writeHost host="hostM2" url="192.168.192.159:3306" user="root" password="itcast">
<readHost host="hostS2" url="192.168.192.160:3306" user="root" password="123456" />
</writeHost>
</dataHost>

</mycat:schema>

writeType:

  • 0 : 写操作都转发到第1台writeHost, writeHost1,挂了会切换到writeHost2上;

  • 1 : 所有的写操作都随机地发送到配置的writeHost上 。

switchType:

  • 1:不自动切换;

  • 1:默认值,自动切换;

  • 2:表示基于MySQL的主从同步状态决定是否切换,心跳语句 : show slave status。

读写分离验证

查询数据 : select * from user;

image-20200104101106144

插入数据 : insert into user(id,name,sex) values(null,’Dawn’,’1’);

image-20200104100956216

可用性验证

关闭Master1,然后再执行写入的SQL语句 , 通过日志查询当前写入操作可知,写操作切换到Master2节点上。