1 数据分片
- 当一个数据库实例写操作过于频繁,很难由一台主机支撑时,我们可以将存放在同一个数据库实例中的数据分散存放到多个数据库实例中,从而提升性能
- 数据分片方式
- 水平切分(分表):将大表按某个字段的某种规则分散到多个节点库中,每个节点中包含一部分数据
- 垂直切分(分库):一个数据库由很多表构成,每个表对应不同的业务,垂直切分是指按照业务将表进行分类并分不到不同的节点上
- 混合切分:水平切分+垂直切分
- 数据分片原则
- 能不切分尽量不要切分
- 如果要切分一定要选择合适的切分规则,提前规划好
- 数据切分尽量通过数据冗余(同一份数据放到不同库中)或表分组来降低跨库join的可能
- 由于数据库中间件对数据join实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表join。
2 分布式查询
分布式查询处理的任务就是把一个分布式数据库上的高层次查询,转换为在本地数据库上的操作,将要查询的数据定位到各节点,使得查询在各节点进行,最后通过网络通信的操作汇聚查询结果
3 关系型数据库和NoSQL
- 关系型数据库:建立在关系模型基础上的数据库,其借助于集合代数等数学概念和方法来处理数据库中的数据,现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示,主流的是oracle、db2、sql server、mysql
- NOSQL(Not Only SQL):对不同于传统的关系数据库的数据库管理系统的统称,允许部分资料使用SQL系统存储,而其他资料允许使用NOSQL系统存储,其数据存储可以不需要固定的表格模式以及元数据,也经常会避免使用SQL的JOIN操作,一般有水平可扩展性的特征
3 mycat简介
3.1 定义
- 对于DBA:mycat是MySQL,而Mycat后面连接的MySQL就好比MySQL的存储引擎,如InnoDB、MyISAM等。mycat本身并不存储数据,数据是在后端的MySQL上存储的,因此数据可靠性以及事务都是MySQL保证的
- 对于开发人员:mycat是一个近似等于MySQL的数据库服务器,你可以用连接MySQL的方式去连接mycat,除了端口不同(mycat为8066,mysql为3306),大多数情况下,可以通过你熟悉的OR Mapping框架来访问mycat,但建议对于大的分片表,尽量使用基础的SQL语句,因为这样能达到最佳性能
- 对于架构师:mycat是一个强大的数据库中间件,可以用于读写分离、分库分表、容灾备份、多租户应用开发、云平台基础设施等,让你的架构具备很强的适应性和灵活性,借助于即将发布的mycat智能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或手工调整后端存储,将不同的表映射到不同的存储引擎上,而整个应用的代码一行也不用改变
3.2 原理
- mycat拦截了用户发送过来的SQL语句,首先对SQL语句进行一些特定的分析,例如分片分析、路由分析、读写分离分析、缓存分析等,然后将此SQL路由到后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户
3.3 应用场景
- 其实公司很少使用mycat的分库分表功能,一般只用到其主从复制和读写分离功能
3.4 数据库中间件对比
对比项目 | mycat | mango | cobar | heisenberg | altas | amoeba |
---|---|---|---|---|---|---|
数据切片 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 |
读写分离 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 |
宕机自动切换 | 支持 | 不支持 | 支持 | 不支持 | 半支持,影响写 | 不支持 |
mysql协议 | 前后端支持 | JDBC | 前端支持 | 前后端支持 | 前后端支持 | JDBC |
支持的数据库 | mysql,oracle,mongodb,postgresql | mysql | mysql | mysql | mysql | mysql,mongodb |
社区活跃度 | 高 | 活跃 | 停滞 | 低 | 中等 | 停滞 |
文档资料 | 极丰富 | 较齐全 | 较齐全 | 较少 | 中等 | 缺少 |
是否开源 | 开源 | 开源 | 开源 | 开源 | 开源 | 开源 |
是否支持事务 | 弱XA | 支持 | 单库强一致,分布式弱事务 | 单库强一致,多库弱事务 | 单库强一致,分布弱事务 | 不支持 |
4 mycat测试环境搭建
- 四台虚拟机,操作系统:centos6.5,内存:1g,cpu核数:1,硬盘:100g
4.1 mysql5.7.29安装
-
安装wget
yum install wget -y
-
更换yum源
#以下命令通过访问https://developer.aliyun.com/mirror/,选择centos中可以查找到 #备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup #替换 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo #1. 2020年11月30日,centos6各大开源镜像站已经停止维护了 #2. 因此会报错http://mirrors.aliyun.com/centos/6/os/x86_64/repodata/repomd.xml: [Errno 14] PYCURL ERROR 22 - "The requested URL returned error: 404 Not Found",直接通过浏览器访问这个文件,发现根本不存在,这个6实际上是读取的当前centos的大版本号,只好将该文件中使用$releasever变量的地方,都改成7,如果原来/etc/yum.repos.d/CentOS-Base.repo中有好多文件,可以先全部删除后重新配置,否则下面步骤中可能还会访问http://mirrors.aliyun.com/centos/6/os/x86_64/repodata/repomd.xml #3. 但删除后,在安装Openssl时,会报错,因为使用的软件和当前操作系统大版本不匹配,因此可以尝试使用其他网站的yum源 #4. curl -o /etc/yum.repos.d/CentOS-Base.repo https://www.xmpan.com/Centos-6-Vault-Aliyun.repo vi /etc/yum.repos.d/CentOS-Base.repo :0,$s/$releasever/7/gi #清理安装包 yum clean all #生成缓存 yum makecache
-
查看系统中是否自带了mysql
yum list installed | grep mysql
-
删除系统自带的mysql及其依赖(防止冲突)
yum -y remove mysql-libs.x86_64
-
下载包含mysql的yum源的rpm包
wget dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm
-
使用yum安装rpm包
#rpm :只能安装已经下载到本地机器上的rpm包,无法解决软件包的依赖关系 #yum:在线下载并安装rpm包,能更新系统,能自动处理包与包之间的依赖问题 yum install mysql-community-release-el6-5.noarch.rpm -y
-
修改mysql-community.repo文件
#将5.6的enabled=1改为enabled=0,5.7的改为enabled=1 vi /etc/yum.repos.d/mysql-community.repo
-
使用yum安装mysql
yum install mysql-community-server -y
-
启动mysql服务并设置开机启动
#1. 启动之前需要生成临时密码,需要用到证书,可能证书过期,需要进行更新操作 yum update -y #2. 启动mysql服务 service mysqld start #3. 设置mysql开机启动 chkconfig mysqld on
-
获取mysql的临时密码
#2020-12-20T18:40:45.644971Z 1 [Note] A temporary password is generated for root@localhost: XPfho+KM<7y# #其中XPfho+KM<7y#就是临时密码 grep "password" /var/log/mysqld.log
-
使用临时密码登录
mysql -uroot -p #输入临时密码XPfho+KM<7y#
-
修改密码
set global validate_password_policy=0; set global validate_password_length=1; ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';
-
修改远程访问权限
grant all privileges on *.* to 'root'@'%' identified by '123456' with grant option; flush privileges;
-
设置字符集为utf-8
vi /etc/my.cnf #在[mysqld]下一行添加 character-set-server=utf8 #在文件末尾新增[client]段,并在[client]段下一行添加 default-character-set=utf8 #重启mysql service mysqld restart #查看字符集是否生效,两个参数内容中基本都是utf8就说明配置成功 mysql -uroot -p123456 show variables like 'char%'; show variables like 'collation%';
4.2 mysql卸载
-
查看mysql相关的rpm包
rpm -qa | grep -i mysql
-
删除所有rpm包
rpm -ev mysql-community-libs-5.7.27-1.el6.x86_64 --nodeps
-
查找和mysql相关的文件
find / -name mysql
-
删除全部文件
rm -rf /var/lib/mysql rm -rf /var/lib/mysql/mysql rm -rf /etc/logrotate.d/mysql rm -rf /usr/share/mysql rm -rf /usr/bin/mysql rm -rf /usr/lib64/mysql
-
检查是否有内容未删除干净
find / -name mysql rpm -qa | grep -i mysql
4.2 mycat安装
-
下载安装包:http://dl.mycat.org.cn/1.6.7.5/2020-4-10/
-
安装jdk
-
解压
tar -zxvf Mycat-server-1.6.7.5-release-20200410174409-linux.tar.gz -C /usr/local
-
配置环境变量
vi /etc/profile export MYCAT_HOME=/usr/local/mycat export PATH=$MYCAT_HOME/bin:$PATH:$JAVA_HOME/bin export PATH=$PATH:$MYCAT_HOME/bin source /etc/profile
-
修改/etc/hosts
192.168.246.128 node01 192.168.246.129 node02 192.168.246.130 node03 192.168.246.131 node04
4.3 mycat配置
-
进入
/usr/local/mycat/conf
目录 -
修改server.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mycat:server SYSTEM "server.dtd"> <mycat:server xmlns:mycat="http://io.mycat/"> <!-- user标签:定义登录mycat的用户和权限 property标签:设置具体的属性值 password:密码,之后就可以通过mysql -uroot -p123456 -P 8066 -h 192.168.246.128登录mycat schemas:控制客户可访问的schema,如果有多个可用逗号分隔 privileges标签:更精细的设置mycat用户对schema中的表的权限 system标签:设置字符集、线程数、全局序列类型等 --> <user name="root" defaultAccount="true"> <property name="password">123456</property> <!--2. 指定mycat中有哪些逻辑库--> <property name="schemas">TESTDB</property> <property name="defaultSchema">TESTDB</property> </user> </mycat:server>
-
修改schema.xml
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <!-- schema:定义mycat的逻辑库 name:逻辑库名 checkSQLschema:如果为true,那么当连接mycat的客户端执行语句select * from TESTDB.user;,mycat会自动将"TESTDB."去掉,防止发送到后端真正的mysql数据库中,由于mysql中没有TESTDB库而报错,但如果语句为select * from AAA.user;即语句中带的不是schema指定的名,那么不会将其去掉,因此该值建议设置为false sqlMaxLimit:连接mycat进行查询时,默认只返回前100条,即默认加一个limit 100,如果语句中本身使用了limit,那么此处配置失效 randomDataNode:语句随机分配到一个分片节点 dataNode:分片节点,用于绑定逻辑库到某个具体的database上 --> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> </schema> <!-- dataNode:指定分片节点,分片节点:数据切分后,一个大表被分到不同的分片数据库中,每个表分片所在的数据库就是分片节点 name:分片节点名 dataHost:指定该分片节点所在节点主机名。节点主机:分片节点所在的机器 database:指定分片节点属于节点主机的哪个数据库,如果对应节点主机上没有这个数据库,使用mycat连接TESTDB时会超时,可以在mysql数据库上先执行create database msb; --> <dataNode name="dn1" dataHost="host1" database="msb" /> <!-- name:节点主机名 maxcon:指定每个读写实例连接池的最大连接 mincon:指定每个读写实例连接连接池的最小链接,初始化连接池的大小 balance:负载均衡类型,后面会具体讲解 writeType:写类型,后面会具体讲解 dbType:指定后端连接的数据库类型 dbDriver:连接后段数据库所使用的driver,可选择native和JDBC,如果连接的数据库是mysql或maridb,可以设置为native,如果为其他数据库,需要设置为JDBC switchType:是否进行主从切换,后面会具体讲解 --> <dataHost name="host1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <!--指定检测mycat所连接的数据库实例是否宕机所用的语句--> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.246.128:3306" user="root" password="123456"> <readHost host="hostS1" url="192.168.246.128:3306" user="root" password="123456"></readHost> </writeHost> </dataHost> </mycat:schema>
-
启动mycat
#1. 控制台启动,如果配置文件写错可以很方便看到报错信息 mycat console #2. 后台启动 mycat start #3. 查看状态 mycat status
-
客户端连接mycat
#-P:指定端口:9066为管理端口,8066为业务端口 #-h:指定连接的mycat服务器地址 mysql -uroot -p123456 -P 8066 -h 192.168.246.128
-
测试
--发现只有一个TESTDB库,这是个逻辑库 show databases; --使用这个逻辑库 use TESTDB; --如果连接的为管理端口,显示不支持该语句 --如果连接的为业务端口,那么可以看到该逻辑库对应的真实的数据库中(msb)中的表 show tables; --查看可执行的命令 show @@help;
4.4 mycat日志
/usr/local/mycat/logs/mycat.log
5 主从复制与读写分离
主机名 | IP地址 |
---|---|
node01 | 192.168.246.128 |
node02 | 192.168.246.129 |
node03 | 192.168.246.130 |
node04 | 192.168.246.131 |
5.1 一主一从
- node01和node02上各有一个mysql实例
- node01为主,node02为从
- 测试前最好将四个节点上都创建msb库,或都删除msb库,防止不统一造成的不一致
5.1.1 搭建
-
node01:vi /etc/my.cnf
#[mysqld]下配置 #1. mysql服务唯一id,不同的mysql服务必须拥有全局唯一的id,不能为0,0表示拒绝任何连接 server-id=1 #2. 启动binlog log-bin=mysql-bin #3. 其实前两个配置完,就已经可以了 #4. 设置不复制的数据库 binlog-ignore-db=mysql binlog-ignore-db=information-schema #5. 设置复制的数据库 binlog-do-db=msb #6. 设置binlog的格式:row、statement、mixed binlog_format=statement
-
node02:vi /etc/my.cnf
#[mysqld]下配置 server-id=2 #启动relay log relay-log=mysql-relay
-
重启node01和node02的mysql服务
service mysqld restart
-
node01:创建账户并授予replication slave权限
--1. 建立用户 CREATE USER 'root'@'%' IDENTIFIED BY '123456'; --2. 授权 grant replication slave on *.* to 'root'@'%' ; --3. 以上两句可以合并为1句 grant replication slave on *.* to 'root'@'%' identified by '123456'; --4. 建立用户时,如果提示密码太简单,可以修改密码安全级别 set global validate_password_policy=0; set global validate_password_length=1;
-
获得master binlog复制点
--node1上执行,会显示binlog日志名、复制点等信息 show master status;
-
node02:指定需要复制的主机
--MASTER_LOG_POS:表示从日志中哪个位置开始读 --MASTER_LOG_FILE:binlog日志名 --这两个值来源于上面show master status; CHANGE MASTER TO MASTER_HOST='192.168.246.128',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=1640;
-
node02:启动复制功能
--node02上执行 start slave;
-
node02:查看从服务器状态
--1. Slave_IO_Running和Slave_SQL_Running状态都为Yes就表示主从复制已正常开启 --2. 如果有状态是No需要在日志/var/log/mysqld.log中查看报错信息 --3. node02重置slave:stop slave;reset slave; --3. node01重置master:reset master;该动作会删除之前所有的binlog日志 show slave status\G;
5.1.2 验证
-
创建数据库
--注意建立主从复制关系之前,需要node01和node02上都没有msb这个数据库,假如node01上有,node02上没有,那么在node01上执行create时,node01会报错,因为无法重新创建数据库,而如果想先在node01上drop,node02会报错,因为node02上还没有数据库,而且由于在node02还原node01上的操作失败,会导致主从复制关系断开,即Slave_SQL_Running变为No,无法继续同步 --1. 在node01上创建数据库 create database msb; --2. 在node01上创建具体的表 create table mytbl(id int,name varchar(20)); --3. 在node01上插入数据 insert into mytbl values(1,'zhangsan'); --4. 在node02上验证发现数据已经同步成功,表示主从复制完成
-
插入语句
- 主插入语句,从能看到,但从插入语句主看不到
- 如果某表存在唯一索引,当从库插入后,主库再插入可以插入成功,但从库这边会由于违反唯一索引导致同步失败,此时Slave_SQL_Running变为No,后续同步也无法继续
- 因此从库是不能提供写入服务的
5.1.3 数据不一致解决方案
- 将从库数据全部清除
- 从库重新建立主从关系,注意MASTER_LOG_POS值为第一次建立主从关系时的值,而不是主上面的最新值
5.1.4 mycat配置读写分离
-
node01执行
--1. 下面语句会将主机名插入数据库中,当使用statement策略时,会按原来语句插入,因此node01执行该语句时,name列为node01,node02上还原该语句时,name列会插入node02 insert into mytbl values(2,@@hostname); --2. 但此时我们如果通过mycat进行数据的访问,无论怎么查询数据,最终返回的name列值都是node01,说明每次读请求都发送到了node01这个主上,还未实现读写分离 select * from mytbl;
-
修改schema.xml
<!-- banlance属性值解析 1. 0:不开启读写分离,读写都发到writeHost 2. 1:适用于双主双从模式,其中一个writeHost提供写的功能,剩下的writeHost和所有的readHost一起提供读的功能 3. 2:读操作随机在writeHost和readHost上分发 4. 3:在writeHost上写,readHost上读 --> <dataHost name="host1" maxCon="1000" minCon="10" balance="2" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.246.128:3306" user="root" password="123456"> <readHost host="hostS1" url="192.168.246.129:3306" user="root" password="123456"></readHost> </writeHost> </dataHost>
-
重启mycat
-
测试
insert into mytbl values(3,@@hostname); --将balance配置为2后,发现name列的值随机出现node01和node02 select * from mytbl;
5.2 双主双从
- node01、node02、node03、node04上各有一个mysql实例
- 其实就是主备模式,且为主和备各提供一个从机,目的是解决单点故障问题,平时主提供写的功能,从+备提供读的功能,当主宕机后,备提供写的功能,从+原主提供读的功能
- node01为主,node03为备,node02和node04为从
5.2.1 搭建
-
node01:vi /etc/my.cnf
server-id=1 log-bin=mysql-bin binlog_format=STATEMENT #1. 当前库作为从库,同步了主库的binlog后,会将同步来数据也记入binlog #2. 如果不使用该参数,只是通过log-bin=mysql-bin开启binlog,那么只有直接向该库写入数据时,才会记录binlog,从主库同步来的数据是不会写binlog,这样,其他节点就无法以该库作为主库同步binlog #3. 虽然node01是主库,但他是有可能变为备库的,所以他本质上也会从其他库同步数据,因此也需要开启此参数 log-slave-updates #1. 以下两个参数用于解决如果两个主都提供写的功能时,如果某字段为auto_increment,那么可能会导致的冲突问题 #2. auto-increment-increment:每次递增的值,默认为1,取值范围可以从1~65535 #3. auto-increment-offset:初始值,取值范围可以从1~65535 #4. node01配置为auto-increment-offset=1,auto-increment-increment=2,表示字段值从1开始,每次递增2,因此就是1、3、5、7,node02上配置auto-increment-offset=2,auto-increment-increment=2,表示字段值从2开始,每次递增2,这样就是2、4、6、8,这样就不会产生冲突了 auto-increment-increment=2 auto-increment-offset=1
-
node03:vi /etc/my.cnf
server-id=3 log-bin=mysql-bin binlog_format=STATEMENT log-slave-updates auto-increment-increment=2 auto-increment-offset=2
-
node02:vi /etc/my.cnf
server-id=2 #启用中继日志,日志名为mysql-relays relay-log=mysql-relay
-
node04:vi /etc/my.cnf
server-id=4 relay-log=mysql-relay
-
所有节点重启mysql服务:
service mysqld restart
-
node01、node03:授予root用户replication slave权限
grant replication slave on *.* to 'root'@'%' ;
-
node01、node03:查看master状态,如果之前存在主从复制需要重置master
reset master; show master status;
-
node02、node04:指定master
--node02 CHANGE MASTER TO MASTER_HOST='192.168.246.128',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=154; --node04 CHANGE MASTER TO MASTER_HOST='192.168.246.130',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=154;
-
node02、node04:启动slave,并查看slave状态
start slave; show slave status\G;
-
node01跟node03相互复制
--node01 CHANGE MASTER TO MASTER_HOST='192.168.246.130',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=154; start slave; show slave status\G; --node03 CHANGE MASTER TO MASTER_HOST='192.168.246.128',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=154; start slave; show slave status\G;
5.2.2 验证
-
node01
--都执行完毕后,到其他节点上是否同步成功 create database msb; use msb; create table mytbl(id int,name varchar(20)); insert into mytbl values(1,'zhangsan');
5.2.3 mycat配置读写分离
-
schema.xml
<!-- 1. balance设置为1,表示一个writeHost提供写,剩下的writeHost和所有readHost提供读 2. writeType:配置到底哪个writeHost进行写 1. 0:所有写操作都默认发送到配置的第一个writeHost,如果第一个挂了,切换到第二个writeHost上,同时将写操作默认发送到的writeHost记录到dnindex.properties文件中 2. 1:所有写操作都随机的发送到配置的writehost中,1.5之后废弃 3. switchType:配置切换主备的方式 1. 1:默认值,表示自动切换 2. -1:表示不自动切换 3. 2:基于mysql主从同步的状态决定是否切换 --> <dataHost name="host1" 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.246.128:3306" user="root" password="123456"> <readHost host="hostS1" url="192.168.246.129:3306" user="root" password="123456"></readHost> </writeHost> <writeHost host="hostM2" url="192.168.246.130:3306" user="root" password="123456"> <readHost host="hostS2" url="192.168.246.131:3306" user="root" password="123456"></readHost> </writeHost> </dataHost>
-
连接mycat并测试
insert into mytbl values(4,@@hostname); --查询到的结果在node02,node03,node04之间切换 select * from mytbl; --停止node01的mysql,恢复后重新插入语句 insert into mytbl values(5,@@hostname); --查询到的结果在node01,node02,node04之间切换,说明node03已经变为了主,而node01变为了备,查看dnindex.properties文件,host1的值也由0变为了1 select * from mytbl;
5.3 基于GTID的主从复制
5.3.1 简介
- GTID:Global Transaction ID,是Master上已提交事务在集群中的唯一编号,主从同步时,可以通过GTID确定slave节点当前同步到的位置,GTID使用
master_auto_position=1
代替了基于master_log_file
和master_log_pos
的主从复制构建方式,强化了数据库的主从一致性、故障恢复及容错能力 - 在GTID出现以前,如果node01、node02互为主备,node03为node01的从机,那么一旦node01宕机,node02就应该作为新的主机,而node03应该改为从node02同步数据,但由于同一个事务在每台机器上所在的binlog名字和位置都不一样,我们很难找到node03当前同步停止点对应node02上的
master_log_file
和master_log_pos
,改为使用基于GTID的主从复制后,node03会根据自身的GTID自动找node02上该GTID之后的数据进行同步 - GTID = UUID+TID
- UUID:mysql实例的唯一标识
- TID:该实例上已经提交的事务数量,并且随着事务提交单调递增
5.3.2 搭建
-
node01:主机,node02:从机
-
node01、node02:vi /etc/my.cnf
gtid_mode=on enforce-gtid-consistency=true
-
node01、node02:重启服务
service mysql restart
-
node02:指定master
stop slave; reset slave; change master to master_host='192.168.246.128',master_user='root',master_password='123456' ,master_auto_position=1; start slave;
-
node01:插入数据测试
create table test(id int,name varchar(10)); insert into test values(1,'handidiao'); insert into test values(2,'xuexiansheng');
-
show slave status \G
解析*************************** 1. row *************************** #1. io线程状态:所有master产生的事件都同步完毕,等master继续发送事件 Slave_IO_State: Waiting for master to send event #2. master的ip、用户、端口 Master_Host: 192.168.246.128 Master_User: root Master_Port: 3306 #3. 重连时间 Connect_Retry: 60 #4. binlog名和当前位置,每执行一个dml语句,该值都变化 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 795 #5. relay log名和当前位置,每执行一个dml语句,该值都变化 Relay_Log_File: mysql-relay.000002 Relay_Log_Pos: 1008 #6. 当前机器产生的binlog名 Relay_Master_Log_File: mysql-bin.000001 #7. IO线程和SQL线程状态 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 795 Relay_Log_Space: 1211 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: #8. 主从复制延迟时间:主库执行完成一个事务,写入binlog,这个时间为T1,之后传给从库,从库接收完这个binlog的时间为T2,从库执行完成这个事务,我们把这个时刻记为T3,该值就是T3-T1,一般我们通过该值判断主从延迟的时间,单位为秒 Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1 Master_UUID: f2856797-42ec-11eb-b9c5-000c299b3760 Master_Info_File: /var/lib/mysql/master.info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: f2856797-42ec-11eb-b9c5-000c299b3760:1-3 Executed_Gtid_Set: f2856797-42ec-11eb-b9c5-000c299b3760:1-3 Auto_Position: 1 Replicate_Rewrite_DB: Channel_Name: Master_TLS_Version: 1 row in set (0.00 sec)
-
binlog日志解析
--连接mysql后,可以通过下面语句,查看binlog中记录的内容,发现每次操作,binlog中都会更新GTID的值 --1. Log_name:日志名 --2. Pos:执行当前事件前的pos --3. Event_type:binlog的事件的类型 -- a. Previous_gtids:因为binlog大小达到指定值之后,会生成新的文件来保存日志,该值用于当前binlog的上一个binlog的最后一个GTID值 -- b. Gitd:修改GITD值 -- c. Xid:提交 -- d. Query:执行的语句 --4. End_log_pos:执行当前事件后的pos --5. Info:事件描述 show binlog events in 'mysql-bin.000001';
5.4 组提交信息查看
-
组提交信息会被保存在GTID事件中,可以通过mysqlbinlog命令格式化binlog日志并查看组提交相关信息
-
进入mysql的binlog路径
cd /var/lib/mysql
-
格式化binlog日志
#last_committed:上一次提交的最大LSN,该值相同的几个语句属于同一组,这些语句在从库恢复时,就可以并行执行 #sequence_number:LSN值,每个语句该值增加1 mysqlbinlog --no-defaults mysql-bin.000001 #只看组提交相关的信息 mysqlbinlog --no-defaults mysql-bin.000001|grep last_committed
-
即使不开启GTID,组提交信息会放在数据库自己建立的Anonymous_Gtid中
-
模拟多个线程批量插入数据库,再格式化binlog日志观察,发现有语句会在同一组进行提交,同一组中的语句
sequence_number
递增,last_committed
相同package com.mashibing; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Date; public class ConCurrentInsert extends Thread{ public void run() { String url = "jdbc:mysql://192.168.85.111/lian2"; String name = "com.mysql.jdbc.Driver"; String user = "root"; String password = "123456"; Connection conn = null; try { Class.forName(name); conn = DriverManager.getConnection(url, user, password);//获取连接 conn.setAutoCommit(false);//关闭自动提交,不然conn.commit()运行到这句会报错 } catch (Exception e1) { e1.printStackTrace(); } // 开始时间 Long begin = new Date().getTime(); // sql前缀 String prefix = "INSERT INTO t1 (id,age) VALUES "; try { // 保存sql后缀 StringBuffer suffix = new StringBuffer(); // 设置事务为非自动提交 conn.setAutoCommit(false); // 比起st,pst会更好些 PreparedStatement pst = (PreparedStatement) conn.prepareStatement("");//准备执行语句 // 外层循环,总提交事务次数 for (int i = 1; i <= 10; i++) { suffix = new StringBuffer(); // 第j次提交步长 for (int j = 1; j <= 10; j++) { // 构建SQL后缀 suffix.append("(" +i*j+","+i*j+"),"); } // 构建完整SQL String sql = prefix + suffix.substring(0, suffix.length() - 1); // 添加执行SQL pst.addBatch(sql); // 执行操作 pst.executeBatch(); // 提交事务 conn.commit(); // 清空上一次添加的数据 suffix = new StringBuffer(); } // 头等连接 pst.close(); conn.close(); } catch (SQLException e) { e.printStackTrace(); } // 结束时间 Long end = new Date().getTime(); // 耗时 System.out.println("100万条数据插入花费时间 : " + (end - begin) / 1000 + " s"+" 插入完成"); } public static void main(String[] args) { for (int i = 1; i <=10; i++) { new ConCurrentInsert().start(); } } }
6 分库分表
6.1 分库
-
在node01和node03上都使用mysql创建orders库
-
schema.xml
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <!-- 1. table:定义逻辑表 2. name:逻辑表表名 3. dataNode:定义逻辑表所属的dataNode,这个值需要和下面dataNode标签中的name名对应,如果需要定义的dataNode过多,可以通过如下方式减少配置 <table name="travelrecord" dataNode="multipleDn$0-99,multipleDn2$100-199" rule="auto-shardinglong" ></table> <dataNode name="multipleDn$0-99" dataHost="localhost1" database="db$0-99" ></dataNode> <dataNode name="multipleDn2$100-199" dataHost="localhost1" database=" db$100-199" ></dataNode> 4. rule:用于指定逻辑表要使用的规则名字,必须与rule.xml中tableRule标签中的name属性值一一对应 5. ruleRequired:如果该值为true,但没有配置具体rule,启动报错 6. primaryKey:配置该逻辑表对应真实表的主键,假如分片的规则中使用非主键进行分片,那么在使用主键查询的时候,就会发送查询语句到所有配置的dataNode上,但如果使用该属性,那么mycat会缓存主键与具体dataNode的映射,那么再次使用主键进行查询的时候就不会进行广播式的查询,会直接发送语句到具体的dataNode,尽管配置该属性,但如果缓存没有命中,还是会发送语句给所有的dataNode来获得数据 7. type:可以配置其值为global,表示全局表,即在所有dataNode都存放一份 8. needAddLimit:和schema中的sqlMaxLimit属性作用相同,都是加上limit 100 --> <!--默认在分片节点dn1中执行语句,但如果涉及到customer表,在dn2这个分片节点中执行语句--> <!--该customer为逻辑表,mycat启动时,实际上在node01和node03中,该逻辑表对应的物理表还不存在--> <table name = "customer" dataNode="dn2"></table> </schema> <dataNode name="dn1" dataHost="host1" database="orders" /> <dataNode name="dn2" dataHost="host2" database="orders"/> <!--注意128和130没有主备和主从的关系,他们是两个主,此处为了节省虚拟机资源,没有对128和130配置主从或主备--> <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.246.128: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.246.130:3306" user="root" password="123456"></writeHost> </dataHost> </mycat:schema>
-
连接mycat并执行如下建表语句
--1. 客户表 CREATE TABLE customer( id INT AUTO_INCREMENT, NAME VARCHAR(200), PRIMARY KEY(id) ); --2. 订单表 CREATE TABLE orders( id INT AUTO_INCREMENT, order_type INT, customer_id INT, amount DECIMAL(10,2), PRIMARY KEY(id) ); --3. 订单详细表 CREATE TABLE orders_detail( id INT AUTO_INCREMENT, detail VARCHAR(2000), order_id INT, PRIMARY KEY(id) ); --4. 订单状态字典表 CREATE TABLE dict_order_type( id INT AUTO_INCREMENT, order_type VARCHAR(200), PRIMARY KEY(id) );
-
发现customer表建立在node03上,其他表建立在node01上,这就是分库功能
-
经查看,发现node03上的customer表为大写,而node01中的表都为小写,这是因为mycat中创建逻辑表对应的物理表时,默认为大写,而在linux上搭建的mysql默认是大小写敏感的,因此会导致使用mycat时,必须通过大写的CUSTOMER访问该表,通过小写的customer访问会失败
-
可以将mysql配置为大小写不敏感,这样连接mycat时,无论通过大写还是小写都能访问customer表
#[mysqld]下添加 lower_case_table_names=1
6.2 分表
6.2.1 PartitionByMod:取模
-
分片方法:mycat会用插入数据的customer_id%count(2),根据得到的结果,插入不同的节点
-
schema.xml
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <table name = "customer" dataNode="dn2"></table> <!-- 配置orders表的分片规则 dataNode:表示将表拆分到哪些分片节点中,配置两个就表示分表 rule:分片的规则,需要在rule.xml中配置 --> <table name ="orders" dataNode="dn1,dn2" rule="mod_rule"></table> </schema>
-
确定node01和node02都有表orders
-
schema.xml:注意mycat不支持tableRule、function、tableRule、function这种交叉配置的方式,tableRule必须放在一起,剩下的function放在一起,否则启动会报错。同时不允许出现name相同的tableRule或function
<tableRule name="mod_rule"> <rule> <!--1. 表示对列customer_id进行运算--> <columns>customer_id</columns> <!--2. 定义使用的算法--> <algorithm>mod-long</algorithm> </rule> </tableRule> <!--3. 定义算法对应的类和节点数,PartitionByMod表示取模运算--> <function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!--4. 该值表示数据节点数量--> <property name="count">2</property> </function>
-
重启mycat
-
测试:使用mycat插入分片表时,必须指定列名,否则报错
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(1,101,100,100100); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(2,101,100,100300); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(3,101,101,120000); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(4,101,101,103000); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(5,102,101,100400); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(6,102,100,100020);
6.2.2 PartitionByFileMap:枚举
-
分片方法:mycat会根据areacode列值的不同,以及配置文件中指定的值与分片节点对照关系,决定将数据插入哪个分片节点
-
schema.xml
<!--1. dataNode中内容可以简写为:dataNode="dn$1-2"--> <table name="orders_ware_info" dataNode="dn1,dn2" rule="sharding_by_intfile" ></table>
-
rule.xml
<tableRule name="sharding_by_intfile"> <rule> <!--1. 表示对列areacode进行运算--> <columns>areacode</columns> <algorithm>hash-int</algorithm> </rule> </tableRule> <function name="hash-int" class="io.mycat.route.function.PartitionByFileMap"> <!--mapFile:指定配置文件路径--> <property name="mapFile">partition-hash-int.txt</property> <!--type:指定分片字段的类型,0为int,非0为String--> <property name="type">1</property> <!--defaultNode:如果配置文件中不包含指定值,将数据放入默认分片节点中,小于0表示不设置默认节点,大于等于0表示默认分片节点的索引,由于该表配置的dataNode="dn1,dn2",所以0对应的就是dn1--> <property name="defaultNode">0</property> </function>
-
partition-hash-int.txt
#110放到dn1,120放到dn2 110=0 120=1
-
重启mycat
-
测试
--mycat执行show tables能看到这个表,但此处看到的该表是逻辑表,所以必须先执行建表语句,保证物理表被建立 CREATE TABLE orders_ware_info ( `id` INT AUTO_INCREMENT comment '编号', `order_id` INT comment '订单编号', `address` VARCHAR(200) comment '地址', `areacode` VARCHAR(20) comment '区域编号', PRIMARY KEY(id) ); INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (1,1,'北京','110'); INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (2,2,'天津','120');
6.2.3 AutoPartitionByLong:范围
-
分片方法:mycat会根据order_id列值的不同,以及配置文件autopartition-long.txt中指定的范围与分片节点对照关系,决定将数据插入哪个分片节点
-
schema.xml文件
<table name="payment_info" dataNode="dn1,dn2" rule="auto_sharding_long" ></table>
-
rule.xml
<tableRule name="auto_sharding_long"> <rule> <columns>order_id</columns> <algorithm>rang-long</algorithm> </rule> </tableRule> <function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong"> <property name="mapFile">autopartition-long.txt</property> <property name="defaultNode">0</property> </function>
-
autopartition-long.txt
#可以在数字后面加字母K或M,K=1000,M=10000 0-102=0 103-200=1
-
重启mycat
-
测试
CREATE TABLE payment_info ( `id` INT comment '编号', `order_id` INT comment '订单编号', `payment_status` INT comment '支付状态', PRIMARY KEY(id) ); INSERT INTO payment_info (id,order_id,payment_status) VALUES (1,101,0); INSERT INTO payment_info (id,order_id,payment_status) VALUES (2,102,1); INSERT INTO payment_info (id,order_id ,payment_status) VALUES (3,103,0); INSERT INTO payment_info (id,order_id,payment_status) VALUES (4,104,1);
6.2.4 PartitionByRangeMod:范围+求模
-
分片方法:mycat会根据id列值的不同,以及配置文件autopartition-long.txt中指定的范围,决定将数据放到哪个分片节点组中,再根据id列值%该组中分片节点个数的值,决定放到该组的具体哪个分片节点中
-
schema.xml
<table name="person" primaryKey="id" dataNode="dn1,dn2" rule="auto-sharding-rang-mod"></table>
-
rule.xml
<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">partition-range-mod.txt</property> <property name="defaultNode">0</property> </function>
-
partition-range-mod.txt
#1. 此处将dataNode分为两组,每组均有1个dataNode,如果为0-1M=2,1M1-2M=3,就相当于分为两组,前两个dataNode一组,后三个dataNode一组 #2. 将id列按范围不同,先决定放到哪组中 #3. 然后用id%组中dataNode个数,根据得到的值,决定放到该组的哪个dataNode中 #4. 0-1M:表示数据范围 #5. 1:表示该组分片节点个数 #6. 1+1必须等于schema.xml配置的该表的dataNode总数,否则启动报错 0-1M=1 1M1-2M=1
-
重启mycat
-
测试
CREATE TABLE `person` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into person(id,name) values(9999,'zhangsan1'); insert into person(id,name) values(10000,'zhangsan2'); insert into person(id,name) values(10001,'zhangsan3'); insert into person(id,name) values(20000,'zhangsan4');
-
该方法综合了范围分片和取模分片的优点,又解决了他们在部分场景下的缺点
- 取模分片缺点:例如之前的数据按取模分片,分到两个分片节点,那么一旦想新增一个分片节点,就会导致原来的数据由%2改为%3,那么数据全部需要重新插入,但使用范围求模分片,如果新插入的数据能保证一定大于原来指定的范围,那么新定义一个范围,该范围对应一个新的节点组,那么原节点组中的数据完全不用变动
- 范围分片的缺点:可能某范围数据特别多,而另一个范围的数据特别少,使用范围求模分片,对某个大范围中的数据,进一步分片,让他们均匀的分散到几个不同的dataNode中,从而避免热点数据问题
6.2.5 PartitionByLong:求模+范围
-
分片方法:感觉官网玩了个文字游戏,其实可以理解为就是范围求模反过来,求模范围算法,mycat将id对1024取模,根据得到的值,放到0-255、256-511、512-1023三个分片节点中
-
schema.xml
<table name="user" primaryKey="id" dataNode="dn1,dn2,dn3" rule="rule1"></table>
-
rule.xml
<tableRule name="rule1"> <rule> <columns>id</columns> <algorithm>func1</algorithm> </rule> </tableRule> <function name="func1" class="io.mycat.route.function.PartitionByLong"> <!--1. sum((partitionCount[i]*partitionLength[i]))必须等于1024--> <!--2. 先用id%1024,得到的结果,0-255放到第一个分片节点,256-511放到第二个分片节点,512到1023放到第三个分片节点--> <property name="partitionCount">2,1</property> <property name="partitionLength">256,512</property> </function>
-
重启mycat
-
测试
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into user(id,name) values(1023,'zhangsan1'); insert into user(id,name) values(1024,'zhangsan2'); insert into user(id,name) values(266,'zhangsan3'); insert into user(id,name) values(255,'zhangsan4');
6.2.6 PartitionByPattern:求模+范围
-
分片方法:mycat用id对256取模,根据得到的值,放到0-86、87-173、174-255三个分片节点中,其实和固定分片hash算法一样,只不过后者对1024取模再根据范围分片,前者可以对指定值取模,再根据自己指定的范围分片
-
schema.xml
<table name="user2" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-pattern"></table>
-
rule.xml
<!-- patternValue:对256取模,这样得到的值的范围一定在0-255,之后根据partition-pattern.txt指定的范围,将对256取模后的值,放入不同的分片节点 --> <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="patternValue">256</property> <property name="defaultNode">0</property> </function>
-
partition-pattern.txt
#1. 因为patternValue为256,所以此处值最多应该到255,官网给的案例有问题,虽然256配置的是到2对应的分片节点,但实际上对256取模,永远不可能等于256 #2. 如果配置的范围中不包含所有的0-255,那么不在文件中的部分默认放到defaultNode中 0-86=0 87-173=1 174-256=2
-
重启mycat
-
测试
--此处id字段不要设置为AUTO_INCREMENT,该参数mysql的一个特性,会影响对测试结果的判断 CREATE TABLE `user2` ( `id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into user2(id,name) values(85,'zhangsan1'); insert into user2(id,name) values(100,'zhangsan2'); insert into user2(id,name) values(200,'zhangsan3');
6.2.7 PartitionByPrefixPattern:hash+求模+范围
-
分片方法:mycat先计算name列的前5位字符的ASCII和,然后用这个和对256取模,根据得到的值,放到0-86、87-173、174-255三个分片节点中。其实就是求模范围算法,只不过增加了对字符串的支持
-
schema.xml
<table name="user3" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-prefixpattern"></table>
-
rule.xml
<tableRule name="sharding-by-prefixpattern"> <rule> <columns>name</columns> <algorithm>sharding-by-prefixpattern</algorithm> </rule> </tableRule> <function name="sharding-by-prefixpattern" class="io.mycat.route.function.PartitionByPrefixPattern"> <property name="mapFile">partition-pattern.txt</property> <property name="patternVAlue">256</property> <!--prefixLength:表示将name列的前几位拿出来计算hash值--> <property name="prefixLength">5</property> </function>
-
partition-pattern.txt
0-86=0 87-173=1 174-256=2
-
重启mycat
-
测试
CREATE TABLE `user3` ( `id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into user3(id,name) values(1,'a'); insert into user3(id,name) values(2,'b'); insert into user3(id,name) values(3,'c'); insert into user3(id,name) values(4,'d'); insert into user3(id,name) values(5,'e'); --V的ascii为86,W为87,所以一个放在第一个分片节点,一个放在第二个分片节点 insert into user3(id,name) values(6,'V'); insert into user3(id,name) values(7,'W'); --A+l=65+108=173,A+m=65+109=174,所以一个放在第二个分片节点,一个放在第三个分片节点 insert into user3(id,name) values(8,'Al'); insert into user3(id,name) values(9,'Am');
6.2.8 PartitionDirectBySubString:应用指定
-
分片方法:由应用程序制定放入哪个分片,mycat截取name的第一个字母,如果为0放到第一个分片节点,1放到第二个,2放到第三个
-
schema.xml
<table name="user4" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-substring"></table>
-
rule.xml
<tableRule name="sharding-by-substring"> <rule> <columns>name</columns> <algorithm>sharding-by-substring</algorithm> </rule> </tableRule> <function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString"> <!--name.substr(startIndex,size)来获取分片节点索引--> <property name="startIndex">0</property> <property name="size">1</property> <property name="partitionCount">3</property> <property name="defaultPartition">0</property> </function>
-
重启mycat
-
测试
CREATE TABLE `user4` ( `id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; --0开头就放到第一个分片节点,1放到第二个,2放到第三个 insert into user4(id,name) values(1,'0-zhangsan'); insert into user4(id,name) values(2,'1-lisi'); insert into user4(id,name) values(3,'2-wangwu');
6.2.9 PartitionByString:hash+求模+范围
-
分片方法:mycat通过id.substr(0,6)得到字符串,对该字符串计算hash值,然后将得到的hash值%(partitionLength*partitionCount),根据得到的值的范围放到不同的分片节点
-
schema.xml
<table name="user5" primaryKey="id" dataNode="dn1,dn2" rule="sharding-by-stringhash"></table>
-
rule.xml
<tableRule name="sharding-by-stringhash"> <rule> <columns>id</columns> <algorithm>sharding-by-stringhash</algorithm> </rule> </tableRule> <!-- hashslice: hash预算位,就是截取id的哪部分来计算hash值,下面为配置的值的含义举例 1. 0:6 :id.substr(0,6) 2. 1: :id.substr(1,0) 3. -1: :id.substr(-1,0) 4. : :id.substr(0,0) partitionLength*partitionCount必须为1024,否则启动报错 --> <function name="sharding-by-stringhash" class="io.mycat.route.function.PartitionByString"> <property name="partitionLength">512</property> <property name="partitionCount">2</property> <property name="hashSlice">0:6</property> </function>
-
重启mycat
-
测试
CREATE TABLE `user5` ( `id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into user5(id,name) values(1111111,database()); insert into user5(id,name) values(2222222,database()); insert into user5(id,name) values(3333333,database()); insert into user5(id,name) values(4444444,database()); insert into user5(id,name) values(8960000,database());
6.2.10 PartitionByDate:日期范围
-
分片方法:login_date从sBeginDate开始,每sPartionDay天数放到一个分片节点中
-
schema.xml
<table name="login_info" dataNode="dn1,dn2" rule="sharding_by_date" ></table>
-
rule.xml
<tableRule name="sharding_by_date"> <rule> <columns>login_date</columns> <algorithm>shardingByDate</algorithm> </rule> </tableRule> <!-- dateFormat :日期格式 sBeginDate :开始日期 sPartionDay :分区天数,从开始日期算起,分隔2天一个分区,但由于只配置了两个分片节点dn1、dn2,因此最多能插入到2020-06-04 sEndDate:结束日期,当设置了sEndDate,那么则代表数据达到了这个日期的分片后,重新从开始分片插入,此时就能插入2020-06-05的数据了,会放到dn1中 --> <function name="shardingByDate" class="io.mycat.route.function.PartitionByDate"> <property name="dateFormat">yyyy-MM-dd</property> <property name="sBeginDate">2020-06-01</property> <property name="sEndDate">2020-06-04</property> <property name="sPartionDay">2</property> </function>
-
重启mycat
CREATE TABLE login_info ( `id` INT comment '编号', `user_id` INT comment '用户编号', `login_date` date comment '登录日期', PRIMARY KEY(id) ); INSERT INTO login_info(id,user_id,login_date) VALUES (1,101,'2020-06-01'); INSERT INTO login_info(id,user_id,login_date) VALUES (2,102,'2020-06-02'); INSERT INTO login_info(id,user_id,login_date) VALUES (3,103,'2020-06-03'); INSERT INTO login_info(id,user_id,login_date) VALUES (4,104,'2020-06-04'); INSERT INTO login_info(id,user_id,login_date) VALUES (5,103,'2020-06-05'); INSERT INTO login_info(id,user_id,login_date) VALUES (6,104,'2020-06-06');
6.2.11 LatestMonthPartion:小时范围
-
分片方法:mycat用一天24h/splitOneDay,得到每8小时一个分片,因此一个月的数据想正常插入,至少需要31*3=93个分片,当日期跨月,重新从开始分片插入
-
schema.xml
<table name="user6" dataNode="dn1,dn2,dn3" rule="sharding-by-hour"></table>
-
rule.xml
<tableRule name="sharding-by-hour"> <rule> <columns>create_time</columns> <algorithm>sharding-by-hour</algorithm> </rule> </tableRule> <!-- splitOneDay:一天切分的分片数,也就是8小时一个分片,但由于我们只有3个分片节点,因此只能插入每个月第一天的数据 --> <function name="sharding-by-hour" class="io.mycat.route.function.LatestMonthPartion"> <property name="splitOneDay">3</property> </function>
-
重启mycat
-
测试
create table user6( id int not null, name varchar(64), create_time varchar(10) ); --1. 0点到7点存在dn1,8点-15点存在dn2,16点-23点存放到dn3 insert into user6(id,name,create_time) values(1,'steven','2020060100'); insert into user6(id,name,create_time) values(1,'steven','2020060107'); insert into user6(id,name,create_time) values(1,'steven','2020060108'); insert into user6(id,name,create_time) values(1,'steven','2020060115'); insert into user6(id,name,create_time) values(1,'steven','2020060116'); insert into user6(id,name,create_time) values(1,'steven','2020060123'); --2. 第二个月的数据,会从开始分片插入 insert into user6(id,name,create_time) values(1,'steven','2020070100'); --3. 因为只有3个分片节点,即3*8小时=24小时,因此只能插入一天的数据,第二天的数据插入报错 insert into user6(id,name,create_time) values(1,'steven','2020060223');
6.2.12 PartitionByRangeDateHash:日期范围+hash
-
分片方法:mycat从sBeginDate开始,每sPartionDay天放到一个分片组中,之后根据每组中分片节点的个数groupPartionSize,按一定hash算法(不使用日期%分片组中节点个数,是因为防止数据集中问题),将数据放到指定的分片节点中。思想类似范围求模算法,也是综合了范围和求模(hash)二者的优点。要求日期格式尽量精确,不然达不到局部均匀的目的
-
schema.xml
<table name="user7" dataNode="dn1,dn2,dn3" rule="rangeDateHash"></table>
-
rule.xml
<tableRule name="rangeDateHash"> <rule> <columns>create_time</columns> <algorithm>range-date-hash</algorithm> </rule> </tableRule> <!-- sPartionDay:代表多少天的数据放到一组中 groupPartionSize:表示每组分片节点个数 --> <function name="range-date-hash" class="io.mycat.route.function.PartitionByRangeDateHash"> <property name="sBeginDate">2020-06-01 00:00:00</property> <property name="sPartionDay">3</property> <property name="dateFormat">yyyy-MM-dd HH:mm:ss</property> <property name="groupPartionSize">1</property> </function>
-
重启mycat
-
测试
create table user7( id int not null, name varchar(64), create_time varchar(20) ); insert into user7(id,name,create_time) values(1,'steven','2020-06-01 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-02 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-03 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-04 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-05 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-06 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-07 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-08 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-09 00:00:00'); --每3天放到1组,1组有1个分片节点,即1个分片节点可以存放3天的数据,我们有3个分片节点,因此最多存储9天的数据,因此后面的插入会报错 insert into user7(id,name,create_time) values(1,'steven','2020-06-10 00:00:00'); insert into user7(id,name,create_time) values(1,'steven','2020-06-11 00:00:00');
6.2.13 PartitionByHotDate:冷热数据
-
分片方法:从当前系统日期计算,近sLastDay天,以及之后的日期,作为热点数据,放到第一个分片节点中,而不在这范围内的数据,作为冷数据,每sPartionDay天放入一个新的分片节点中
-
schema.xml
<table name="user8" dataNode="dn1,dn2,dn3" rule="sharding-by-hotdate" />
-
rule.xml
<tableRule name="sharding-by-hotdate"> <rule> <columns>create_time</columns> <algorithm>sharding-by-hotdate</algorithm> </rule> </tableRule> <!-- sLastDay:表示当前日期到当前日期-10这个范围的数据为热点数据,放到第一个分片节点中,注意如果插入的日期大于当前日期,也放入到第一个分片节点 sPartionDay:冷数据每30天放入一个分片节点 --> <function name="sharding-by-hotdate" class="io.mycat.route.function.PartitionByHotDate"> <property name="dateFormat">yyyy-MM-dd</property> <property name="sLastDay">10</property> <property name="sPartionDay">30</property> </function>
-
重启mycat
-
测试
CREATE TABLE user8(create_time timestamp NULL ON UPDATE CURRENT_TIMESTAMP ,`db_nm` varchar(20) NULL); --放到dn1 INSERT INTO user8 (create_time,db_nm) VALUES ('2020-12-24', database()); INSERT INTO user8 (create_time,db_nm) VALUES ('2020-12-25', database()); INSERT INTO user8 (create_time,db_nm) VALUES ('2020-12-15', database()); --放到dn2:因为12-14已经超过了近10天 INSERT INTO user8 (create_time,db_nm) VALUES ('2020-12-14', database()); INSERT INTO user8 (create_time,db_nm) VALUES ('2020-11-15', database()); --放到dn3:因为距离12-14已经超过了30天 INSERT INTO user8 (create_time,db_nm) VALUES ('2020-11-14', database()); --无法放入:因为只有3个分片节点 INSERT INTO user8 (create_time,db_nm) VALUES ('2020-10-15', database());
6.2.14 PartitionByMonth:自然月
-
分片方法:从sBeginDate开始,每个月的数据放入一个分片节点
-
schema.xml
<table name="user9" dataNode="dn1,dn2,dn3" rule="sharding-by-month" />
-
rule.xml
<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">2019-01-01</property> </function>
-
重启mycat
-
测试
CREATE TABLE user9(id int,name varchar(10),create_time varchar(20)); --因为按月分片,只有3个分片节点,因此最多存放3个自然月数据,因此从2019-05-01开始全无法插入 insert into user9(id,name,create_time) values(111,'zhangsan','2019-01-01'); insert into user9(id,name,create_time) values(111,'zhangsan','2019-02-01'); insert into user9(id,name,create_time) values(111,'zhangsan','2019-03-01'); insert into user9(id,name,create_time) values(111,'zhangsan','2019-05-01'); insert into user9(id,name,create_time) values(111,'zhangsan','2019-07-01'); insert into user9(id,name,create_time) values(111,'zhangsan','2019-09-01'); insert into user9(id,name,create_time) values(111,'zhangsan','2019-11-01');
6.2.15 PartitionByMurmurHash:一致性hash
-
分片方法:
-
一致性hash算法参考redis中的讲解,采用一致性hash算法可以解决扩容问题,在扩容时只需迁移部分数据而不是全部数据。但是横向扩容的过程中是以一种因式分解的方式增加,因此有浪费物理计算力的可能性
-
schema.xml
<table name="user10" dataNode="dn1,dn2" primaryKey="id" rule="sharding-by-murmur" />
-
rule.xml
<tableRule name="sharding-by-murmur"> <rule> <columns>id</columns> <algorithm>murmur</algorithm> </rule> </tableRule> <!-- seed:使用哪种hash算法计算节点和key的hash值,默认为0 count:分片节点个数,其实就是物理节点个数 virtualBucketTimes:一个物理节点有多少虚拟节点 weightMapFile:物理节点的权重文件,不指定默认所有物理节点权重都是1,可以通过properties文件的格式指定每个分片节点的权重,例如0=2,表示第1个分片节点权重是2。所有权重值必须是正整数,否则以1代替 bucketMapPath:虚拟节点hash值与物理节点的映射关系对照文件,不指定也可以,只不过看不到映射关系,但自己测试时,发现无法出现该文件 --> <function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash"> <property name="seed">0</property> <property name="count">2</property> <property name="virtualBucketTimes">160</property> <!--<property name="weightMapFile">weightMapFile</property>--> <property name="bucketMapPath">/etc/mycat/bucketMapPath</property> </function>
-
重启mycat
-
测试
create table user10(id bigint not null primary key,name varchar(20)); insert into user10(id,name) values(1111111,database()); insert into user10(id,name) values(2222222,database()); insert into user10(id,name) values(3333333,database()); insert into user10(id,name) values(4444444,database()); insert into user10(id,name) values(8960000,database());
7 mycat跨分片join
- 其实只有Share join方式是真正实现跨分片join,其他方式只不过将需要跨分片join的数据,以某种方法放到了一个分片中
7.1 跨分片join性能建议
- 尽量避免使用left join或right join,而用inner join
- 在使用left join或right join时,on会优先执行,where条件在最后执行,所以在使用过程中,条件尽可能的在on语句中判断,减少where的执行
SELECT * FROM table1 LEFT JOIN table2 ON table1.COL1=table2.COL1 AND TF.COL1=123
- 少使用子查询,而用join
7.2 mycat跨分片join方式
- 以下讨论都是在默认情况下,也就是不进行一些特殊配置的情况下,mycat对跨分片join的处理
- 默认不支持跨分片join,跨分片join可能报错可能不报错,但即使不报错也无法得到正确的查询结果
- 如果两个表不在同一分片,无法进行join,如果不跨分片,可以join,且不限制join表的数量
- 如果表A在分片1,表B在分片1和分片2都有数据,那么join两表时,只显示分片1上,表A和表Bjoin的结果
- 如果表A、B在分片1和分片2上都有数据,那么join两表时,A、B先分别在分片1和分片2中进行join,最后将结果汇总传到控制台,但这样查询出的结果,和我们预期的结果是不同的,例如表A中id为1的数据在分片1,表B中id为1的数据在分片2,我们预期join后能得到一条id为1的记录,但实际上mycat返回的结果为空,同时如果带条件进行查询,还可能会报错
7.2.1 全局表
-
在mycat中可以指定某个逻辑表为全局表,那么该表插入数据时,会在每个分片节点都插入一份,因此该表与其他表关联时,就不存在跨分片join的问题了,因此全局表可以与任何一个表join,当单独查询全局表时,只从其中一个分片节点获取数据
-
mycat中指定的全局表对应的物理表,不能通过mysql创建,必须通过mycat创建,因为通过mycat创建全局表的物理表时,会隐式地为该表添加一个列,通过mycat向该表插入数据时,mycat会改写语句自动向这个列中插入值,因此如果通过mysql创建该表,会导致插入失败
-
schema.xml
<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>
-
测试
CREATE TABLE dict_order_type( id INT AUTO_INCREMENT, order_type VARCHAR(200), PRIMARY KEY(id) ); --发现在配置的所有分片节点(dn1、dn2)中都能查到这个表的数据 INSERT INTO dict_order_type(id,order_type) VALUES(101,'type1'); INSERT INTO dict_order_type(id,order_type) VALUES(102,'type2');
7.2.2 ER分片
-
通过关联关系,将子表的记录与所关联的父表记录存放在同一个数据分片上
-
schema.xml
<table name ="orders" dataNode="dn1,dn2" rule="mod_rule"> <!-- name:定义子表的表名 joinKey:子表的哪个列和父表关联 parentKey:子表与父表的哪个列关联 primaryKey:同table标签中primaryKey属性 needAddLimit:同table标签中needAddLimit属性 --> <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" /> </table>
-
重启tomcat
-
测试
--1. 因为orders_detail的order_id字段关联orders表的id,因此如果orders表中对应的id如果还不存在,那么插入会报错 --2. 但发现这个插入报错有bug,orders表插入过对应id的数据,但后来将orders表删除,那么orders_detail还是能正常插入 INSERT INTO orders_detail(id,detail,order_id) values(1,'detail1',1); INSERT INTO orders_detail(id,detail,order_id) VALUES(2,'detail1',2); INSERT INTO orders_detail(id,detail,order_id) VALUES(3,'detail1',3); INSERT INTO orders_detail(id,detail,order_id) VALUES(4,'detail1',4); INSERT INTO orders_detail(id,detail,order_id) VALUES(5,'detail1',5); INSERT INTO orders_detail(id,detail,order_id) VALUES(6,'detail1',6); select * from orders o join orders_detail od on o.id = od.order_id;
7.2.3 ShareJoin
-
目前最多支持两个表跨分片join,其实就是为语句加上指定的hint,mycat发现这个hint后,会解析该语句,并拆分成单表的 SQL语句执行,然后把各个节点的数据汇集
-
schema.xml
<table name="company" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long" /> <table name="customers" primaryKey="id" dataNode="dn1,dn2" rule="sharding-by-intfile"/>
-
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"> <!-- how many data nodes --> <property name="count">3</property> </function> <tableRule name="sharding-by-intfile"> <rule> <columns>sharding_id</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> </function>
-
partition-hash-int.txt
10000=0 10010=1
-
测试
create table company(id int primary key,name varchar(10)) engine=innodb; insert company (id,name) values(1,'mycat'); insert company (id,name) values(2,'ibm'); insert company (id,name) values(3,'hp'); create table customers(id int not null primary key,name varchar(100),company_id int not null,sharding_id int not null); insert into customers(id,name,company_id,sharding_id)values(1,'wang',1,10000),(2,'xue',2,10010),(3,'feng',3,10000); --1. 可以看到有时可以查出对应的结果,有时报错 select a.*,b.ID,b.NAME as tit from customers a,company b where a.COMPANY_ID=b.ID; --2. 写法1 /*!mycat:catlet=io.mycat.catlets.ShareJoin */select a.*,b.ID,b.NAME as tit from customers a,company b where a.COMPANY_ID=b.ID; --3. 写法2 /*!mycat:catlet=io.mycat.catlets.ShareJoin */select a.*,b.ID,b.NAME as tit from customers a join company b on a.COMPANY_ID=b.ID; --4. 写法3 /*!mycat:catlet=io.mycat.catlets.ShareJoin */select a.*,b.ID,b.NAME as tit from customers a join company b where a.COMPANY_ID=b.ID;
7.2.4 catletT(人工智能)
- 自己写代码实现跨分片join,很少使用
8 全局序列号
- 在实现分库分表的情况下,mysql的自增主键已经无法保证主键的全局唯一,因此mycat提供本地文件配置、数据库配置等多种方式提供全局sequence
8.1 本地文件方式
-
将序列配置到classpath下的sequence_conf.properties文件中,当语句中使用
next value for mycatseq_序列名
,mycat会更新该文件中该序列的CURID(当前值)为原值+1 -
sequence_conf.properties
#1. GLOBAL_SEQ:序列名 #2. HISIDS:使用过的历史分段(一般无特殊需要可不配置) #3. MAXID:最大值 #4. MINID:最小值 #5. CURID:当前值 #6. 当序列的CURID超过MAXID,HISIDS会显示超之前的MAXID和MINID,MINID更新成原MAXID+1,MAXID更新成原MAXID+(原MAXID-原MINID)+1 GLOBAL_SEQ.HISIDS= GLOBAL_SEQ.MINID=10001 GLOBAL_SEQ.MAXID=20000 GLOBAL_SEQ.CURID=10000
-
server.xml
<!--1. 0:本地文件方式,1:数据库方式--> <!--2. system标签必须在user标签之上,否则无法启动--> <system> <property name="sequnceHandlerType">0</property> </system>
-
测试
--不用在schema.xml中对表tab1进行特殊配置,因为默认放在dn1 create table tab1(id int primary key,name varchar(10)); --注意1.6.7.5的mycat存在问题,需要使用1.6.7.1版本,否则插入报错 insert into tab1(id,name) values(next value for mycatseq_global_seq,'test1'); insert into tab1(id,name) values(next value for mycatseq_global_seq,'test2'); insert into tab1(id,name) values(next value for mycatseq_global_seq,'test3');
-
缺点:配置文件如果被修改,序列值就丢失了,且如果为一个mycat集群,集群中的其他mycat无法访问到这个文件
-
优点:本地加载,读取速度快
8.2 数据库方式
-
在数据库中建立表
mycat_sequence
(固定名),存放序列名name
、当前值current_value
、步长increment
- 语句中第一次使用
next value for MYCATSEQ_序列名
时,mycat一次性将从current_value
+increment
开始的increment
个值读入内存,并修改mycat_sequence
表中current_value
为原current_value
+increment
- 再次使用
next value for MYCATSEQ_序列名
,mycat判断内存中是否还有未使用的序列值,如果有就使用内存中的下一个序列值,如果没有重新执行步骤1 - 若某次内存中的序列值没用完,系统就宕机了,则这次读取的序列的剩余值不会再使用
- 语句中第一次使用
-
server.xml
<system><property name="sequnceHandlerType">1</property></system>
-
schema.xml
<!--测试表--> <table name="test" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long"/> <!--指定表mycat_sequence的存放位置--> <table name="mycat_sequence" primaryKey="name" dataNode="dn2"/>
-
sequence_db_conf.properties
#名为MYCAT的序列放到哪个分片节点 MYCAT=dn2
-
mycat执行
--1. 建立存放序列信息的表mycat_sequence,该表名固定,不能随意修改 DROP TABLE IF EXISTS mycat_sequence; CREATE TABLE mycat_sequence (name VARCHAR(50) NOT NULL,current_value INT NOT NULL,increment INT NOT NULL DEFAULT 100, PRIMARY KEY(name)) ENGINE=InnoDB; --2. 插入初始记录 INSERT INTO mycat_sequence(name,current_value,increment) VALUES ('mycat', -99, 100);
-
dn2上mysql执行
--3. 创建函数,这些函数会被mycat自动调用 --函数一 DROP FUNCTION IF EXISTS mycat_seq_currval; DELIMITER $ CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf8 DETERMINISTIC BEGIN DECLARE retval VARCHAR(64); SET retval="-999999999,null"; SELECT concat(CAST(current_value AS CHAR),",",CAST(increment AS CHAR)) INTO retval FROM mycat_sequence WHERE name = seq_name; RETURN retval; END $ DELIMITER ; --函数二 DROP FUNCTION IF EXISTS mycat_seq_setval; DELIMITER $ CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),value INTEGER) RETURNS varchar(64) CHARSET utf8 DETERMINISTIC BEGIN UPDATE mycat_sequence SET current_value = value WHERE name = seq_name; RETURN mycat_seq_currval(seq_name); END $ DELIMITER ; --函数三 DROP FUNCTION IF EXISTS mycat_seq_nextval; DELIMITER $ CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf8 DETERMINISTIC BEGIN UPDATE mycat_sequence SET current_value = current_value + increment WHERE name = seq_name; RETURN mycat_seq_currval(seq_name); END $ DELIMITER ;
-
测试
--mycat执行 create table test(id int,name varchar(10)); --查看序列表 SELECT * FROM mycat_sequence; --使用名为MYCAT的全局序列插入 insert into test(id,name) values(next value for MYCATSEQ_MYCAT,(select database())); --查询添加的数据 SELECT * FROM test order by id asc; --重启mycat后,重新添加数据,发现之前读入内存的序列不再使用,重新从表中获取当前值 insert into test(id,name) values(next value for MYCATSEQ_MYCAT,(select database())); SELECT * FROM mycat_sequence; SELECT * FROM test order by id asc;
8.3 本地时间戳方式
-
语句中使用
next value for MYCATSEQ_GLOBAL
时,mycat可以自动生成一个64位二进制转换成的十进制数,64位=42位(毫秒)+5位(机器 ID)+5位(业务编码)+12位(重复累加)
,因此不会重复,且每毫秒可以并发12位2进制数的累加 -
server.xml
<property name="sequnceHandlerType">2</property>
-
sequence_time_conf.properties
#设置自身机器ID WORKID=06 #设置自身业务编码 DATAACENTERID=06
-
schema.xml
<table name="test2" dataNode="dn1,dn2,dn3" primaryKey="id" autoIncrement="true" rule="mod-long" />
-
测试
create table test2(id bigint auto_increment primary key,xm varchar(32)); insert into test2(id,xm) values(next value for MYCATSEQ_GLOBAL,'lisi') ;
-
优点:配置简单
-
缺点:序列值太长,需要耗费多余的存储空间
8.4 自定义全局序列
- 用户还可以在程序中自定义全局序列,通过java代码实现,但这种方式比较麻烦,因此如果mycat提供的方式满足需求就不自己实现
8.5 分布式ZK ID生成器
- 如果在搭建的时候使用了zookeeper,也可以使用zk来生成对应的id,此方式需要zk的配合
9 mycat负载均衡与高可用
-
HAProxy:保证几台mycat间的负载均衡,以及解决mycat的单点故障
-
keepalived:解决HAProxy的单点故障
9.1 环境规划
编号 | 角色 | IP地址 | 机器名 |
---|---|---|---|
1 | mycat1 | 192.168.246.128 | node01 |
2 | mycat2 | 192.168.246.129 | node02 |
3 | HAProxy(master) | 192.168.246.130 | node03 |
4 | Keepalived(master) | 192.168.246.130 | node03 |
5 | HAProxy(backup) | 192.168.246.131 | node04 |
6 | keepalived(backup) | 192.168.246.131 | node04 |
9.2 安装配置HAProxy
-
下载网址:https://www.haproxy.org/download/1.8/src/
-
安装流程
#1. 解压 tar -xvf haproxy-1.8.25.tar.gz -C /usr/local #2. 查看内核版本,并根据内核版本决定编译时的TARGET值,centos6的内核的TARGET应指定为linux26 uname -r #3. 进入目录 cd /usr/local/haproxy-1.8.25/ #4. 编译 make TARGET=linux26 #5. 安装到指定目录 make install PREFIX=/usr/local/haproxy #6. 创建HAProxy需要的目录以及配置文件 mkdir -p /usr/data/haproxy mkdir /usr/local/haproxy/conf vi /usr/local/haproxy/conf/haproxy.conf #7. 配置文件中的内容 global log 127.0.0.1 local0 #log 127.0.0.1 local1 notice #log loghost local0 info maxconn 4096 chroot /usr/local/haproxy pidfile /usr/data/haproxy/haproxy.pid uid 99 gid 99 daemon #debug #quiet defaults log global mode tcp option abortonclose option redispatch retries 3 maxconn 2000 timeout connect 5000 timeout client 50000 timeout server 50000 listen proxy_status #指定haproxy端口 bind :48066 mode tcp #负载均衡策略为轮循,第一次连接128,第二次连接129 balance roundrobin #server名 ip:端口 server mycat_1 192.168.246.128:8066 check inter 10s server mycat_2 192.168.246.129:8066 check inter 10s frontend admin_stats #配置haproxy管理控制太,端口为7777 bind :7777 mode http stats enable option httplog maxconn 10 stats refresh 30s #指定监控页面uri stats uri /admin #指定,用户名:密码 stats auth admin:123123 stats hide-version stats admin if TRUE #8. 启动haproxy /usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/conf/haproxy.conf #9. 查看haproxy的进程是否存在 ps -ef | grep haproxy #10. 浏览器访问haproxy管理控制台,用户名为admin,密码为123123,查看两台mycat的Status,应该都为up http://192.168.246.130:7777/admin
-
测试
#1. 我们可以通过Terminus使用tail -f /usr/local/mycat/logs/mycat.log观察mycat后台日志,哪个日志的窗口上出现绿点,就说明哪个日志变化了,从而观察haproxy到底连接的是哪台服务器上的mycat #2. 发现第一次连接,node01上的日志变化,第二次连接,node02上日志变化,这也印证了haproxy中的负载均衡策略balance roundrobin,即轮循 mysql -uroot -p123456 -P 48066 -h 192.168.246.130
9.3 安装配置keepalived
-
下载网址:https://www.keepalived.org/index.html
-
node03:安装
#1. 安装需要依赖的环境组件 yum install gcc openssl openssl-devel popt-devel -y #2. 解压 tar -xvf keepalived-2.1.5.tar.gz -C /usr/local #3. 进入目录 cd /usr/local/keepalived-2.1.5 #4. 生成makefile文件,并指定keepalived的安装目录 ./configure --prefix=/usr/local/keepalived #5. 编译并安装 make && make install #6. 将keepalived的服务注册为系统服务 cp /usr/local/keepalived-2.1.5/keepalived/etc/init.d/keepalived /etc/init.d/ mkdir /etc/keepalived cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/ cp /usr/local/keepalived-2.1.5/keepalived/etc/sysconfig/keepalived /etc/sysconfig/ cp /usr/local/keepalived/sbin/keepalived /usr/sbin/ #7. 修改配置文件 vi /etc/keepalived/keepalived.conf #8. 配置文件中的内容 global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 127.0.0.1 smtp_connect_timeout 30 router_id LVS_DEVEL vrrp_skip_check_adv_addr vrrp_garp_interval 0 vrrp_gna_interval 0 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.246.200/24 dev eth0 label eth0:3: } } #9. 启动keepalived service keepalived start #10. 使用ifconfig命令观察,发现多了一个eth0:3:网卡,地址为192.168.246.200 #11. 使用虚拟ip登录 mysql -uroot -p123456 -h 192.168.246.200 -P 48066
-
node04:安装
#1. 安装keepalived方式和上面相同,只不过配置文件中state MASTER改为state BACKUP,启动keepalived后,通过ifconfig命令,不会出现地址为192.168.246.200的那块网卡 #2. 停止node03上的keepalived,此时发现自身的ifconfig上出现eth0:3:网卡,地址为192.168.246.200 service keepalived stop #3. 此时使用虚拟ip登录的就是node04这台机器上的haproxy了 mysql -uroot -p123456 -h 192.168.246.200 -P 48066
10 mycat安全管理
-
可以在mycat的配置文件server.xml中配置登录用户的密码、权限等,这样就不用在其后端连接的mysql再进行权限控制了,否则mysql有多台时,权限配置比较麻烦
-
逻辑库只读
<!--如下配置需要mysql -umycat -pmycat -h 192.168.246.128 -P 8066进行登录--> <user name="mycat"> <property name="password">mycat</property> <property name="schemas">TESTDB</property> <!--true表示只读,插入数据报错--> <property name="readOnly">true</property> </user>
-
对逻辑库和逻辑表,进行更精细的限制
<user name="mycat"> <property name="password">mycat</property> <property name="schemas">TESTDB</property> <!--check为true表示开启权限检查,false不开启--> <privileges check="true"> <!--对逻辑库权限进行配置,四位数分别对应增、改、查、删,1表示允许,0表示不允许--> <schema name="TESTDB" dml="1111"> <!--对逻辑表权限进行配置--> <table name="orders" dml="0000"></table> </schema> </privileges> </user>
-
白名单
<!--firewall标签应该在system标签下,user标签上,如果顺序不对启动会报错--> <firewall> <whitehost> <!--表示用户mycat可以由这个ip访问,如果不进行其他配置,就表示该ip只能使用用户mycat,用户mycat也只能由这个ip访问--> <host host="192.168.246.128" user="mycat"></host> </whitehost> </firewall>
-
黑名单
<!--和上面whitehost标签同级,在firewall标签内--> <blacklist check="true"> <!--deleteAllow:是否允许删除,false表示不允许删除,还有一些其他的功能控制,可参考mycat官网pdf--> <property name="deleteAllow">false</property> </blacklist>
11 mycat监控工具
- mycat-web:是mycat可视化运维管理和监控平台,引入了zookeeper作为配置中心,可以管理多个mycat节点
- 管理和监控mycat的流量、连接、活动线程、内存
- 统计SQL并分析慢SQL和高频SQL,为SQL优化提供依据
- 白名单、邮件告警
11.1 mycat-web搭建
-
安装zookeeper
#1. 下载安装包https://zookeeper.apache.org/ #2. 解压 tar -zxvf apache-zookeeper-3.6.2-bin.tar.gz -C /usr/local #3. 建立配置文件 cd /usr/local/apache-zookeeper-3.6.2-bin/conf cp zoo_sample.cfg zoo.cfg cd /usr/local/apache-zookeeper-3.6.2-bin/bin ./zkServer.sh start #4. 查看是否正常启动 netstat -nlpt | grep 2181
-
安装mycat-web
#1. 下载安装包http://dl.mycat.org.cn/mycat-web-1.0/ #2. 解压 tar -zxvf Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz -C /usr/local #3. 修改zookeeper配置地址,此处不用特殊配置,因为zk和mycat-web在同一台机器,且zk端口也未做特殊修改 /usr/local/mycat-web/mycat-web/WEB-INF/classes vi mycat.properties #4. 启动 cd /usr/local/mycat-web ./start.sh & #5. 查看服务是否正常启动 netstat -nlpt | grep 8082 #6. 访问mycat-web http://192.168.246.131:8082/mycat/
-
mycat-web可视化配置:http://192.168.246.131:8082/mycat/,注意mycat-web所在机器必须有访问配置的mycat的权限,否则无法保存