MySql运维篇---009:分库分表:垂直拆分、水平拆分、通过MyCat进行分片,读写分离:一主一从、 双主双从
MySql运维篇---009:分库分表:垂直拆分、水平拆分、通过MyCat进行分片,读写分离:一主一从、 双主双从
3.分库分表
3.1 介绍
3.1.1 问题分析
使用单个数据库存储所有的数据,如果磁盘和内存和内存不足了可以增大磁盘和内存,但是对于一台服务器的磁盘和内存不可能无限制的扩张下去,它是受我们服务器的硬件影响的,如果说数据库所存储的数据还在不断增加,那么会带来数据库的性能问题。而此时前端的程序还在持续频繁的去请求数据库,那么数据库的访问压力会变得非常大。
随着互联网及移动互联网的发展,应用系统的数据量也是成指数式增长,若采用单数据库进行数据存储,存在以下性能瓶颈:
-
IO瓶颈:热点数据太多,数据库缓存不足,产生大量磁盘IO,效率较低。 请求数据太多,带宽不够,网络IO瓶颈。
- 在InnoDB存储引擎中有很大一部分的内存都分配给了缓存区,如果服务器的内存不足那么缓冲区分配的空间也会不足,缓冲区不足会造成缓冲区缓冲的数据变少了,这个时候就会产生大量的磁盘Io。
- 大量的请求过来会占用大量的网络带宽,网络io瓶颈。
-
CPU瓶颈:大量用户请求过来,用户在查询时所涉及到的排序、分组、连接查询、聚合统计等,这些SQL会耗费大量的CPU资源,请求数太多,CPU出现瓶颈。
为了解决上述问题,我们需要对数据库进行分库分表处理。
分库分表的中心思想都是将数据分散存储,使得单一数据库/表的数据量变小来缓解单一数据库的性能问题,从而达到提升数据库性能的目的。
3.1.2 拆分策略
分库分表的形式,主要是两种:垂直拆分和水平拆分。而拆分的粒度,一般又分为分库和分表,所以组成的拆分策略最终如下:
- 分库:指的是对一个数据库进行拆分,将一个数据库中的数据分散存储在多个数据库当中。
- 分表:指的是对表结构进行拆分,原来存储在一张表结构当中的数据,现在我要分散地存储在多张表结构当中
3.1.3 垂直拆分
1.垂直分库
垂直分库
:以表为依据,根据业务将不同表拆分到不同库中。
- 场景:一台数据库中存储了6张表,以表为单位进行拆分,根据业务将用户表相关的表结构拆分到一个服务器,订单相关的表结构拆分到第二台服务器中,商品相关的表结构拆分到第三台服务器中。
特点:
- 每个库的表结构都不一样。
- 每个库的数据也不一样。
- 所有库的并集是全量数据。
2.垂直分表
垂直分表
:以字段为依据,根据字段属性将不同字段拆分到不同表中。
- 场景:数据库当中有一张表,这张表的数据量特别大而且用户访问频次特别高,这样就会造成这台数据库的压力特别大。解决:可以对这张表进行拆分,以这张表的字段为依据拆分为2张表,这2张表可以位于不同的服务器当中,这样就缓解了单台数据库服务器的访问压力。
特点:
- 每个表的结构都不一样。
- 每个表的数据也不一样,一般通过一列(主键/外键)关联。
- 所有表的并集是全量数据。
3.1.4 水平拆分
1.水平分库
水平分库
:以字段为依据,按照一定策略,将一个库的数据拆分到多个库中。
- 场景:单台数据库此时有2张表,拆分后此时这三个数据库当中将都会存储这两张表,虽然里面的表结构一样但是所涉及到的数据不同,也就是说对于tb_order这张表来说,有一部分数据是存放在第一个数据库当中的,有一部分存储在第二个数据库中,有一部分存储在第三个数据库服务器中。
- 总结:原来一张表中的数据,现在分散地存储在这三个数据库当中的三张表中。
特点:
- 每个库的表结构都一样。
- 每个库的数据都不一样。
- 所有库的并集是全量数据。
2.水平分表
水平分表
:以字段为依据,按照一定策略,将一个表的数据拆分到多个表中。
- 他不会再将这张表结构拆分,而是对tb_order这张表的数据进行拆分,也就意味着将tb_order这张表的数据分散存储在这三台服务器中,这三台数据库服务器所存放的表结构相同,表中的数据不同。
- 总结:原来这一张表存放的数据,现在分散的存储在这3台数据库服务器中的3张表结构中,每个表结构相同数据不同。
特点:
- 每个表的表结构都一样。
- 每个表的数据都不一样。
- 所有表的并集是全量数据。
在业务系统中,为了缓解磁盘IO及CPU的性能瓶颈,到底是垂直拆分,还是水平拆分;具体是分库,还是分表,都需要根据具体的业务需求具体分析。
3.1.5 实现技术
问题:
- 以前:我们的应用程序直接去访问一个数据库就可以了
- 现在:对数据库进行拆分之后,现在应用程序需要访问多个数据库服务器,那假如说我们的设置的拆分策略为根据id进程拆分,如果id取模等于0则访问第一个数据库,等于1访问第二个数据库,等于2访问第三个数据库,也就意味着原来只需要操作一个数据库现在要操作三个数据库,而且在应用程序中我们还需要自行根据当前业务的执行,来决定当前要操作哪一个数据库,这就造成用程序的编写代码难度将会增大而且处理起来相当繁琐。甚至对拆分后的数据库又搭建了主从结构,那么此时应用程序的操作将会更加繁琐。
解决:
-
shardingJDBC:基于AOP原理,在应用程序中对本地执行的SQL进行拦截,解析、改写、路由处理。需要自行编码配置实现,只支持java语言,性能较高。
-
MyCat:数据库分库分表中间件,不用调整代码即可实现分库分表,支持多种语言,性能不及前者。
什么叫数据库分库分表中间件:
- 我们使用了MyCat之后,应用程序你不需要做任何改动,也不用考虑每次需要连接的是哪一个数据库,而且也不需要在应用程序中去集成任何第三方的依赖,也不用做编码和配置。
- 有了这个中间件之后,应用程序在访问的时候不用直接访问数据库,直接访问的是MyCat中间件,而我们访问MyCat就和访问mysql是一样的,对应用程序来说它是无感知的。
本次课程,我们选择了是MyCat数据库中间件,通过MyCat中间件来完成分库分表操作。
3.2 MyCat概述
3.2.1 介绍
Mycat(阿里的)是开源的、活跃的、基于Java语言编写的MySQL数据库中间件
。可以像使用mysql一样来使用mycat,对于开发人员来说根本感觉不到mycat的存在。
原理:MyCat伪装了mysql的协议,所以就可以把mycat看作成一台mysql。对于我们的应用程序来说,完全可以不关心你用的是mysql还是mycat,只需要把数据库的连接换为mycat就可以了,连驱动都不用换。
开发人员只需要连接MyCat即可,而具体底层用到几台数据库,每一台数据库服务器里面存储了什么数据,都无需关心。 具体的分库分表的策略,只需要在MyCat中配置即可。
优势:
- 性能可靠稳定
- 强大的技术团队
- 体系完善
- 社区活跃
3.2.2 下载
官网: http://www.mycat.org.cn/
2版本出来没多久,所以这里选择比较稳定的版本1.6
3.2.3 安装
Mycat是采用java语言开发的开源的数据库中间件,支持Windows和Linux运行环境,下面介绍MyCat的Linux中的环境搭建。我们需要在准备好的服务器中安装如下软件。
- MySQL
- 因为最终数据是存储在mysql当中的,mycat仅仅是一个中间件他不存储数据。
- JDK
- 因为它是基于java语言开发的,所以需要安装java的运行环境
- Mycat
服务器 | 安装软件 | 说明 |
---|---|---|
192.168.10.210 | JDK、Mycat | MyCat中间件服务器 |
192.168.10.210 | MySQL | 分片服务器 |
192.168.10.213 | MySQL | 分片服务器 |
192.168.10.214 | MySQL | 分片服务器 |
占用磁盘太大了,测试完成后删了。
具体的安装步骤: 参考资料中提供的 《MyCat安装文档》即可,里面有详细的安装及配置步骤。mycat安装的博客地址
3.2.4 目录介绍
进入解压后生成的mycat目录:
- bin : 存放可执行文件,用于启动停止mycat
- conf:存放mycat的配置文件
- lib:存放mycat的项目依赖包(jar)
- logs:存放mycat的日志文件
进入存放jar包的目录,可以看到这个自带的mysql驱动版本的包比较低,我们现在使用的mysql版本是8.0.26,所以需要替换掉这个驱动包
cd lib
ll
在lib目录下删除此驱动包
rm -rf mysql-connector-java-5.1.35.jar
重新上传一份驱动包到lib目录
ll
查看,发现此jar包为红色,因为它的权限不够,所以还要进行授权。
chmod 777 mysql-connector-java-8.0.22.jar
#再次查看就变为绿色了
ll
3.2.5 概念介绍
在MyCat的整体结构中,分为两个部分:上面的逻辑结构、下面的物理结构。
在MyCat的逻辑结构主要负责逻辑库、逻辑表、分片规则、分片节点等逻辑结构的处理,而具体的数据存储还是在物理结构,也就是数据库服务器中存储的。
在后面讲解MyCat入门以及MyCat分片时,还会讲到上面所提到的概念。
3.3 MyCat入门(水平分表)
3.3.1 需求
由于 tb_order 表中数据量很大,磁盘IO及容量都到达了瓶颈,现在需要对 tb_order 表进行数据分片,分为三个数据节点,每一个节点主机位于不同的服务器上, 具体的结构,参考下图:
水平分表:
3.3.2 环境准备
准备3台服务器:
- 192.168.10.210:MyCat中间件服务器,同时也是第一个分片服务器。
- 192.168.10.213:第二个分片服务器。
- 192.168.10.214:第三个分片服务器。
并且在上述3台数据库中创建数据库 db01 。
说明:
- 底层的数据库的准本工作做完了,现在这3台mysql我们就不要在动了,不要在这3台数据库上创建表,也不要去执行一些增删改的操作。
- 现在所有的操作都是针对于mycat,所以接下来呢我们就需要在mycat当中来配置当前tb_order表的分表的一些策略
3.3.3 配置
1).schema.xml
在schema.xml中配置逻辑库、逻辑表、数据节点、节点主机等相关信息。具体的配置如下:
#存放在mycat解压后的conf目录
cd /usr/local/src/MyCat/mycat/conf/
#也可以直接在远程工具中使用记事本打开左侧的文件,这样更方便。
vim schema.xml
精简完毕后的内容
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!--schema标签配置逻辑库:
name属性:逻辑库的名字
后2个属性后面讲。
-->
<schema name="DB01" checkSQLschema="true" sqlMaxLimit="100">
<!--table标签配置逻辑库中的逻辑表:一个逻辑库下面可以包含多个逻辑表
dataNode:这个逻辑表关联的数据节点有几个
rule:分片规则,暂时使用官方默认的分片规则
-->
<table name="TB_ORDER" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
</schema>
<!--dataNode:具体的数据节点
dataHost:配置该数据节点关联的节点主机
database:关联该节点主机下的哪个数据库,这3台数据库都是db01
-->
<dataNode name="dn1" dataHost="dhost1" database="db01" />
<dataNode name="dn2" dataHost="dhost2" database="db01" />
<dataNode name="dn3" dataHost="dhost3" database="db01" />
<!--dataHost:具体配置的是数据库的连接信息
maxCon:最大连接数,minCon:最小连接数,balance:负载均衡的策略
这些参数暂时不用管,只需要改一个参数dbDriver:mysql8.0暂时只支持jdbc的连接方式,不支持native
-->
<dataHost name="dhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<!--heartbeat:心跳,不用管-->
<heartbeat>select user()</heartbeat>
<!--writeHost:当前mycat中的这个节点主机所关联的数据库的连接信息
host:这个名字无所谓
-->
<writeHost host="master" url="jdbc:mysql://192.168.10.210:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
<dataHost name="dhost2" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.213:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
<dataHost name="dhost3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.214:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
</mycat:schema>
去掉注释后的内容:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="DB01" checkSQLschema="true" sqlMaxLimit="100">
<table name="TB_ORDER" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
</schema>
<dataNode name="dn1" dataHost="dhost1" database="db01" />
<dataNode name="dn2" dataHost="dhost2" database="db01" />
<dataNode name="dn3" dataHost="dhost3" database="db01" />
<dataHost name="dhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.210:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
<dataHost name="dhost2" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.213:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
<dataHost name="dhost3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.214:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
</mycat:schema>
2).server.xml
需要在server.xml中配置用户名、密码,以及用户的访问权限信息,具体的配置如下:
同样在这个目录下:
cd /usr/local/src/MyCat/mycat/conf/
配置内容:其它的配置信息到下面讲解,这里只配置需要修改的部分内容
<user name="root" defaultAccount="true">
<!--登录mycat的密码:123456-->
<property name="password">123456</property>
<!--root用户访问的逻辑库是哪个-->
<property name="schemas">DB01</property>
<!--没有配置说明既能读又能写-->
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<schema name="TESTDB" dml="0110" >
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges>
-->
</user>
<user name="user">
<property name="password">123456</property>
<!--user用户访问的逻辑库是哪个-->
<property name="schemas">DB01</property>
<!--配置只能够读-->
<property name="readOnly">true</property>
</user>
上述的配置表示,定义了两个用户 root 和 user ,这两个用户都可以访问 DB01 这个逻辑库,访问密码都是123456,但是root用户访问DB01逻辑库,既可以读,又可以写,但是 user用户访问DB01逻辑库是只读的。
3.3.4 测试
3.3.4.1 启动
配置完毕后,先启动涉及到的3台分片服务器,然后启动MyCat服务器。切换到Mycat的安装目录,执行如下指令,启动Mycat:
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#启动
bin/mycat start
#停止
bin/mycat stop
Mycat启动之后,占用端口号 8066。
启动完毕之后,可以查看logs目录下的启动日志,查看Mycat是否启动完成。
#在mycat目录下查看日志
tail -f logs/wrapper.log
3.3.4.2 测试
1).连接MyCat
通过如下指令,就可以连接并登陆MyCat(用法和mysql相同
)。
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是DB01,他是在schema.xml文件中配置的逻辑库
show databases;
use DB01;
#显示tb_order(配置文件里是大写,这里显示小写),这张表目前只是在mycat中逻辑
# 存在的,在具体的数据库当中是没有的。
show tables;
我们看到我们是通过MySQL的指令来连接的MyCat,因为MyCat在底层实际上是模拟了MySQL的协议。
2).数据测试
然后就可以在MyCat中来创建表
,并往表结构中插入数据,查看数据在MySQL中的分布情况。
CREATE TABLE TB_ORDER (
id BIGINT(20) NOT NULL,
title VARCHAR(100) NOT NULL ,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8 ;
一旦在mycat中把这个表结构创建了,那么这个逻辑表对应的数据库表结构中的真实表就已经创建了,此时底层所涉及到的三个数据节点都有这张表了。(水平分表----表结构相同)
插入数据测试:
INSERT INTO TB_ORDER(id,title) VALUES(1,'goods1');
INSERT INTO TB_ORDER(id,title) VALUES(2,'goods2');
INSERT INTO TB_ORDER(id,title) VALUES(3,'goods3');
INSERT INTO TB_ORDER(id,title) VALUES(5000000,'goods5000000');
INSERT INTO TB_ORDER(id,title) VALUES(10000000,'goods10000000');
INSERT INTO TB_ORDER(id,title) VALUES(10000001,'goods10000001');
INSERT INTO TB_ORDER(id,title) VALUES(15000000,'goods15000000');
INSERT INTO TB_ORDER(id,title) VALUES(15000001,'goods15000001');
经过测试,我们发现,在往 TB_ORDER 表中插入数据时:
-
如果id的值在1-500w之间,数据将会存储在第一个分片数据库中。
-
如果id的值在500w-1000w之间,数据将会存储在第二个分片数据库中。
-
如果id的值在1000w-1500w之间,数据将会存储在第三个分片数据库中。
-
如果id的值超出1500w,在插入数据时,将会报错,需要在去增加对应的数据节点。
为什么会出现这种现象,数据到底落在哪一个分片服务器到底是如何决定的呢? 这是由逻辑表配置时的一个参数 rule 决定的,而这个参数配置的就是分片规则,关于分片规则的配置,在后面的课程中会详细讲解。
解释:
-
第一步:在schema.xml配置的分片规则
auto-sharding-long
,他引用了rule.xml
-
第二步上:在rule.xml可以看到这种分片规则是根据id字段进行分片的,具体是通过下面这个rang-long算法决定的
-
第二步下:rang-long也是一个引用,具体引用的是rule.xml中下面的分片函数
-
第三步:这个函数又管理了一个物理文件,autopartition-long.txt。
- range start-end:范围,开始-结束(0-500)
- data node index:数据节点的索引(从0开始排,=0,=1,=2)
- k:一千
- M:一万
- 最终:0-500万存放在第一个节点,500万-1000万存放在第二个节点,1000万-1500万存放在第三个节点
-
schema.xml、rule.xml、autopartition-long.txt都在/usr/local/src/MyCat/mycat/conf/目录下
3.4 MyCat配置
3.4.1 schema.xml
schema.xml 作为MyCat中最重要的配置文件之一 , 涵盖了MyCat的逻辑库 、 逻辑表 、 分片规则、分片节点及数据源的配置。
主要包含以下三组标签:
- schema标签
- datanode标签
- datahost标签
3.4.1.1 schema标签
1).schema 定义逻辑库
schema 标签用于定义 MyCat实例中的逻辑库 , 一个MyCat实例中, 可以有多个逻辑库 , 可以通过 schema 标签来划分不同的逻辑库。MyCat中的逻辑库的概念,等同于MySQL中的database概念, 需要操作某个逻辑库下的表时,也需要切换逻辑库(use xxx)。
核心属性:
-
name:指定自定义的逻辑库库名(注意大小写)
-
checkSQLschema:在SQL语句操作时指定了数据库名称,执行时是否自动去除;true:自动去 除,false:不自动去除
- 正常情况想要执行sql语句必须要先切换到对应的数据库下,现在可以不用切换直接在sql语句上数据库名.表名进行执行,如果配置的为true在执行的时候会自动去掉数据库名称,如果是false不会去掉数据库名执行这条sql会报错,必须要先切换数据库在执行。
-
sqlMaxLimit:如果未指定limit进行查询,列表查询模式最多查询多少条记录
- 不使用分页也就是说查询的是全部数据,既然使用mycat查询说明数据非常大,查询全表数据太耗费性能了。
2).schema 中的table定义逻辑表
table 标签定义了MyCat中逻辑库schema下的逻辑表 ,一个逻辑库下面可以配置多个逻辑表,所有需要拆分的表都需要在table标签中定义 。
核心属性:
- name:定义逻辑表表名,在该逻辑库下唯一
- dataNode:定义逻辑表所属的dataNode数据节点,该属性需要与dataNode标签中name对应;多个dataNode逗号分隔
- rule:分片规则的名字,分片规则名字是在rule.xml中定义的
- primaryKey:逻辑表对应真实表的主键
- type:逻辑表的类型,目前逻辑表只有全局表和普通表,如果未配置,就是普通表;全局表,配置为 global
- 普通表:需要去分片的表
- 全局表:所有分片节点当中都会包含这张全局表
3.4.1.2 datanode标签
用来配置数据节点
核心属性:
- name:定义数据节点名称
- dataHost:数据库实例主机名称,引用自 dataHost 标签中name属性
- database:定义分片所属数据库
3.4.1.3 datahost标签
该标签在MyCat逻辑库中作为底层标签存在, 直接定义了具体的数据库实例、读写分离、心跳语句。
核心属性:
- name:唯一标识,供上层标签使用
- maxCon/minCon:最大连接数/最小连接数
- balance:负载均衡策略,取值 0,1,2,3 (
具体到读写分离时讲解
) - writeType:写操作分发方式(0:写操作转发到第一个writeHost数据源,第一个挂了,切换到第二 个;1:写操作随机分发到配置的writeHost)
- 也就是说writeHost可以配置多个
- dbDriver:数据库驱动,支持 native、jdbc(mysql8版本支持)
- writeHost标签:
- host属性的值任意
3.4.2 rule.xml
rule.xml中定义所有拆分表的规则,在使用过程中可以灵活的使用分片算法,或者对同一个分片算法使用不同的参数,它让分片过程可配置化。主要包含两类标签:tableRule、Function。
- tableRule:定义所涉及到的分片规则(
在后面详解
)- columns标签:根据那个字段进行分片
- algorithm便签:当前这种分片规则所关联的分片算法是那个。它也是一个引用,引用的是下面这个Function中定义的分片算法。
- Function:这个分片规则所涉及到的一些java类以及参数配置
- name属性:上下2个对应
- class属性:mycat提供分片规则处理的java类是哪个
- property标签:这种分片规则所关联的一些属性配置,当然这些属性配置也可以封装在一个外部的文件中。
3.4.3 server.xml
server.xml配置文件包含了MyCat的系统配置信息,主要有两个重要的标签:system、user。
1). system标签
不作为重点关注
主要配置MyCat中的系统配置信息,对应的系统配置项及其含义,如下:
属性 | 取值 | 含义 |
---|---|---|
charset | utf8 | 设置Mycat的字符集, 字符集需要与MySQL的字符集保持一致 |
nonePasswordLogin | 0,1 | 0为需要密码登陆、1为不需要密码登陆 ,默认为0,设置为1则需要指定默认账户 |
useHandshakeV10 | 0,1 | 使用该选项主要的目的是为了能够兼容高版本的jdbc驱动, 是否采用 HandshakeV10Packet来与client进行通 信, 1:是, 0:否 |
useSqlStat | 0,1 | 开启SQL实时统计, 1 为开启 , 0 为关闭 ; 开启之后, MyCat会自动统计SQL语句的执行情况 ; mysql -h 127.0.0.1 -P 9066 -u root -p 查看MyCat执行的SQL, 执行效率比较低的SQL , SQL的整体执行情况、读写比例等 ; show @@sql ; show @@sql.slow ; show @@sql.sum ; |
useGlobleTableCheck | 0,1 | 是否开启全局表的一致性检测。1为开启 ,0 为关闭 |
sqlExecuteTimeout | 1000 | SQL语句执行的超时时间 , 单位为 s ; |
sequnceHandlerType | 0,1,2 | 用来指定Mycat全局序列类型,0 为本地文件,1 为数据库方式,2 为时间戳列方式,默认使用本地文件方式,文件方式主要用于测试 |
sequnceHandlerPattern | 正则表达式 | 必须带有MYCATSEQ或者 mycatseq进入序列匹配流程 注意MYCATSEQ_有空格的情况 |
subqueryRelationshipCheck | true,false | 子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false |
useCompression | 0,1 | 开启mysql压缩协议 , 0 : 关闭, 1 : 开启 |
fakeMySQLVersion | 5.5,5.6 | 设置模拟的MySQL版本号 |
属性 | 取值 | 含义 |
---|---|---|
defaultSqlParser | 由于MyCat的最初版本使用了FoundationDB 的SQL解析器, 在MyCat1.3后增加了Druid 解析器, 所以要设置defaultSqlParser属性来指定默认的解析器; 解析器有两个 : druidparser 和 fdbparser, 在MyCat1.4之后,默认是druidparser, fdbparser已经废除了 | |
processors | 1,2… | 指定系统可用的线程数量, 默认值为CPU核心x 每个核心运行线程数量; processors 会影响processorBufferPool,processorBufferLocalPercent,processorExecutor属性, 所有, 在性能调优时, 可以适当地修改processors值 |
processorBufferChunk | 指定每次分配Socket Direct Buffer默认值为4096字节, 也会影响BufferPool长度, 如果一次性获取字节过多而导致buffer不够 用, 则会出现警告, 可以调大该值 | |
processorExecutor | 指定NIOProcessor上共享businessExecutor固定线程池的大小; MyCat把异步任务交给 businessExecutor 线程池中, 在新版本的MyCat中这个连接池使用频次不高, 可以适当地把该值调小 | |
packetHeaderSize | 指定MySQL协议中的报文头长度, 默认4个字节 | |
maxPacketSize | 指定MySQL协议可以携带的数据最大大小, 默认值为16M | |
idleTimeout | 30 | 指定连接的空闲时间的超时长度;如果超时,将关闭资源并回收, 默认30分钟 |
属性 | 取值 | 含义 |
---|---|---|
txIsolation | 1,2,3,4 | 初始化前端连接的事务隔离级别,默认为REPEATED_READ , 对应数字为3 ,READ_UNCOMMITED=1;READ_COMMITTED=2;REPEATED_READ=3;SERIALIZABLE=4; |
sqlExecuteTimeout | 300 | 执行SQL的超时时间, 如果SQL语句执行超时, 将关闭连接; 默认300秒; |
serverPort | 8066 | 定义MyCat的使用端口, 默认8066 |
managerPort | 9066 | 定义MyCat的管理端口, 默认9066 |
2). user标签
配置MyCat中的用户、访问密码,以及用户针对于逻辑库、逻辑表的权限信息,具体的权限描述方式及配置说明如下:
在测试权限操作时,我们只需要将 privileges 标签的注释放开。 在 privileges 下的schema 标签中配置的dml属性配置的是逻辑库的权限。 在privileges的schema下的table标签的dml属性中配置逻辑表的权限。(如果不配置就代表他可以对这个逻辑库当中的虽有的逻辑表进行任何操作。
)
测试完成后注释掉。
<!-- 表级 DML 权限设置 -->
<privileges check="true"> <!--true:开启权限检查-->
<schema name="DB01" dml="1110" > <!--tb01:指定逻辑库名,0110:没有增加权限,有修改权限,有查询权限,没有删除权限 (配置数据库的增改查删权限,0代表没有,1代表有)-->
<table name="TB_ORDER" dml="1110"> </table> <!--TB_ORDER:逻辑表的名字,0000:配置逻辑表的权限,如果逻辑库和逻辑表都配置了权限,那么使用的是逻辑表的权限,遵循就近原则-->
</schema>
</privileges>
3.5 MyCat分片
3.5.1 垂直拆分(垂直分库)
3.5.1.1 场景
在业务系统中,涉及以下表结构,但是由于用户与订单每天都会产生大量的数据,单台服务器的数据存储及处理能力是有限的,可以对数据库表进行拆分,原有的数据库表如下。
现在考虑将其进行垂直分库操作,将商品相关的表拆分到一个数据库服务器,订单表拆分的一个数据库服务器,用户及省市区表拆分到一个服务器。最终结构如下:
省市区相关的表里面存储的数据本来就不多,几千最多几万条数据,也就没有必要存放在一个单独的分区中,用户表里面有一个用户地址字段,里面存放的有省市区相关的编号,最终我在展示的时候还需要展示省市区的名字。所以考虑把用户和省市区相关的表放在一个分片当中。
3.5.1.2 准备
准备三台服务器,IP地址如图所示:
并且在192.168.10.210,192.168.10.213,192.168.10.214上面创建数据库shopping
(3台都要创建)。
3.5.1.3 配置
1).schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="SHOPPING" checkSQLschema="true" sqlMaxLimit="100">
<!--只有涉及到分表的时候才需要指定分片规则,现在是垂直分库所以不需要指定,代表这张表的所有数据都存放在这一个分片上-->
<table name="tb_goods_base" dataNode="dn1" primaryKey="id" />
<table name="tb_goods_brand" dataNode="dn1" primaryKey="id" />
<table name="tb_goods_cat" dataNode="dn1" primaryKey="id" />
<table name="tb_goods_desc" dataNode="dn1" primaryKey="goods_id" />
<table name="tb_goods_item" dataNode="dn1" primaryKey="id" />
<table name="tb_order_item" dataNode="dn2" primaryKey="id" />
<table name="tb_order_master" dataNode="dn2" primaryKey="order_id" />
<table name="tb_order_pay_log" dataNode="dn2" primaryKey="out_trade_no" />
<table name="tb_user" dataNode="dn3" primaryKey="id" />
<table name="tb_user_address" dataNode="dn3" primaryKey="id" />
<table name="tb_areas_provinces" dataNode="dn3" primaryKey="id"/>
<table name="tb_areas_city" dataNode="dn3" primaryKey="id"/>
<table name="tb_areas_region" dataNode="dn3" primaryKey="id"/>
</schema>
<dataNode name="dn1" dataHost="dhost1" database="shopping" />
<dataNode name="dn2" dataHost="dhost2" database="shopping" />
<dataNode name="dn3" dataHost="dhost3" database="shopping" />
<dataHost name="dhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.210:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
<dataHost name="dhost2" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.213:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
<dataHost name="dhost3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.214:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234" />
</dataHost>
</mycat:schema>
2).server.xml
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<!--root用户访问的逻辑库是哪个-->
<property name="schemas">SHOPPING</property>
<!--没有配置说明既能读又能写-->
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="true">
<schema name="DB01" dml="1110" >
<table name="TB_ORDER" dml="1110"></table>
</schema>
</privileges>
-->
</user>
<user name="user">
<property name="password">123456</property>
<!--user用户访问的逻辑库是哪个-->
<property name="schemas">SHOPPING</property>
<!--配置只能够读-->
<property name="readOnly">true</property>
</user>
3.5.1.4 测试
1)mycat修改完配置文件后需要重新启动
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先把之前启动的mycat 关闭掉
bin/mycat stop
#启动
bin/mycat start
#在mycat目录下查看日志,是否启动成功
tail -f logs/wrapper.log
2)连接连接MyCat
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use SHOPPING;
#显示所有的逻辑表,这些逻辑表只是在schema.xml中定义出来了,在逻辑上是有这些表的,
# 但是这些表在具体的物理节点中并不存在,所以接下来要创建具体的表结构
show tables;
3)上传测试SQL脚本到服务器的/root目录
4)执行指令导入测试数据
重新启动MyCat后,在mycat的命令行中,通过source指令导入表结构,以及对应的数据,查看数据分布情况。
#登录mycat并且切换了数据库之后执行
source /root/shopping-table.sql
source /root/shopping-insert.sql
将表结构及对应的测试数据导入之后,可以检查一下各个数据库服务器中的表结构分布情况。 检查是否和我们准备工作中规划的服务器一致,以及表中是否有数据。
5)查询用户的收件人及收件人地址信息(包含省、市、区)。
涉及到的表
第三个分片服务器中
在MyCat
的命令行中,当我们执行以下多表联查的SQL语句时,可以正常查询出数据。
SELECT ua.user_id, ua.contact, p.province, c.city, r.area , ua.address
FROM tb_user_address ua ,tb_areas_city c , tb_areas_provinces p ,tb_areas_region r
WHERE ua.province_id = p.provinceid AND ua.city_id = c.cityid AND ua.town_id = r.areaid ;
6)查询每一笔订单及订单的收件地址信息(包含省、市、区)。
涉及到的表
第二个分片服务器中
第三个分片服务器中
实现该需求对应的SQL语句如下:
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 ;
但是现在存在一个问题,订单相关的表结构是在 192.168.10.213 数据库服务器中,而省市区的数据库表是在 192.168.10.214 数据库服务器中。那么在MyCat中执行是否可以成功呢?
经过测试,我们看到,SQL语句执行报错。原因就是因为MyCat在执行该SQL语句时,需要往具体的数据库服务器中路由,而当前没有一个数据库服务器完全包含了订单以及省市区的表结构,造成SQL语句失败,报错。
对于上述的这种现象,我们如何来解决呢? 下面我们介绍的全局表,就可以轻松解决这个问题。
3.5.1.5 全局表
对于省、市、区/县表tb_areas_provinces , tb_areas_city , tb_areas_region,是属于数据字典表,在多个业务模块中都可能会遇到,可以将其设置为全局表,利于业务操作。
字典表:数据不多还不会变。
全局表:在每一个节点中都存在。
修改schema.xml中的逻辑表的配置,修改 tb_areas_provinces、tb_areas_city、tb_areas_region 三个逻辑表,增加 type 属性,配置为global,就代表该表是全局表,就会在所涉及到的dataNode中创建给表。对于当前配置来说,也就意味着所有的节点中都有该表了。
<table name="tb_areas_provinces" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_city" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_region" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
1)配置完毕后,先停止MyCat。
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#停止
bin/mycat stop
2)删除原来每一个数据库服务器中的所有表结构(即:把三个分片数据库shopping中的表都删除)
3)删除表结构之后,重新启动mycat
#启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
4)通过source指令,导入表及数据
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
use SHOPPING;
source /root/shopping-table.sql
source /root/shopping-insert.sql
5)检查每一个数据库服务器中的表及数据分布,看到三个节点中都有这三张全局表
6)然后再次执行上面的多表联查的SQL语句
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 ;
是可以正常执行成功的。
7)当在MyCat中更新全局表的时候,我们可以看到,所有分片节点中的数据都发生了变化,每个节点的全局表数据时刻保持一致。
update tb_areas_provinces set province = "北京" where id = 1;
3.5.2 水平拆分(水平分表)
3.5.2.1 场景
在业务系统中,有一张表(日志表),业务系统每天都会产生大量的日志数据,单台服务器的数据存储及处理能力是有限的,可以对数据库表进行拆分。
3.5.2.2 准备
准备三台服务器,具体的结构如下:
并且,在三台数据库服务器中分表创建一个数据库itcast
。
3个都要创建
3.5.2.3 配置
1).schema.xml
在schema.xml中增加水平分表的配置,上面垂直分库的配置不用删除。
<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100">
<!--分表需要指定分片规则,mod-long:这张表的数据需要均匀分散的存储在这三个节点中
原理:根据id进行分片,对主键id进行求模(id%3),结果为0落在第一个节点,为1落在第二个节点,为2落在第三个节点
-->
<table name="tb_log" dataNode="dn4,dn5,dn6" primaryKey="id" rule="mod-long" />
</schema>
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
tb_log表最终落在3个节点中,分别是 dn4、dn5、dn6 ,而具体的数据分别存储在 dhost1、dhost2、dhost3的itcast数据库中。
rule.xml中的分片规则:
2).server.xml
配置root用户既可以访问 SHOPPING 逻辑库,又可以访问ITCAST逻辑库。
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<!--root用户访问的逻辑库是哪个-->
<property name="schemas">SHOPPING,ITCAST</property>
<!--没有配置说明既能读又能写-->
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="true">
<schema name="DB01" dml="1110" >
<table name="TB_ORDER" dml="1110"></table>
</schema>
</privileges>
-->
</user>
<user name="user">
<property name="password">123456</property>
<!--user用户访问的逻辑库是哪个-->
<property name="schemas">SHOPPING</property>
<!--配置只能够读-->
<property name="readOnly">true</property>
</user>
3.5.2.4 测试
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_log只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
然后在mycat的命令行中
,执行如下SQL创建表、并插入数据,查看数据分布情况。
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;
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','2022-01-06 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','2022-01-06 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','2022-01-06 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','2022-01-06 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','2022-01-06 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','2022-01-06 18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.controller.UserController','find','29',2);
效果:发现是均匀分片,并且满足分片规则(分片1:id%=0,分片2:id%=1,分片3:id%=2)
3.5.3 分片规则
schema.xml文件中的rule字段指定分片规则,它是个引用
具体是在rule.xml中配置的分片规则:
3.5.3.1 范围分片
1).介绍
根据指定的字段及其配置的范围与数据节点的对应情况, 来决定该数据属于哪一个分片。
2).配置
schema.xml逻辑表配置:
<table name="TB_ORDER" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
schema.xml数据节点配置:
<dataNode name="dn1" dataHost="dhost1" database="db01" />
<dataNode name="dn2" dataHost="dhost2" database="db01" />
<dataNode name="dn3" dataHost="dhost3" database="db01" />
rule.xml分片规则配置:
<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>
分片规则配置属性含义:
属性 | 描述 |
---|---|
columns | 标识将要分片的表字段 |
algorithm | 指定分片函数与function的对应关系 |
class | 指定该分片算法对应的类 |
mapFile | 对应的外部配置文件 |
type | 默认值为0 ; 0 表示Integer , 1 表示String |
defaultNode | 默认节点 默认节点的所用:枚举分片时,如果碰到不识别的枚举值, 就让它路由到默认节点 ; 如果没有默认值,碰到不识别的则报错 。 |
在rule.xml中配置分片规则时,关联了一个映射配置文件 autopartition-long.txt,该配置文件的配置如下:
# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2
含义:0-500万之间的值,存储在0号数据节点(数据节点的索引从0开始) ; 500万-1000万之间的数据存储在1号数据节点 ; 1000万-1500万的数据节点存储在2号节点 ;
该分片规则,主要是针对于数字类型
的字段适用。 在MyCat的入门程序中,我们使用的就是该分片规则。
3.5.3.2 取模分片
1).介绍
根据指定的字段值与节点数量进行求模运算,根据运算结果, 来决定该数据属于哪一个分片。
2).配置
schema.xml逻辑表配置:
<table name="tb_log" dataNode="dn4,dn5,dn6" primaryKey="id" rule="mod-long" />
schema.xml数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml分片规则配置:
<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 | 数据节点的数量 |
该分片规则,主要是针对于数字类型
的字段适用。 在前面水平拆分的演示中,我们选择的就是取模分片。
3.5.3.3 一致性hash分片
1).介绍
所谓一致性哈希指的是在进行分片操作的时候,它会去计算我们所指定的字段的哈希值,相同的哈希因子计算值总是被划分到相同的分区表中,不会因为分区节点的增加而改变原来数据的分区位置,有效的解决了分布式数据的拓容问题。
2).配置
schema.xml中逻辑表配置:
<!-- 一致性hash -->
<table name="tb_order" dataNode="dn4,dn5,dn6" rule="sharding-by-murmur" />
schema.xml中数据节点配置:在3.5.2水平分表中已经写过了
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml中分片规则配置:
<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><!-- 默认是0 -->
<property name="count">3</property>
<property name="virtualBucketTimes">160</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值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 |
3).测试
配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数 据分布情况。
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_order只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
然后在mycat的命令行中
,执行如下SQL创建表、并插入数据,查看数据分布情况。
create table tb_order(
id varchar(100) not null primary key,
money int null,
content varchar(200) null
);
INSERT INTO tb_order (id, money, content) VALUES ('b92fdaaf-6fc4-11ec-b831-482ae33c4a2d', 10, 'b92fdaf8-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93482b6-6fc4-11ec-b831-482ae33c4a2d', 20, 'b93482d5-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b937e246-6fc4-11ec-b831-482ae33c4a2d', 50, 'b937e25d-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93be2dd-6fc4-11ec-b831-482ae33c4a2d', 100, 'b93be2f9-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93f2d68-6fc4-11ec-b831-482ae33c4a2d', 130, 'b93f2d7d-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9451b98-6fc4-11ec-b831-482ae33c4a2d', 30, 'b9451bcc-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9488ec1-6fc4-11ec-b831-482ae33c4a2d', 560, 'b9488edb-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b94be6e6-6fc4-11ec-b831-482ae33c4a2d', 10, 'b94be6ff-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b94ee10d-6fc4-11ec-b831-482ae33c4a2d', 123, 'b94ee12c-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b952492a-6fc4-11ec-b831-482ae33c4a2d', 145, 'b9524945-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95553ac-6fc4-11ec-b831-482ae33c4a2d', 543, 'b95553c8-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9581cdd-6fc4-11ec-b831-482ae33c4a2d', 17, 'b9581cfa-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95afc0f-6fc4-11ec-b831-482ae33c4a2d', 18, 'b95afc2a-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95daa99-6fc4-11ec-b831-482ae33c4a2d', 134, 'b95daab2-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9667e3c-6fc4-11ec-b831-482ae33c4a2d', 156, 'b9667e60-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b96ab489-6fc4-11ec-b831-482ae33c4a2d', 175, 'b96ab4a5-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b96e2942-6fc4-11ec-b831-482ae33c4a2d', 180, 'b96e295b-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b97092ec-6fc4-11ec-b831-482ae33c4a2d', 123, 'b9709306-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b973727a-6fc4-11ec-b831-482ae33c4a2d', 230, 'b9737293-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b978840f-6fc4-11ec-b831-482ae33c4a2d', 560, 'b978843c-6fc4-11ec-b831-482ae33c4a2d');
效果:根据字段的hash值来决定到底路由到那个分片
3.5.3.4 枚举分片
1).介绍
通过在配置文件中配置可能的枚举值, 指定数据分布到不同数据节点上, 本规则适用于按照省份、性别、状态拆分数据等业务 。
- 枚举值:索引
- 枚举值:字段的枚举值,比如 某一张表的数据状态
- 索引:节点的索引
- eg:
- (1: 0):如果这个字段的枚举值是1,则数据存放在第1个节点上
- (2: 1):如果这个字段的枚举值是2,则数据存放在第2个节点上
- (3: 2):如果这个字段的枚举值是3,则数据存放在第3个节点上
- defaultNode默认节点:指的是如果我们往这个数据库表在插入数据的时候,超出了我们所指定的枚举值,那么此时默认往哪一个节点当中进行存储。默认值是2,意味着往第3个节点进行储存。
- mapFile关联的外部文件partition-hash-int.txt:配置的是枚举值与对应的分片节点
2).配置
schema.xml中逻辑表配置:
<!-- 枚举 -->
<table name="tb_user" dataNode="dn4,dn5,dn6" rule="sharding-by-intfile-enumstatus"/>
schema.xml中数据节点配置:在3.5.2水平分表中已经写过了
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml中分片规则配置:
<!--columns指定根据什么字段进行分片,我们是想要根据status字段分片,如果直接写可能会出现问题???
例如:在逻辑表当中有可能有另外的一张逻辑表,也想根据枚举进行分片,此时就会存在一个矛盾。逻辑表1
在进行枚举分片的时候他想根据status进行分片,逻辑表2想根据sex性别进行分片,此时如果只有一个逻辑分片规则
那么columns属性该怎么写呢????
解决:复制一份,修改名字即可
-->
<tableRule name="sharding-by-intfile">
<rule>
<columns>sharding_id</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
<!-- 自己增加 tableRule -->
<tableRule name="sharding-by-intfile-enumstatus">
<rule>
<columns>status</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
<property name="defaultNode">2</property>
<property name="mapFile">partition-hash-int.txt</property>
</function>
partition-hash-int.txt ,内容如下 :
1=0
2=1
3=2
分片规则属性含义:
属性 | 描述 |
---|---|
columns | 标识将要分片的表字段 |
algorithm | 指定分片函数与function的对应关系 |
class | 指定该分片算法对应的类 |
mapFile | 对应的外部配置文件 |
type | 默认值为0 ; 0 表示Integer , 1 表示String |
defaultNode | 默认节点 ; 小于0 标识不设置默认节点 , 大于等于0代表设置默认节点 ; 默认节点的所用:枚举分片时,如果碰到不识别的枚举值, 就让它路由到默认节点 ; 如果没有默认值,碰到不识别的则报错 。 |
3).测试
配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_user只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
然后在mycat的命令行中
,执行如下SQL创建表、并插入数据,查看数据分布情况。
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;
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);
insert into tb_user (id,username ,status) values(6,'Tom',1);
insert into tb_user (id,username ,status) values(7,'Cat',2);
insert into tb_user (id,username ,status) values(8,'Rose',3);
insert into tb_user (id,username ,status) values(9,'Coco',2);
insert into tb_user (id,username ,status) values(10,'Lily',1);
效果:第一个节点的status都为1,第二个节点的status都为2,第三个节点的status都为3
4).测试:如果我们往这个数据库表在插入数据的时候,超出了我们所指定的枚举值,那么此时默认往哪一个节点当中进行存储。默认值是2,意味着往第3个节点进行储存。
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_order只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
#status为6,自己在partition-hash-int.txt中设置的是1 2 3,超出最大值3所以会放在第3个节点中。
insert into tb_user (id,username ,status) values(11,'xiaoheihei',6);
3.5.3.5 应用指定算法
1).介绍
运行阶段由应用自主决定当前这条数据会路由到那个分片 , 直接根据字符子串(必须是数字)计算分片号。
即:根据这条数据当中某一个字段的子字符串,来计算出对应的分片。这个求取出来的子字符串必须是数字,比如求取出来的这个子字符串是0 就代表会落在第一个分片上,求取出来的这个子字符串是1 就代表会落在第二个分片上,求取出来的这个子字符串是2 就代表会落在第三个分片上。
截取前2个子字符串
- (0010001:0):截取的是00则落在第一个分片中
- (0110001:1):截取的是01则落在第二个分片中
- (0210001:2):截取的是02则落在第三个分片中
- 开始索引:从那个位置开始截取字符串
- 截取长度:截取几位
- 分片数量:当前是3片
- 默认分片:截取出来的子字符串不在这个分片数量的范围3中,那么他会走默认分片,值为2也就是第3个分片
2).配置
schema.xml中逻辑表配置:
<!-- 应用指定算法 -->
<table name="tb_app" dataNode="dn4,dn5,dn6" rule="sharding-by-substring" />
schema.xml中数据节点配置:在3.5.2水平分表中已经写过了
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml中分片规则配置:默认没有提供这个规则样例,需要自己增加
<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 。
3).测试
配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数 据分布情况。
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
重新登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_app只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
然后在mycat的命令行中
,执行如下SQL创建表、并插入数据,查看数据分布情况。
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;
insert into tb_app (id,name) values('0000001','Testx00001');
insert into tb_app (id,name) values('0100001','Test100001');
insert into tb_app (id,name) values('0100002','Test200001');
insert into tb_app (id,name) values('0200001','Test300001');
insert into tb_app (id,name) values('0200002','TesT400001');
效果:截取id的前2个字符串,截取的数字就代表所属的分片。
如果插入数据的id,截取出来的前2个字符串不在3这个分片范围内,默认放在第一个分片中(因为rule.xml中设置的是0)
insert into tb_app (id,name) values('0400002','TesT500001');
3.5.3.6 固定分片hash算法
1).介绍
该算法类似于十进制的求模运算,但是为二进制的操作,例如,取 id 的二进制低 10 位 与1111111111 进行位 & 运算,位与运算最小值为 0000000000,最大值为1111111111,转换为十进制,也就是位于0-1023之间。
同为1则为1,有一个是0则为0
例如:如果计算出来的结果在0~255
之间则落在第一个分片,256~511
之间则落在第二个分片,512~1023
之间则落在第三个分片。
举例:
- 当插入的数据id值为1,转化为二进制后和1111111111 进行位 & 运算,结果在0到255之间,所以在第一个分片中。
- 当插入的数据id值为2,转化为二进制后和1111111111 进行位 & 运算,结果在0到255之间,所以在第一个分片中。
- 当插入的数据id值为300,转化为二进制后和1111111111 进行位 & 运算,结果在256到511之间,所以在第二个分片中。
特点:
- 如果是求模,连续的值,分别分配到各个不同的分片;但是此算法会将连续的值可能分配到相同的分片,降低事务处理的难度。
- 比如0、1、2都会分配在第一个分片中
- 可以均匀分配,也可以非均匀分配。
- 目前这个图是非均匀分配,第3个分片的范围是前2个分片的范围之和。
- 分片字段必须为数字类型。
- 因为要进行位运算
- partitionCount:分片的数量,(2,1)----->(有2个分片节点,有一个分片节点)
- partitionLength:分片的长度,(256,512)------>(前面2个分片节点的长度都是256,后一个的分片节点长度是512)。这2组之和是1024,而且必须是1024。
- 也就是说固定分片hash算法,整个分片长度是固定的就是1024。
2).配置
schema.xml中逻辑表配置:
<!-- 固定分片hash算法 -->
<table name="tb_longhash" dataNode="dn4,dn5,dn6" rule="sharding-by-long-hash" />
schema.xml中数据节点配置:在3.5.2水平分表中已经写过了
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml中分片规则配置:这个也是rule.xml中默认没有案例
<tableRule name="sharding-by-long-hash">
<rule>
<columns>id</columns>
<algorithm>sharding-by-long-hash</algorithm>
</rule>
</tableRule>
<!-- 分片总长度为1024,count与length数组长度必须一致; -->
<function name="sharding-by-long-hash" class="io.mycat.route.function.PartitionByLong">
<property name="partitionCount">2,1</property>
<property name="partitionLength">256,512</property>
</function>
分片规则属性含义:
属性 | 描述 |
---|---|
columns | 标识将要分片的表字段名 |
algorithm | 指定分片函数与function的对应关系 |
class | 指定该分片算法对应的类 |
partitionCount | 分片个数列表 |
partitionLength | 分片范围列表 |
约束 :
-
分片长度 : 默认最大2^10 , 为 1024 ;
-
count, length的数组长度必须是一致的 ;
以上分为三个分区:0-255,256-511,512-1023
示例说明 :
当配置了这2个属性(分片的数量partitionCount,分片的长度partitionLength)之后,mycat在进行分片的时候,就相当于在底层他要去初始化一个数组,数组的整个长度是1024,其中0到255索引上存放的元素全部是0,256到511索引上存放的元素全部是1,512到1023索引上存放的元素全部是2。当插入的id为515时他会与1023进行位与运算,515转化为二进制就是10 0000 0011,1023转化为二进制就是11 1111 1111,结果为10 0000 0011也就是515,在数组中515索引对应的元素是2,这个2是分片的索引,所以此时会路由到第三个分片中。
3).测试
配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。
配置完毕后,重新启动MyCat,此时会报错
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志,此时会报错
#因为在rule.xml中还有2个引用使用到了func1,而我们在下面函数中把func1修改成了sharding-by-long-hash
#解决:这个个规则测试时候我们没用到,所以直接注释换删除掉即可
tail -f logs/wrapper.log
注释掉后再次重新启动
#先停止
bin/mycat stop
#在启动
bin/mycat start
#查看日志启动成功
tail -f logs/wrapper.log
重新登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_longhash只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
然后在mycat的命令行中
,执行如下SQL创建表、并插入数据,查看数据分布情况。
CREATE TABLE tb_longhash (
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;
insert into tb_longhash (id,name,firstChar) values(1,'七匹狼','Q');
insert into tb_longhash (id,name,firstChar) values(2,'八匹狼','B');
insert into tb_longhash (id,name,firstChar) values(3,'九匹狼','J');
insert into tb_longhash (id,name,firstChar) values(4,'十匹狼','S');
insert into tb_longhash (id,name,firstChar) values(5,'六匹狼','L');
insert into tb_longhash (id,name,firstChar) values(6,'五匹狼','W');
insert into tb_longhash (id,name,firstChar) values(7,'四匹狼','S');
insert into tb_longhash (id,name,firstChar) values(8,'三匹狼','S');
insert into tb_longhash (id,name,firstChar) values(9,'两匹狼','L');
效果:这9条记录都是在第一个节点
当插入id为1089的数据时,此时超过1024范围,会落在第一个节点上。因为进行位与运算之后,他出来的结果就落在第一个节点上了。
insert into tb_longhash (id,name,firstChar) values(1089,'两匹狼','L');
3.5.3.7 字符串hash解析算法
1).介绍
截取字符串中的指定位置的子字符串,进行hash算法,算出分片。
解释:
- 固定分片hash算法:针对的是数字类型的字段进行截取
- 字符串hash解析算法:针对的是字符串类型的字段进行截取
举例:截取的子字符串为字段的前2位,之后对截取出来的这2个子字符串进行hash运算,算出的hash值在与1023进行位于运算,最后根据运算的结果来决定到底落在哪一个分片节点上。
- partitionCount:分片的数量,2----->有2个分片节点
- partitionLength:分片的长度,512------>2个分片节点的长度都是512
- hashSlice:hash运算位,就是在进行hash运算的时候所截取的子字符串是哪个。
- 0如果在末尾代表的是整个字符串的长度
- 如果是-1代表字符串长度减去1
- 如果是大于0代表数字本身
- (0:2):0出现在start,就代表从第1个位置开始到索引为2的字符串截取出来。
2).配置
schema.xml中逻辑表配置:
<!-- 字符串hash解析算法 -->
<table name="tb_strhash" dataNode="dn4,dn5" rule="sharding-by-stringhash" />
schema.xml中数据节点配置:在3.5.2水平分表中已经写过了
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
rule.xml中分片规则配置:默认没有需要自己定义
<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 |
示例说明:
在rule.xml配置了分片规则后,他会初始化一个数组长度为1024,配置了2个分片所以分为2个部分,0~511
之间的索引存的都是0,512~1023
之间的索引存的都是1。如果插入的数据需要截取的字符串为world
,他会截取(0:2)3个字符为wor,之后对wor进行hash运算,运算出来的结果在和1023进行位于运算,运算的结果为5(结果是二进制,转化为了十进制),之后它就会拿着这个5到数组中找索引为5的元素是多少,对应的是0,这个0代表的就是当前这条记录会路由在第一个分片。(0和1代表的是分片的索引值)
3).测试
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
重新登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_strhash只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
然后在mycat的命令行中
,执行如下SQL创建表、并插入数据,查看数据分布情况。
create table tb_strhash(
name varchar(20) primary key,
content varchar(100)
)engine=InnoDB DEFAULT CHARSET=utf8mb4;
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());
效果:因为只配置了2个分片,所以此时只有在第一个分片和第二个分片中有这张表。分片1中有2条数据,分片2中有3条数据。
3.5.3.8 按天分片算法
1).介绍
按照日期及对应的时间周期来分片。
- begin:开始时间
- end:结束时间
- partionday:周期
eg:
- ( 2022-01-1 ~ 2022-01-10:0):放在第一个分片当中
- ( 2022-01-11 ~ 2022-01-20:1):放在第二个分片当中
- ( 2022-01-21 ~ 2022-01-30:2):放在第三个分片当中
- 问题:假如指定的这个分片字段的时间大于1月30号为1月31号,那么这条记录该如何路由呢????
- 答:如果配置了结束时间,当结束时间到达后它会重新在从头开始计算分片,那也就意味着1月31号到2月9号,这十天的数据会继续放到第一个分片
- dateFormat:日期格式
- sBeginDate:开始时间
- sEndDate:结束时间
- sPartionDay:周期,10代表10天为一个分片,按照开始时间和结束时间相差30天,也就意味着在这个范围内有3个分片。
注意事项:
- 从开始时间开始,每10天为一个分片,到达结束时间之后,会重复开始分片插入(也就是说会再从第一个分片开始插入)
- 我们在配置逻辑表的时候所指定的分片节点的数量为3个,那么在配置分片规则的时候,它所计算出来的分片数量也必须是3个。这个地方设置的1月1号到1月30号,每隔10天是一个分片,刚好是3个,这样就保证了上下是一致的,如果不一致会报错。
2).配置
schema.xml中逻辑表配置:
<!-- 按天分片 -->
<table name="tb_datepart" dataNode="dn4,dn5,dn6" rule="sharding-by-date" />
schema.xml中数据节点配置:在3.5.2水平分表中已经写过了
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml中分片规则配置:没有案例,自己定义
<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">2022-01-01</property>
<property name="sEndDate">2022-01-30</property>
<property name="sPartionDay">10</property>
</function>
分片规则属性含义:
属性 | 描述 |
---|---|
columns | 标识将要分片的表字段 |
algorithm | 指定分片函数与function的对应关系 |
class | 指定该分片算法对应的类 |
dateFormat | 日期格式 |
sBeginDate | 开始日期 |
sEndDate | 结束日期,如果配置了结束日期,则代码数据到达了这个日期的分片后,会重复从开始分片插入 |
sPartionDay | 分区天数,默认值 10 ,从开始日期算起,每个10天一个分区 |
3).测试
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_datepart只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
然后在mycat的命令行中
,执行如下SQL创建表、并插入数据,查看数据分布情况。
create table tb_datepart(
id bigint not null comment 'ID' primary key,
name varchar(100) null comment '姓名',
create_time date null
);
insert into tb_datepart(id,name ,create_time) values(1,'Tom','2022-01-01');
insert into tb_datepart(id,name ,create_time) values(2,'Cat','2022-01-10');
insert into tb_datepart(id,name ,create_time) values(3,'Rose','2022-01-11');
insert into tb_datepart(id,name ,create_time) values(4,'Coco','2022-01-20');
insert into tb_datepart(id,name ,create_time) values(5,'Rose2','2022-01-21');
insert into tb_datepart(id,name ,create_time) values(6,'Coco2','2022-01-30');
insert into tb_datepart(id,name ,create_time) values(7,'Coco3','2022-01-31');
效果:
3.5.3.9 自然月分片
1).介绍
使用场景为按照月份来分片, 每个自然月为一个分片。
注意事项:
- 从开始时间开始,一个月为一个分片,到达结束时间之后,会重复开始分片插入(也就是说会再从第一个分片开始插入)
- 我们在配置逻辑表的时候所指定的分片节点的数量为3个,那么在配置分片规则的时候,它所计算出来的分片数量也必须是3个。这个地方设置的1月1号到3月31号,每隔一个月是一个分片,刚好是3个,这样就保证了上下是一致的,如果不一致会报错。
2).配置
schema.xml中逻辑表配置:
<!-- 按自然月分片 -->
<table name="tb_monthpart" dataNode="dn4,dn5,dn6" rule="sharding-by-month" />
schema.xml中数据节点配置:在3.5.2水平分表中已经写过了
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml中分片规则配置:
<tableRule name="sharding-by-month">
<rule>
<columns>create_time</columns>
<algorithm>partbymonth</algorithm>
</rule>
</tableRule>
<function name="partbymonth" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2022-01-01</property>
<property name="sEndDate">2022-03-31</property>
</function>
分片规则属性含义:
属性 | 描述 |
---|---|
columns | 标识将要分片的表字段 |
algorithm | 指定分片函数与function的对应关系 |
class | 指定该分片算法对应的类 |
dateFormat | 日期格式 |
sBeginDate | 开始日期 |
sEndDate | 结束日期,如果配置了结束日期,则代码数据到达了这个日期的分片后,会重复从开始分片插入 |
3).测试
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
重新登录mycat,并切换到ITCAST数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
#显示的是ITCAST、SHOPPING,他是在schema.xml文件中配置的逻辑库
show databases;
use ITCAST;
#这个逻辑表tb_monthpart只是逻辑上存在的,在真实的数据库中并不存在,所以要进行创建
show tables;
然后在mycat的命令行中
,执行如下SQL创建表、并插入数据,查看数据分布情况。
create table tb_monthpart(
id bigint not null comment 'ID' primary key,
name varchar(100) null comment '姓名',
create_time date null
);
insert into tb_monthpart(id,name ,create_time) values(1,'Tom','2022-01-01');
insert into tb_monthpart(id,name ,create_time) values(2,'Cat','2022-01-10');
insert into tb_monthpart(id,name ,create_time) values(3,'Rose','2022-01-31');
insert into tb_monthpart(id,name ,create_time) values(4,'Coco','2022-02-20');
insert into tb_monthpart(id,name ,create_time) values(5,'Rose2','2022-02-25');
insert into tb_monthpart(id,name ,create_time) values(6,'Coco2','2022-03-10');
insert into tb_monthpart(id,name ,create_time) values(7,'Coco3','2022-03-31');
insert into tb_monthpart(id,name ,create_time) values(8,'Coco4','2022-04-10');
insert into tb_monthpart(id,name ,create_time) values(9,'Coco5','2022-04-30');
效果:
3.6 MyCat管理及监控
3.6.1 MyCat原理
在MyCat中,当执行一条SQL语句时,MyCat需要进行SQL解析、分片分析、路由分析、读写分离分析 等操作,最终经过一系列的分析决定将当前的SQL语句到底路由到那几个(或哪一个)节点数据库,数据 库将数据执行完毕后,如果有返回的结果,则将结果返回给MyCat,最终还需要在MyCat中进行结果合 并、聚合处理、排序处理、分页处理等操作,最终再将结果返回给客户端。
而在MyCat的使用过程中,MyCat官方也提供了一个管理监控平台MyCat-Web(MyCat-eye)。Mycat-web 是 Mycat 可视化运维的管理和监控平台,弥补了 Mycat 在监控上的空白。帮 Mycat 分担统计任务和配置管理任务。Mycat-web 引入了 ZooKeeper 作为配置中心,可以管理多个节点
。Mycat-web 主要管理和监控 Mycat 的流量、连接、活动线程和内存等,具备 IP 白名单、邮件告警等模块,还可以统计 SQL 并分析慢 SQL 和高频 SQL 等。为优化 SQL 提供依据。
3.6.2 MyCat管理(方式一:命令行)
Mycat默认开通2个端口,可以在server.xml中进行修改。
- 8066 数据访问端口,即进行 DML 和 DDL 操作。
- 9066 数据库管理端口,即 mycat 服务管理控制功能,用于管理mycat的整个集群状态
连接MyCat的管理控制台:
#之前连接的是8066数据访问端口,现在连接的是9066数据库管理端口
#这个密码是连接mycat的密码,之前在server.xml中配置的
mysql -h 192.168.10.210 -P 9066 -uroot -p123456
#注意要加分号
show @@help;
#之前修改配置文件后需要重启mycat才能生效,现在可以使用此命令直接加载配置文件生效。
reload @@config;
命令 | 含义 |
---|---|
show @@help | 查看Mycat管理工具帮助文档 |
show @@version | 查看Mycat的版本 |
reload @@config | 重新加载Mycat的配置文件 |
show @@datasource | 查看Mycat的数据源信息 |
show @@datanode | 查看MyCat现有的分片节点信息 |
show @@threadpool | 查看Mycat的线程池信息 |
show @@sql | 查看执行的SQL |
show @@sql.sum | 查看执行的SQL统计 |
说明:
- 以上这些都是使用命令行的方式来进行管理的,不方便查看。
- MyCat官方也提供了一个管理监控平台MyCat-Web(MyCat-eye)。
- 图形化界面的方式。
3.6.3 MyCat-eye(方式二:图形化界面)
3.6.3.1 介绍
Mycat-web(Mycat-eye)是对mycat-server提供监控服务,功能不局限于对mycat-server使用,通过它也可以去监控mysql。他通过JDBC连接对Mycat、Mysql监控,监控远程服务器(目前仅限于linux系统)的cpu、内 存、网络、磁盘。
Mycat-eye运行过程中需要依赖zookeeper,因此需要先安装zookeeper。
3.6.3.2 安装
- zookeeper安装
- Mycat-web安装
具体的安装步骤,请参考资料中提供的《MyCat-Web安装文档》
MyCat-web安装文档:安装Zookeeper、安装Mycat-web
3.6.3.3 访问
http://192.168.10.210:8082/mycat
3.6.3.4 配置
1).开启MyCat的实时统计功能(server.xml)
#进入到mycat的配置文件目录
cd /usr/local/src/MyCat/mycat/conf/
#直接在左侧打开也可以
vim server.xml
配置内容:
<property name="useSqlStat">1</property> <!-- 1为开启实时统计、0为关闭 -->
配置完毕后,重新启动MyCat
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
2).在Mycat监控界面配置服务地址
Mycat01
192.168.10.210
9066
8066
ITCAST
root
123456
3.6.3.5 测试
配置好了之后,我们可以通过MyCat执行一系列的增删改查的测试,然后过一段时间之后,打开mycat-eye的管理界面,查看mycat-eye监控到的数据信息。
A.性能监控
B.物理节点
C.SQL统计
D.SQL表分析
E.SQL监控
F.高频SQL
3.7 总结
4. 读写分离
4.1 介绍
读写分离的前提是基于主从复制的,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。
-
方式一:如果应用程序直接连接数据库,那么也就意味着应用程序需要操作两个数据源,执行写入操作连接的是主库,执行读操作连接的是从库。这样的话应用程序在操作时就比较繁琐,那么我们需要根据所执行的业务来决定到底要操作那个数据库,实现起来比较麻烦,
不推荐这种方式
。
-
方式二:通过MyCat即可轻易实现上述功能,不仅可以支持MySQL,也可以支持Oracle和SQL Server关系型数据库。
- 应用程序执行crud操作的时候只需要连接mycat,把sql语句发送给mycat,mycat在根据所执行的SQL语句,将sql语句路由到对应的数据库节点即可。
- 如果是增删改操作 它直接将sql语句路由到主节点,在主节点执行增删改之后,主节点的数据会同步到从节点,这样主从节点的数据就一致了。
- 如果是查询操作 它直接将sql语句路由到从节点,从节点执行查询操作,把数据查询后返回给应用程序即可
- 应用程序执行crud操作的时候只需要连接mycat,把sql语句发送给mycat,mycat在根据所执行的SQL语句,将sql语句路由到对应的数据库节点即可。
-
主要通过mycat给我们提供的2个组件来实现:
- writeHost:写入的节点是哪个
- readHost:读取的节点是那个
4.2 一主一从的搭建
之前已经学过了,这里再次复习下。
4.2.1 原理
MySQL的主从复制,是基于二进制日志(binlog)实现的。
4.2.2 准备
不在重新创建2台虚拟机了,直接使用之前学习主从复制的2台虚拟机。
还用到了一个分片1的虚拟机,因为要使用mycat。
主机 | 角色 | 用户名 | 密码 |
---|---|---|---|
192.168.10.200 | master | root | 1234 |
192.168.10.201 | slave | root | 1234 |
备注:主从复制的搭建,可以参考前面课程中 主从复制 章节讲解的步骤操作。
博客地址:日志,主从复制章节
4.2.3 测试:主从复制环境是否搭建成功
在主库中执行以下sql操作:
mysql -uroot -p1234
create database itcast;
use itcast;
create table tb_user(
id int(11) not null,
name varchar(50) not null,
sex varchar(1),
primary key (id)
)engine=innodb default charset=utf8;
insert into tb_user(id,name,sex) values(1,'Tom','1');
insert into tb_user(id,name,sex) values(2,'Trigger','0');
insert into tb_user(id,name,sex) values(3,'Dawn','1');
查看从库:发现从库也有主库创建的库和表以及表中的数据,说明主从环境搭建成功。
mysql -uroot -p1234
show databases;
use itcast;
show tables;
select * from tb_user;
4.3 一主一从读写分离
MyCat控制后台数据库的读写分离和负载均衡由schema.xml文件datahost标签的balance属性控制。
配置的是mycat当中的配置文件
4.3.1 schema.xml配置
cd /usr/local/src/MyCat/mycat/conf/
配置内容
<!-- 配置逻辑库 :原先dataNode是加在定哟逻辑表的标签上,现在没有定义逻辑表,这个属性
可以写在逻辑库的标签上-->
<schema name="ITCAST_RW" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn7">
</schema>
<dataNode name="dn7" dataHost="dhost7" database="itcast" />
<!--host属性值任意,但是尽量上下2个不要一样-->
<dataHost name="dhost7" maxCon="1000" minCon="10" balance="3"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.10.200:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234">
<readHost host="salve" url="jdbc:mysql://192.168.10.201:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234">
</readHost>
</writeHost>
</dataHost>
上述配置的具体关联对应情况如下:
writeHost代表的是写操作对应的数据库,readHost代表的是读操作对应的数据库。 所以我们要想实现读写分离,就得配置writeHost关联的是主库,readHost关联的是从库。
注意:
- 这个地方声明了逻辑库之后并没有指定对应的逻辑表,在读写分离的结构中逻辑表可以不指定,当然指定也没有问题。如果不指定它会自动的去加载所在的数据节点对应的数据库,然后去读取这个数据库当中所有的表结构作为逻辑表。
而仅仅配置好了writeHost以及readHost还不能完成读写分离,还需要配置一个非常重要的负责均衡的参数 balance(默认是0),取值有4种,具体含义如下:配置1或者3都可以实现读写分离
参数值 | 含义 |
---|---|
0 | 不开启读写分离机制 , 所有读操作都发送到当前可用的writeHost上,也就是说即使你配置了readHost也不生效 |
1 | 全部的readHost 与 备用的writeHost 都参与select 语句的负载均衡(主要针对于双主双从模式) |
2 | 所有的读写操作都随机在writeHost , readHost上分发,并不会读写分离 |
3 | 所有的读请求随机分发到writeHost对应的readHost上执行, writeHost不负担读压力 只负担写压力 |
所以,在一主一从模式的读写分离中,balance配置1或3都是可以完成读写分离的。
4.3.2 server.xml配置
配置root用户可以访问SHOPPING、ITCAST 以及 ITCAST_RW逻辑库。
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<!--root用户访问的逻辑库是哪个-->
<property name="schemas">SHOPPING,ITCAST,ITCAST_RW</property>
<!--没有配置说明既能读又能写-->
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="true">
<schema name="DB01" dml="1110" >
<table name="TB_ORDER" dml="1110"></table>
</schema>
</privileges>
-->
</user>
<user name="user">
<property name="password">123456</property>
<!--user用户访问的逻辑库是哪个-->
<property name="schemas">SHOPPING</property>
<!--配置只能够读-->
<property name="readOnly">true</property>
</user>
4.3.3 测试
1)配置完毕MyCat后,重新启动MyCat。
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先停止
bin/mycat stop
#在启动
bin/mycat start
#在mycat目录下查看日志
tail -f logs/wrapper.log
2)登录mycat,并切换到ITCAST_RW数据库
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
show databases;
use ITCAST_RW;
#显示tb_user逻辑表,我们并没有在schema.xml中配置逻辑表,那么这个逻辑表是哪来的呢???
# 它是dn7这个dataNode数据节点对应的itcast数据库,这个数据库下面有那些表
# 这个地方就会展示出来。
show tables;
3)然后观察,在执行增删改操作时,对应的主库及从库的数据变化。 在执行查询操作时,检查主库及从库对应的数据变化。
在mycat中执行
#执行查询操作
select * from tb_user;
#执行插入操作
insert into tb_user(id,name,sex) values (4,"aaaaa",0);
#再次查询:那么这个时候有没有实现读写分离呢?????
# 单纯的通过数据我们是没有办法判断的,因为当我们在进行查询的时候,由于主库和从库当中的数据是
# 一致的,所以查询出来的数据你无法判断查的是主库还是从库当中的数据。
select * from tb_user;
---------------------------------------------------------------------------------
在mysql从库中执行
#解决:因为主库的数据会同步到从库,而从库的数据不会同步到主库,所以我们可以更新从库当中的一个
# 数据比如name值为Tom的字段修改为Tom1,此时从库当中的数据是Tom1,主库当中的数据依然是Tom,
# 这样在查询的时候就知道到底查询的是哪个节点数据库了。
mysql -uroot -p1234
use itcast;
show tables;
select * from tb_user;
update tb_user set name="Tom1" where id=1;
---------------------------------------------------------------------------------
在mycat中接着执行
#这个时候查询的结果为Tom1,说明查询的是从库,实现读写分离了。
select * from tb_user;
#执行插入操作:发现主库和从库都有这条数据,说明走的是主节点
# 原因:因为主节点有数据所以它走的就是主节点,如果他插入的是从节点
# 从节点的数据是不会同步到主数据库的,主数据库也就没有这条数据,
# 所以插入操作走的是主节点,实现了读写分离。
insert into tb_user(id,name,sex) values (5,"bbbb",1);
在mycat中直接查询,无法区分查询的是主库还是从库当中的数据。
在mysql从库中,修改Tom为Tom1。
在mycat中再次执行查询操作,这个时候查询的结果为Tom1,说明查询的是从库,实现读写分离了。
主库和从库当中都有这条数据,如果他插入的是从节点,从节点的数据是不会同步到主数据库的,主数据库也就没有这条数据,所以插入操作走的是主节点,实现了读写分离。
4)在测试中,我们可以发现当主节点Master宕机之后,业务系统就只能够读,而不能写入数据了。
在主库中停止mysql服务,模拟主节点Master宕机
systemctl stop mysqld
systemctl status mysqld
此时在mycat中只能做查询操作,不能做增删改操作了。
(我自己测试在从库中关闭了mysql服务之后,在mycat中查询和更新操作都报错了
)
那如何解决这个问题呢?这个时候我们就得通过另外一种主从复制结构来解决了,也就是我们接下来讲解的双主双从。
4.4 双主双从的搭建
4.4.1 介绍
一个主机 Master1 用于处理所有写
请求,它的从机 Slave1 和另一台主机 Master2 还有它的从机 Slave2 负责所有读
请求。当 Master1 主机宕机后,Master2 主机负责写
请求,Master1 、Master2 互为备机。架构图如下:
- m1:主库1
- s1:主库1的从库
- m2:主库2
- s2:主库2的从库
- 为了保证这2个主从结构有关系,双主双从的结构指的是m1和m2会相互复制。也就是说m1中的数据变更会同步到m2,m2中的数据变更也会同步给m1,这样就可以保证m1、s1、m2、s2这4台数据库服务器里面的数据是一致的。
- 当写入数据到m1之后,那么其它的3个节点的数据会跟着变化。
- 当写入数据到m2之后,那么其它的3个节点的数据也会跟着变化。
- 因为配置了主从复制,2个主库之间又相互复制。
4.4.2 准备
我们需要准备5台服务器,具体的服务器及软件安装情况如下:
编号 | IP | 预装软件 | 角色 |
---|---|---|---|
1 | 192.168.10.210 | MyCat、MySQL | MyCat中间件服务器 |
2 | 192.168.10.200 | MySQL | M1 |
3 | 192.168.10.201 | MySQL | S1 |
4 | 192.168.10.202 | MySQL | M2 |
5 | 192.168.10.203 | MySQL | S2 |
- 其中MyCat中间件服务器在学习分库分表时候已经准备过了
- 准备其它的4台虚拟机,都安装了mysql,并且关闭了防火墙。
关闭以上所有服务器的防火墙:
- systemctl stop firewalld
- systemctl disable firewalld
4.4.3 搭建
这个地方修改的是mysql中的配置文件
4.4.3.1 主库配置
1). Master1(192.168.10.200)
A.修改配置文件 vim /etc/my.cnf
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,默认为1
server-id=1
#指定同步的数据库,也就是说指定的这几个数据库会进行主从复制,别的数据库不会进行主从复制的。
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
# 当前节点在作为从数据库的时候,有写入操作也要更新二进制日志文件
# 因为这个从库它需要从这个主库当中读取二进制日志文件,然后完成主从复制,所以也需要加上这个参数。
log-slave-updates
B.重启MySQL服务器
systemctl restart mysqld
C.创建账户并授权:
mysql -uroot -p1234
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
#为 'itcast'@'%' 用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%';
通过指令,查看两台主库的二进制日志坐标
show master status ;
2). Master2(192.168.10.202)
A.修改配置文件 vim /etc/my.cnf
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,默认为1
server-id=3
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
B.重启MySQL服务器
systemctl restart mysqld
C.创建账户并授权
这个地方因为是直接复制的一主一从的虚拟机,所以账户和授权已经配置过了,创建重复的账号会报错。
mysql -uroot -p1234
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
#为 'itcast'@'%' 用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%';
通过指令,查看两台主库的二进制日志坐标
show master status ;
4.4.3.2 从库配置
1). Slave1(192.168.10.201)
A.修改配置文件 vim /etc/my.cnf
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 232-1,默认为1
server-id=2
B.重新启动MySQL服务器
systemctl restart mysqld
2). Slave2(192.168.10.203)
A.修改配置文件 vim /etc/my.cnf
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 232-1,默认为1
server-id=4
B.重新启动MySQL服务器
systemctl restart mysqld
4.4.3.3 从库关联主库
1).两台从库配置关联的主库
需要注意slave1对应的是master1,slave2对应的是master2。
A. 在 slave1(192.168.10.201)上执行
mysql -uroot -p1234
# 指定主库1的信息
CHANGE MASTER TO MASTER_HOST='192.168.10.200', MASTER_USER='itcast',MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000003',MASTER_LOG_POS=663;
#开启同步操作
start slave;
#查看从库状态
show slave status \G;
B. 在 slave2(192.168.10.203)上执行
mysql -uroot -p1234
# 指定主库2的信息
CHANGE MASTER TO MASTER_HOST='192.168.10.202', MASTER_USER='itcast',MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000004',MASTER_LOG_POS=663;
#开启同步操作
start slave;
#查看从库状态
show slave status \G;
2).两台主库相互复制
Master2 复制 Master1,Master1 复制 Master2。
A. 在 Master1(192.168.10.200)上执行
mysql -uroot -p1234
#指定Master2的连接信息
CHANGE MASTER TO MASTER_HOST='192.168.10.202', MASTER_USER='itcast',MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000005',MASTER_LOG_POS=156;
#开启同步操作
start slave;
#查看从库状态
show slave status \G;
B. 在 Master2(192.168.10.202)上执行
mysql -uroot -p1234
#指定Master1的连接信息
CHANGE MASTER TO MASTER_HOST='192.168.10.200', MASTER_USER='itcast',MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000003',MASTER_LOG_POS=663;
#开启同步操作
start slave;
#查看从库状态
show slave status \G;
经过上述的三步配置之后,双主双从的复制结构就已经搭建完成了。 接下来,我们可以来测试验证一下。
4.4.4 测试
分别在两台主库Master1、Master2上执行DDL、DML语句,查看涉及到的数据库服务器的数据同步情况。
- 在Master1中执行DML、DDL操作,看看数据是否可以同步到另外的三台数据库中。
- 在Master2中执行DML、DDL操作,看看数据是否可以同步到另外的三台数据库中。
在主库Master1上执行:
create database db01;
use db01;
create table tb_user(
id int(11) not null primary key ,
name varchar(50) not null,
sex varchar(1)
)engine=innodb default charset=utf8mb4;
insert into tb_user(id,name,sex) values(1,'Tom','1');
insert into tb_user(id,name,sex) values(2,'Trigger','0');
insert into tb_user(id,name,sex) values(3,'Dawn','1');
insert into tb_user(id,name,sex) values(4,'Jack Ma','1');
insert into tb_user(id,name,sex) values(5,'Coco','0');
insert into tb_user(id,name,sex) values(6,'Jerry','1');
效果:此时m1,s1,m2,s2都有数据
在主库Master2上执行:
create database db02;
use db02;
create table tb_user(
id int(11) not null primary key ,
name varchar(50) not null,
sex varchar(1)
)engine=innodb default charset=utf8mb4;
insert into tb_user(id,name,sex) values(1,'Tom','1');
insert into tb_user(id,name,sex) values(2,'Trigger','0');
insert into tb_user(id,name,sex) values(3,'Dawn','1');
insert into tb_user(id,name,sex) values(4,'Jack Ma','1');
insert into tb_user(id,name,sex) values(5,'Coco','0');
insert into tb_user(id,name,sex) values(6,'Jerry','1');
效果:此时m1,s1,m2,s2同样都有数据
完成了上述双主双从的结构搭建之后,接下来,我们再来看看如何完成这种双主双从的读写分离。
4.5 双主双从读写分离
在mycat的配置文件中进行配置
#配置文件都是存放在mycat解压后的conf目录
cd /usr/local/src/MyCat/mycat/conf/
4.5.1 配置
MyCat控制后台数据库的读写分离和负载均衡由schema.xml文件datahost标签的balance属性控制,通过writeType及switchType来完成失败自动切换的。
1).schema.xml
配置逻辑库:
<schema name="ITCAST_RW2" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn7">
</schema>
配置数据节点:
<dataNode name="dn7" dataHost="dhost7" database="db01" />
配置节点主机:
<!--还需要配置balance属性,-->
<dataHost name="dhost7" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!--配置主库1从库1的数据库连接信息-->
<writeHost host="master1" url="jdbc:mysql://192.168.10.200:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234">
<readHost host="salve1" url="jdbc:mysql://192.168.10.201:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234">
</readHost>
</writeHost>
<!--配置主库2从库2的数据库连接信息-->
<writeHost host="master2" url="jdbc:mysql://192.168.10.202:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234">
<readHost host="salve2" url="jdbc:mysql://192.168.10.203:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="1234">
</readHost>
</writeHost>
</dataHost>
具体的对应情况如下:
属性说明:
- balance=“1”
- 代表全部的 readHost (读主机:s1、m2、s2)与 stand by writeHost (备用写主机:m1)参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,S1、M2、S2 都参与 select 语句的负载均衡,而m1负责
写入
操作,这样就完成了读写分离 ; - 解释:balance为1代表的就是在双主双从的模式下进行负载均衡,在双主双从的模式下m1负责
写入
操作,而其它的s1、m2、s2负责读取
操作。
- 代表全部的 readHost (读主机:s1、m2、s2)与 stand by writeHost (备用写主机:m1)参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,S1、M2、S2 都参与 select 语句的负载均衡,而m1负责
- writeType
- 0 :
写
操作都转发到第1台writeHost,writeHost1挂了, 会切换到writeHost2上; - 1 : 所有的
写
操作都随机地发送到配置的writeHost上 ;- writeHost:用来配置主数据库的连接信息。
- 0 :
- switchType
- -1 : 不自动切换
- 1 : 自动切换
- 指的是writeHost1挂掉了之后会不会自动切换到writeHost2。
2).server.xml
配置root用户也可以访问到逻辑库 ITCAST_RW2。
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<!--root用户访问的逻辑库是哪个-->
<property name="schemas">SHOPPING,ITCAST,ITCAST_RW2</property>
<!--没有配置说明既能读又能写-->
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="true">
<schema name="DB01" dml="1110" >
<table name="TB_ORDER" dml="1110"></table>
</schema>
</privileges>
-->
</user>
<user name="user">
<property name="password">123456</property>
<!--user用户访问的逻辑库是哪个-->
<property name="schemas">SHOPPING</property>
<!--配置只能够读-->
<property name="readOnly">true</property>
</user>
具体的对应情况如下:
修改完成配置后重启mycat:
#注意不能到bin目录下
cd /usr/local/src/MyCat/mycat/
#先把之前启动的mycat 关闭掉
bin/mycat stop
#启动
bin/mycat start
#在mycat目录下查看日志,是否启动成功
tail -f logs/wrapper.log
4.5.2 测试
登录MyCat,测试查询及更新操作,判定是否能够进行读写分离,以及读写分离的策略是否正确。
1)登录MyCat
:
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
show databases;
use ITCAST_RW2;
#显示tb_user,同一主一从一样虽然没有配置逻辑表,但是它会把dn7这个数据节点对应的数据库db01
# 下的所有表作为逻辑表显示。db01数据库此时只有tb_user一张表,所以这个地方显示的是tb_user作为逻辑表。
show tables;
#直接查询因为现在4个数据库(m1 s1 m2 s2)中的数据是一样的,所以无无区分查询的是哪一个数据库
#和之前学习一主一从一样,同样可以通过修改从库进行测试
select * from tb_user;
2)修改从库
中的数据
因为m1(主库1)的数据会同步给s1(从库1),m2(主库2)的数据会同步给s2(从库2)。反过来s1(从库1)的数据不会同步给m1(主库1),s2(从库2)的数据不会同步给m2(主库2)。所以此时可以修改s1和s2的字段,这样主库和从库的数据就不相同了,再次执行查询就知道查询的是主库还是从库了。
在从库1上执行
mysql -uroot -p1234
use db01;
show tables;
select * from tb_user;
#此时从库1的字段值为TomS1
update tb_user set name="TomS1" where id=1;
-----------------------------------------------------
在从库2上执行
mysql -uroot -p1234
use db01;
show tables;
select * from tb_user;
#此时从库2的字段值为TomS2
update tb_user set name="TomS2" where id=1;
3)在mycat
中再次进行查询:
- 如果查询到的字段是TomS1说明数据来自从库1,如果字段是TomS2说明数据来自从库2,如果查询到的字段是Tom说明数据来自主库1或者主库2。
- 因为配置了双主双从模式,实际上查询的Tom是来自于主库2的,但是主库1和主库2的字段都是Tom所以无法区分,也不能修改主库2的字段,因为主库1和主库2会相互复制,里面的数据是一样的。
select * from tb_user;
4)在mycat
中进行插入操作:
- 双主双从模式下m1是写入(增删改)操作,s1、m2、s2都是读取(查询)操作。
- 如果是在主节点1中插入数据的,他会把数据同步给从库1、主库2、从库2中,此时4个节点的数据库都会有这条数据。说明数据是在主库1或者主库2中插入的,实现了读写分离。
- 因为在主库1或者主库2中插入数据,这4个节点都会有数据,所以只看数据无法区分插入的是主库1还是主库2,只能区分是在主库上插入还是在从库中插入。
- 如果是在从库中插入数据的,从库中的数据不会同步给主库,所以此时主库中没有这条数据,也就没有实现读写分离。
insert into tb_user(id,name,sex) values (7,"AAAAA",0);
5)当主库挂掉一个之后,是否能够自动切换。
在主库1
中停止mysql服务,模拟主节点Master1宕机
#在主库1中执行,不需要登录mysql
#停止mysql服务
systemctl stop mysqld
#查看mysql状态
systemctl status mysqld
在mycat
中执行,查询和插入操作:
#登录mycat后执行
mysql -h 192.168.10.210 -P 8066 -uroot -p123456
use ITCAST_RW2;
show tables;
#可以进行查询
#正常情况:主库1是写入操作,从库2 主库2 从库2负责读取操作
#主库1挂掉之后:主库2负责写入操作,从库2负责读取操作
#当然查询操作走的是从库1 主库2 从库2,主库1挂掉之后影响的是增删改,所以主库1挂不挂掉都不影响查询操作,
# 没办法区分是否实现了高可用。
SELECT * FROM tb_user;
#可以进行插入
#主库1已经挂了,所以此时插入是的主库2,之后同步给从库2。
#这个时候主库2和从库2有这条记录,主库1 和从库1没有这条记录
#主库1挂掉之后影响的是是增删改 也就是说不能在进行增删改操作了,但是现在执行插入操作任然可以成功说明
# 他把主库2变为写入操作了(增删改),从库2进行读取操作,也就实现了高可用。
insert into tb_user(id,name,sex) values (8,"BBBBB",1);
效果:
- 主库1:主库1挂了直接就连不上数据库,里面当然没有这条数据了
- 从库1:可以看到没有这条记录
- 主库2:有这条记录
- 从库2:有这条记录
- 当master1挂掉之后master会自动联系上来,这样就保证了数据库的高可用。
4.6 读写分离总结
运维篇总结
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)