目录
入门概述
MyCat概念
MyCAT 是目前最流行的分布式数据库中间插件,是一个开源的分布式数据库系统,是一个实现了 MySQL 协议的服务器。前端用户可以把它看作一个数据库代理,用 MySQL客户端工具和命令行访问,其后端可以用 MySQL 原生协议与多个 MySQL 服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将个大表水平分割为 N个小表,存储在后端 MySQL 服务器里或者其他数据库里
MyCAT 发展到目前,已经不是一个单纯的 MySQL代理了,它的后端可以支持 MySQL、SQL Server Oracle、DB2PostqreSQL等主流数据库,也支持 MonoDB 这种新型 NOSQL方式的存储。未来,它还会支持更多类型的存储
不过,无论是哪种存储方式,在最终用户看,MyCAT里都是一个传统的数据库表,支持标准的 SQL 语句进行数据的操作,这样一来,对前端业务系统来说,可以大幅降低开发难度,提升开发速度
更多解释见官网
项目地址:源码地址
随着互联网的发展,数据的量级也是呈指数的增长,从 GB 到TB 到 PB,操作数据也是愈加的困难,传统的关系性数据库已经无法满足快速查询与插入数据的需求
而 NoSQL的出现暂时解决了这一危机。
NoSQL 通过降低数据的安全性,减少对事务的支持,减少对复杂查询的支持来提升性能。但是,NoSQL 存在无法满足某些使用场景的情况,比如,有些使用场景绝对要有事务与安全指标。这时 NOSQL 肯定无法满足。
所以,我们还是需要使用关系型数据库.
那么,如何使用关系型数据库解决海量存储的问题呢?此时就需要做数据库集群,为了提高查询性能将一个数据库的数据分散到不同的数据库中存储,为应对该问题,MyCAT 就出现了
MyCat的作用
读写分离
数据分片
多数据源整合
MyCat 解决问题的思路
MyCat 和MySQL的区别
我们可以把上层看作是对下层的抽象,例如操作系统是对各类计算机硬件的抽象。那么我们什么时候需要抽象?
假设一个项目只需要一个人完成时,我们不需要 Leader,但是当需要几十人完成时,就应该有一个管理者,发挥沟通协调等作用,这个管理者对于他的上层来说就是对项目组的抽象
同样的,当我们的应用只需要一台数据库服务器时,我们并不需要 MyCAT,如果你需要分库甚至分表,这时候应用要面对很多个数据库时,这时,就需要对数据库层做一个抽象,来管理这些数据库,最上面的应用只需要面对一个数据库层的抽象或者说数据库中间件就好了,这就是 MyCAT 的核心作用。
所以你可以这样理解:
数据库是对底层存储文件的抽象,而 MyCAT 是对数据库的抽象
MyCat原理
简单来说就是拦截用户发来的SQL语句,对SQL语句做了一些特定的分析,如分片分析,路由分析,读写分离分析,缓存分析等,然后将此SQL发往后端的真实数据库,并将返回的结果做适当处理,最终返回给用户。
MyCAT2的安装
前言
使用MyCAT 2要安装JDK,也可以直接使用yum -y install java-1.8.0-openjdk.x86_64 因为MyCAT 是基于JDK1.8开发的。
下载压缩包和jar包
- 安装程序包:http://dl.mycat.org.cn/2.0/install-template/mycat2-install-template-1.21.zip
- jar包:http://dl.mycat.org.cn/2.0/1.21-release/mycat2-1.21-release-jar-with-dependencies.jar
- wget:http://dl.mycat.org.cn/2.0/install-template/mycat2-install-template-1.21.zip
- wget:http://dl.mycat.org.cn/2.0/1.21-release/mycat2-1.21-release-jar-with-dependencies.jar
安装MyCAT2
创建/data/tools
mkdir -p /data/tools
进入/data/tools目录
cd /data/tools
下载
如果没有wget,就执行yum -y install wget
wget http://dl.mycat.org.cn/2.0/install-template/mycat2-install-template-1.21.zip
wget http://dl.mycat.org.cn/2.0/1.21-release/mycat2-1.21-release-jar-with-dependencies.jar
下载完成
解压并移动到data目录下
yum -y install unzip
unzip mycat2-install-template-1.21.zip
mv mycat ../
修改权限
把bin目录的文件加执行权限:
cd /data/mycat/bin
chmod +x *
把所需的jar复制到mycat/lib目录
cd /data/mycat/lib/
cp /data/tools/mycat2-1.21-release-jar-with-dependencies.jar ./
MyCAT的目录结构
其中:
1)bin 执行命令的目录
2)conf 配置文件
3)lib 依赖包
4)logs 日志包
启动一个3306的MySQL
会为mycat代理连接启动时需要有一个默认的数据源,所以我们在启动的时候先为其准备一个数据源,接下来我们使用docker启动
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names=1
配置物理库地址
在启动之前我们要配置物理库的地址,要不然MyCAT启动就会报错。
配置文件位置: mycat/conf/datasources/prototypeDs.datasource.json
主要配置user、password、url三个字段,其他的字段是默认值,不用管
{
"dbType":"mysql",
"idleTimeout":60000,
"initSqls":[],
"initSqlsGetConnection":true,
"instanceType":"READ_WRITE",
"maxCon":1000,
"maxConnectTimeout":3000,
"maxRetryCount":5,
"minCon":1,
"name":"prototypeDs",
"password":"123456",
"type":"JDBC",
"url":"jdbc:mysql://localhost:3306?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8",
"user":"root",
"weight":0
}
启动MyCAT
cd /data/mycat/bin
./mycat start 启动
./mycat stop 停止
./mycat console 前台运行
./mycat install 添加到系统自动启动
./mycat remove 取消随系统自动启动
./mycat restart 重启
./mycat pause 暂停
./mycat status 查看启动状态
连接MyCAT
我们在外面看MyCAT,认为MyCAT 就是一个MySQL,怎么连接MySQL?
1)Navicat for MySQL 对MyCAT支持查询
2)SQLyog 操作MyCAT更友好
输入IP地址、用户名,和端口号(注意端口号为8066)。
如果出现上面的弹出框,说明MyCAT安装成功。
MyCat2 配置文件介绍
本章节配置基于MyCat的配置文件,内容繁多,了解即可,下一章节的注释配置才是硬菜
用户(user)
配置文件位置mycat/conf/user/{用户名}.user.json。这个配置文件主要是用来配置MyCAT的登录用户的,也就是我们连接8066这个端口的用户信息。
{
"dialect":"mysql",
"ip":null,
"password":"123456",
"transactionType":"proxy",
"username":"root",
"isolation":3
}
- 配置说明
- dialect:数据库(方言)类型
- ip:配置白名单使用,一般写null 如果要限制这个用户
- password:配置MyCAT用户的密码(明文)
- isolation:设置初始化的事务隔离级别
- READ_UNCOMMITTED:1
- READ_COMMITTED:2
- REPEATED_READ:3,默认
- SERIALIZABLE:4
- transactionType
- 默认值:proxy(本地事务,在涉及大于1个数据库的事务,commit阶段失败会导致不一致,但是兼容性最好)
- 可选值:xa(事务,需要确认存储节点集群类型是否支持XA)
数据源(datasource)
作用:配置MyCAT连接后端的物理库的数据源。
配置文位置:mycat/conf/datasources/{数据源名字}.datasource.json
{
"dbType": "mysql",
"idleTimeout": 60000,
"initSqls": [],
"initSqlsGetConnection": true,
"instanceType": "READ_WRITE",
"maxCon": 1000,
"maxConnectTimeout": 3000,
"maxRetryCount": 5,
"minCon": 1,
"name": "prototype",
"password": "123456",
"type": "JDBC",
"url": "jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&serverTimezone=UTC",
"user": "root",
"weight": 0,
"queryTimeout":30,//mills
}
配置说明
- dbType:数据源类型
- name:数据源名字
- password:后端MySQL的密码
- url:后端MySQL的JDBC连接地址
- user:后端MySQL的用户名
- weight:配置数据源负载均衡的使用权重
逻辑库与逻辑表(logicaltable)
作用:配置MyCAT里面和MySQL对应的逻辑表。
配置位置:mycat/conf/schemas/{库名}.schema.json
{
"customTables": {},
"globalTables": {},
"normalTables": {},
"schemaName": "test",
"shardingTables": {},
"targetName": "prototype"
}
配置说明
- customTables:自定义表
- globalTables:全局表
- normalTables:默认表
- schemaName:库名
- shardingTables:分片表
- targetName:数据源名,也可以是集群名
序列号(sequence)
作用:使用序列号的分片表,对应的自增主键要在建表SQL中体现。
配置位置: mycat/conf/sequences/{数据库名字}_{表名字}.sequence.json(后面细说)。
服务器(server)
这个是针对MyCAT的服务器的配置,一般情况下默认就行了,不用怎么配置。
MyCat2 注释配置
本章节的注释指令要求是在MyCat上执行,通常做法是通过MySQL图形化连接工具直连MyCat
重置配置
/*+ mycat:resetConfig{} */
用户相关
创建用户
/*+ mycat:createUser{
"username":"user",
"password":"",
"ip":"127.0.0.1",
"transactionType":"xa"
} */
删除用户
/*+ mycat:dropUser{
"username":"user"} */
显示用户
/*+ mycat:showUsers */
数据源相关
创建数据源
这个只需要设置name、password、type、url、user几个字段就行了
/*+ mycat:createDataSource{
"dbType":"mysql",
"idleTimeout":60000,
"initSqls":[],
"initSqlsGetConnection":true,
"instanceType":"READ_WRITE",
"maxCon":1000,
"maxConnectTimeout":3000,
"maxRetryCount":5,
"minCon":1,
"name":"dc1",
"password":"123456",
"type":"JDBC",
"url":"jdbc:mysql://127.0.0.1:3306?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
"user":"root",
"weight":0
} */;
删除数据源
这个只要设置name字段就行,其他的直接CV大法
/*+ mycat:dropDataSource{
"dbType":"mysql",
"idleTimeout":60000,
"initSqls":[],
"initSqlsGetConnection":true,
"instanceType":"READ_WRITE",
"maxCon":1000,
"maxConnectTimeout":3000,
"maxRetryCount":5,
"minCon":1,
"name":"dc1",
"type":"JDBC",
"weight":0
} */;
显示数据源
/*+ mycat:showDataSources{} */
集群相关
创建集群
/*! mycat:createCluster{
"clusterType":"MASTER_SLAVE",
"heartbeat":{
"heartbeatTimeout":1000,
"maxRetry":3,
"minSwitchTimeInterval":300,
"slaveThreshold":0
},
"masters":[
"dc1" //主节点
],
"maxCon":2000,
"name":"c0",
"readBalanceType":"BALANCE_ALL",
"replicas":[
"dc2" //从节点
],
"switchType":"SWITCH"
} */;
删除集群
/*! mycat:dropCluster{
"name":"c0"
} */;
显示集群
/*+ mycat:showClusters{} */
MySQL 主从搭建
因为 MyCAT 只能路由、分发,不能把多个数据库里面的数据进行同步,所以要数据同步必须还要使用 MySQL 的读写分离,主从复制。
主从介绍
MySQL的主从复制是一种常见的数据库复制技术,用于实现数据在多个数据库服务器之间的同步。
主从作用有:实时灾备,用于故障切换;读写分离,提供查询服务;备份,避免影响业务
在主从复制中,有一个主数据库(Master)和一个或多个从数据库(Slave)。主数据库负责处理事务操作(INSERT、UPDATE、DELETE),并将这些操作的日志(binlog)传送给从数据库。从数据库通过解析主数据库的日志并执行相同的操作,在从库上实现数据的同步。
主从复制实现步骤
- 主库将数据的事务操作(DML)记录到一个二进制日志中(即:binary log),也就是配置文件中指定的log-bin指定的文件就是日志文件。
- 从库会监听主库的binary log日志文件变化,当发生数据变化后,将主库的日志文件拷贝到他的中继日志即配置文件中指定的relay log日志文件中,I/O线程去请求主库的bin-log日志,并将日志写入到relay log中继日志中,此时主库会生成一个log dump线程,用来给从库I/O线程传输bin-log日志文件。
- 从库会更新relay log文件中的操作,将数据的改变在从库中进行数据重演即重新执行一次,即SQL线程执行操作,将日志文件中的记录变为数据操作行为再次执行,以达到主从数据最终一致性的目的。
搭建主从复制
- 确保从数据库与主数据库里的数据一致
- 在主数据库里创建一个同步账户授权给从数据库使用
- 配置主数据库 (修改配置文件)
- 配置从数据库 (修改配置文件)
- 需求
- 搭建两台 MySQL 服务器一台作为从服务器,主服务器进行写操作,从服务器进行读操作一台作为主服务器
环境说明(使用Docker启动两个)
docker run --name M1 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names=1
docker run --name M1S1 -p 3308:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names=1
修改配置文件
将容器里面的配置文件复制出来,主要修改服务器的配置;在root目录下创建一个mysqlms的目录存放从Docker容器里面复制过来的配置文件。进入目录:cd /mysqlms
docker cp M1:/etc/mysql/conf.d/docker.cnf m1.cnf
docker cp M1S1:/etc/mysql/conf.d/docker.cnf m1s1.cnf
主机的配置m1.cnf
主机里面要记录SQL 语句,以后从机会把该SQL 语句拷贝过去
server-id=1
log-bin=master.bin
从机的配置m1s1.cnf
server-id=2
配置文件修改后,复制到容器里面
docker cp m1.cnf M1:/etc/mysql/conf.d/docker.cnf
docker cp m1s1.cnf M1S1:/etc/mysql/conf.d/docker.cnf
重启MySQL(m1,m1s1):
docker restart M1 M1S1
测试连接:
执行SQL语句
进入主机里面执行相关配置
docker exec -it M1 bash
mysql -uroot -p123456
创建用户:
create user 'rep'@'%' identified by '123456';
给该用户授予权限:
grant replication slave on *.* to 'rep'@'%';
刷新权限:
flush privileges;
至此,M1 里面已经创建了一个用户:rep 123456 拥有所有库,所有表replication slave。
接下来,我们尝试使用M1 里面的rep 用户登录:
进入从机里面执行相关配置
docker exec -it M1S1 bash
mysql -u root -p123456
change master to master_host="192.168.106.133",master_port=3307,master_user="rep",master_password="123456",master_log_file="master.000001",master_log_pos=745;
其中,master_log_file:该文件具体叫什么名称,需要从主机里面去看看。进入M1 里面使用root 用户登录M1,执行下面的SQL:show master status;
修改上面的SQL执行:
启动主从:(在M1S1里面执行):start slave ;
查询主从的状态(M1S1):show slave status \G;
成功的标志:
搭建失败的原因
第一个不是yes,是connecting
是因为从机使用你配置的主机信息没有登陆到主机里面!修改(从机里面)
stop slave;
change master to master_host="192.168.106.133",master_port=3307,master_user="rep",master_password="123456",master_log_file="master.000001",master_log_pos=745;
start slave;
第二个不是yes,是no
原因是主机和从机里的数据不一致:
从机会复制主机里面的SQL语句,来自己执行!实验时先把从机里面的db3 删除—>再把主机里面的db3 删除->从机里面复制该删除的命令->从机执行删除的命令(db3),事务无法提交,将一直阻塞!
现在从机里面要删除db3 ,但是没有db3,导致一直阻塞,以后的主从复制不会进行了。解决:在从机新建一个db3,然后停止主从,启动主从:
第一个不是yes,是no
就是你的server-id 没有配置成功的原因,需要重新修改配置文件,复制配置文件到容器里面,然后重启就ok
MySQL主从的操作规范
- 只能在主机里面执行DML 语句,不能在从机里面执行DML语句(会破坏主从)
- 在从机里面可以执行查询语句
- 主机只有一台,但是从机可以有多台
测试
在M1 里面创建数据库,看M1S1 有没有复制过去
MyCAT2的主从配置
前提,先搭建好 MySQL的主从配置,登录 MyCAT 2在 MyCAT2 里面操作,也就是连接 8066 这个端口
创建数据源
添加读写的数据源
/*+ mycat:createDataSource{
"dbType":"mysql",
"idleTimeout":60000,
"initSqls":[],
"initSqlsGetConnection":true,
"instanceType":"READ_WRITE",
"maxCon":1000,
"maxConnectTimeout":3000,
"maxRetryCount":5,
"minCon":1,
"name":"m1",
"password":"123456",
"type":"JDBC",
"url":"jdbc:mysql://127.0.0.1:3307/db1?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
"user":"root",
"weight":0
} */;
添加读的数据源
/*+ mycat:createDataSource{
"dbType":"mysql",
"idleTimeout":60000,
"initSqls":[],
"initSqlsGetConnection":true,
"instanceType":"READ",
"maxCon":1000,
"maxConnectTimeout":3000,
"maxRetryCount":5,
"minCon":1,
"name":"m1s1",
"password":"123456",
"type":"JDBC",
"url":"jdbc:mysql://127.0.0.1:3308/db1?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
"user":"root",
"weight":0
} */;
查询数据源
/*+ mycat:showDataSources{} */
创建集群
/*! mycat:createCluster{
"clusterType":"MASTER_SLAVE",
"heartbeat":{
"heartbeatTimeout":1000,
"maxRetry":3,
"minSwitchTimeInterval":300,
"slaveThreshold":0
},
"masters":[
"m1"
],
"maxCon":2000,
"name":"prototype",
"readBalanceType":"BALANCE_ALL",
"replicas":[
"m1s1"
],
"switchType":"SWITCH"
} */;
查询集群
/*+ mycat:showClusters{} */
创建逻辑库
CREATE DATABASE db1 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
修改逻辑库的数据源
修改conf/schemas/db1.schema.json
vim /data/mycat/conf/schemas/db1.schema.json
在里面添加
"targetName":"prototype",
查看集群的配置文件
我们查看集群配置的结果,发现文件里面自动帮我们添加了。
测试读写分离是否成功(在MyCAT里面测试)
重启MyCAT:
在MyCAT里面创建一个sys_user表:
CREATE TABLE SYS_USER(
ID BIGINT PRIMARY KEY,
USERNAME VARCHAR(200) NOT NULL,
ADDRESS VARCHAR(500)
)
通过注释生成物理库和物理表:
如果物理表不存在,在 MyCAT2 能正常启动的情况下,根据当前配置自动创建分片表,全局表和物理表:
/*+ mycat:repairPhysicalTable{} */;
查看后端物理库:发现物理库和物理表都生成了。
在MyCAT里面向sys_user表添加一条数据:
INSERT INTO SYS_USER(ID,USERNAME,ADDRESS) VALUES(1,"XIAOMING","WUHAN");
修改MySQL里面的让数据不一样:
在MyCAT里面查询数据,会发现每次查询的结果不一样:
到此,我们使用MyCAT2主从搭建就完成了。
MySQL 的集群搭建(双主双重)
集群搭建概述
集群(Cluster)是一种较新的技术,通过集群技术,可以在付出较低成本的情况下,获得在性能、可靠性、灵活性方面的相对较高的收益,其任务调度则是集群系统中的核心技术
MySQL 集群技术在分布式系统中为 MySQL 数据提供了几余特性,增强了安全性,使得单 MSQL 服务器故障不会对系统产生巨大的负面效应,系统的稳定性得到保障。
在MySQL集群中,节点之间的数据是实时同步的。
MySQL集群通过使用同步复制机制来确保数据在各个节点之间的一致性。当在主节点上进行写操作时,这些写操作会立即被复制到其他节点。因此,所有节点上的数据都是同步的,可以保持一致性。
优点
- 高可伸缩性:服务器集群具有很强的可伸缩性,随着需求和负荷的增长,可以向集群系统添加更多的服务器,在这样的配置中,可以有多台服务器执行相同的应用和数据库操作。
- 高可用性:在不需要操作者干预的情况下,防止系统发生故障或从故障中自动恢复的能力。通过把故障服务器上的应用程序转移到备份服务器上运行,集群系统能够把正常运行时间提高到大于 99.9%,大大减少服务器和应用程序的停机时间。
缺点
-
数据一致性:由于MySQL集群是分布式的,数据同步和一致性是一个挑战。在极端情况下,例如网络故障或节点故障,可能会导致数据不一致的情况发生,需要通过其他机制来处理这种情况。
-
性能损耗:MySQL集群需要进行数据分片和数据同步,这可能会对性能产生一定的损耗。特别是在高并发的情况下,数据同步的延迟可能会导致读写操作的延迟。
搭建准备工作
使用Docker启动5台MySQL(至少要5台)
前面主从已启动了两个,所以只用启动三个就够了
docker run --name M1 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names=1
docker run --name M1S1 -p 3308:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names=1
docker run --name M1S2 -p 3309:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names=1
docker run --name M2 -p 3310:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names=1
docker run --name M2S1 -p 3311:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names=1
配置文件的修改
先准备5 个配置文件:
分别修改。
1)因为在上面的主从里面已有两个配置文件,所以只用创建三个就够了:m1s2.cnf、m2.cnf、m2s1.cnf
2)M2 需要添加一个配置项(具体配置看下面截图)
3)M2 会从M1 复制数据,但是M2 从M1复制的数据,不会记录下来,则M2S1 里面没有数据!
4)我们需要打开M2的级联复制功能,让M2 也能记录从M1 里面复制的数据。
最后结果如下:
配置文件修改好了后,复制到容器里面,并且重启容器!
#docker cp m1.cnf M1:/etc/mysql/conf.d/docker.cnf
#docker cp m1s1.cnf M1S1:/etc/mysql/conf.d/docker.cnf
docker cp m1s2.cnf M1S2:/etc/mysql/conf.d/docker.cnf
docker cp m2.cnf M2:/etc/mysql/conf.d/docker.cnf
docker cp m2s1.cnf M2S1:/etc/mysql/conf.d/docker.cnf
重启新建的三个容器
docker restart M1S2 M2 M2S1
进入容器执行SQL
在搭建MySQL的集群时,先清空所有机器里面的数据。
现在有数据的是:M1 M1S1,只需要清空M1的数据,就都没有了。
将之前新建的db1删除就ok。
4.1,M1修改
因为前面搭建好了,所有不用修改
4.2,M1S1修改
同理上面的4.1
4.3,M1S2修改(从机)
给它设置一个主机就ok
docker exec -it M1S2 bash
mysql -uroot -p123456
关联主机:
change master to master_host="192.168.106.133",master_port=3307,master_user="rep",master_password="123456",master_log_file="master.000001",master_log_pos=3574;
启动主从:start slave ;
查看状态:show slave status \G;
4.4,M2修改
进入M2使用Root登录
docker exec -it M2 bash
mysql -uroot -p123456
- 新建用户:create user 'rep1'@'%' identified by '123456';
- 给用户授权:grant replication slave on *.* to 'rep1'@'%';
- 刷新权限:flush privileges;
- 使用新的用户尝试登录:
M2 作为M1的从机需要执行的SQL:(登录M2时要使用root用户)
change master to master_host="192.168.106.133",master_port=3307,master_user="rep",master_password="123456",master_log_file="master.000001",master_log_pos=3574;
start slave ;
show slave status \G;
4.5,M2S1修改
进入容器
docker exec -it M2S1 bash
mysql -uroot -p123456
M2S1 是M2的从机,执行的SQL
进入M2查看pos:show master status;
在M2S1里面执行下面的命令:
change master to master_host="192.168.106.133",master_port=3310,master_user="rep1",master_password="123456",master_log_file="master.000001",master_log_pos=747;
启动主从:start slave ;
show slave status \G;
验证集群是否成功
使用工具连接上所有的MySQL
1)往M1 写数据,看 M2S1
2)在M1 新建数据库
3)观察M2S1
4)往M1 写数据,看M1S1
MyCAT2操作双主双从
目的:在MyCAT中集群配置实现双主双从。
角色说明
M1 主机
M1S1 是M1的从机
M1S2 是M1的从机
M2 主机
M2S1 是M2的从机
添加数据源
添加M1S2读的数据源
/*+ mycat:createDataSource{
"dbType":"mysql",
"idleTimeout":60000,
"initSqls":[],
"initSqlsGetConnection":true,
"instanceType":"READ",
"maxCon":1000,
"maxConnectTimeout":3000,
"maxRetryCount":5,
"minCon":1,
"name":"m1s2",
"password":"123456",
"type":"JDBC",
"url":"jdbc:mysql://127.0.0.1:3309?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
"user":"root",
"weight":0
} */;
添加M2的数据源
/*+ mycat:createDataSource{
"dbType":"mysql",
"idleTimeout":60000,
"initSqls":[],
"initSqlsGetConnection":true,
"instanceType":"READ_WRITE",
"maxCon":1000,
"maxConnectTimeout":3000,
"maxRetryCount":5,
"minCon":1,
"name":"m2",
"password":"123456",
"type":"JDBC",
"url":"jdbc:mysql://127.0.0.1:3310?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
"user":"root",
"weight":0
} */;
添加M2S1的数据源
/*+ mycat:createDataSource{
"dbType":"mysql",
"idleTimeout":60000,
"initSqls":[],
"initSqlsGetConnection":true,
"instanceType":"READ",
"maxCon":1000,
"maxConnectTimeout":3000,
"maxRetryCount":5,
"minCon":1,
"name":"m2s1",
"password":"123456",
"type":"JDBC",
"url":"jdbc:mysql://127.0.0.1:3311?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
"user":"root",
"weight":0
} */;
修改配置集群
因为我们现在后端的MySQL集群结构发生了变化,现在修改集群的配置【只是修改】
{
"clusterType":"MASTER_SLAVE",
"heartbeat":{
"heartbeatTimeout":1000,
"maxRetryCount":0,
"minSwitchTimeInterval":300,
"showLog":true,
"slaveThreshold":0.0
},
"masters":[
"m1","m2"
],
"maxCon":2000,
"name":"prototype",
"readBalanceType":"BALANCE_ALL",
"replicas":[
"m1s1","m1s2","m2s1"
],
"switchType":"SWITCH"
}
重启MyCAT测试
重启MyCAT
cd /data/mycat/bin
./mycat restart
开始测试
在MyCAT创建db1库:
CREATE DATABASE db1 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
在MyCAT创建表:
create table sys_user(
id bigint primary key,
username varchar(200) not null,
address varchar(500)
)
刷新逻辑表到物理库
/*+ mycat:repairPhysicalTable{} */;
刷新完成之后我们可以在后台的MySQL里面看到数据表已创建完成。
在MyCAT里面添加数据:
insert INTO sys_user(id,username,address) values(1,"xiaofang","wuhan");
完成之后刷新后端MySQL物理库,我们发现有数据了。
在MyCAT里查询测试:
修改上面5个MySQL数据库中db1库中sys_user表里面的数据,让它不一样,再在MyCAT里面执行查询 结果如下:
MyCAT2分库分表
什么是分库分表?
分库
分库是指在表数量不变的情况下对库进行切分。
举例:如下图,数据库A 中存放了 user 和 order 两张表,将两张表切分到两个数据库中,user表放到 database A,order表放到 database B。
分表
分表是指在库数量不变的情况下对表进行切分。
举例:如下图,数据库 A 中存放了 user表,将 user表切分成 user1 和 user2 两张表并放到 database A中。
分库分表
分库分表是指库和表都切分,数量都发生变化。
举例:如下图,数据库 A 中存放了 user表,将 user表切分成 user1、user2、user3、user4 四张表,user1 和 user2 放到 database A中,user3 和 user4 放到 database B 中。
如何切分库和表?
主流的切分方式有 3种:水平切分、垂直切分和混合切分
水平切分
水平切分包含水平分库和水平分表。
水平分表
水平分表指的表结构不变,将单表数据切分成多表。切分后的结果:
- 每个表的结构一样;
- 每个表的数据不一样;
- 所有表的数据并集为全量数据;
切分抽象图如下:
举例:如下图,order表,按照 oder_id 的数据范围水平切分后变成了 order1 和 order2 表,两个表的结构一样,数据不同。
2.1.2 水平分库
水平分库是指,将表水平切分后分到不同的数据库,使得每个库具有相同的表,表中的数据不相同,水平分库一般是伴随水平分表。
举例:如下图,order 表,水平切分后,分到 database A 和 database B 中,这样原来一个库就被拆分成 2个库。
垂直切分
垂直切分包含垂直分库和垂直分表。
垂直分表
垂直分表指将存在一张表中的字段切分到多张表。切分后的结果:
- 每个表的结构不一样;
- 每个表的数据不一样;
- 所有表的字段并集是原表的字段;
切分抽象图如下:
举例:如下图,order 表,根据字段垂直切分,切分后 order_base表包含一部分字段的数据 和 order_info表包含另一部分字段的数据。
垂直分库
垂直分库指的是,将单个库中的表分到多个库,每个库包含的表不一样。
举例:如下图,database A 中的 order 表 和 user表,垂直分库为 database A 包含 order表,database B 包含 user 表。
混合切分
混合切分其实就是水平切分和垂直切分的组合,切分抽象图如下:
举例:如下图,order表,按照 oder_id数据范围做了水平切分,并且按照表字段做了垂直切分。
说明: 上面的举例只是为了更好的展示如何切分,并不包含真实业务内容。
切分策略
主流的切分策略有3种:Range 范围、hash切分、映射表。
分库分表的依据--分库分表字段的选择
分库分表首先要确定根据哪个字段、或者哪几个字段进行路由,一般的原则是按使用频率最高维度的字段去分库分表,尽量保证高使用维度下只查询单表。
Range 范围
Range 范围是指按某个字段的数据区间来进行切分。
比如:user表按照 user_id 的数据范围切分成多张表,每 1000万条数据存放一张表,切分后的表可以放到同一个数据库,也可以放到不同的数据库,示例图如下:
优点
- 方便扩容,每次数据量达到 range值就新加一张表,可以通过代码实现自动化扩容;
缺点
- 存在写偏移,可能有热点问题;
举例 比如用户注册场景:user表,因为新注册的用户数据都是写新表,通常来说新用户的活跃度高,所以读写流量全部集中在最新的 user表,因此,新表可能存在热点问题。
Hash切分
通过对分表键 key 进行一定的运算(通常有取余、取模运算,比如:key % m,key / m,hash(key)/m 等等),通过运算结果来决定路由的库和表。目前大多数互联网公司主要采用该方法。
优点
- 数据分片比较均匀,大大降低热点问题;
缺点
- hash 算法选择不合理,后期扩容可能需要迁移数据;
- 数据被切分到不同的库和表中,可能存在跨节点查询和分页等问题;
举例
比如:user表信息,根据 user_id 对 10 取余,这样就可以通过 user_id 尾号 hash 到 user_0 到 user_9 10张表中:
映射表
映射表其实是 Range范围 和 hash切分的混合模式,将分表键和数据库的映射关系记录在一个单独的表(表的形式可以是 数据库表,文件或者配置中心)。
优点
- 可以灵活设置路由规则;
缺点:
- 方案比较复杂;
- 映射表可能也会随着业务量的增大,同样需要分库分表,带来更多的问题;
举例
某社区电商下单场景,因为全国仓库的数量有限,所以分库直接使用了仓编编码-数据库映射表(后期新增加仓库,只要在表中增加映射关系),为了保证履约的时效性,用户下单时,商城端会选择最近的仓库,服务器在映射表中根据仓库编码查询并路由到对应的数据库,最后在库中进行 order表的操作,交互如下图:
分片算法简介
取模哈希分片 MOD_HASH
1)如果分片值是字符串则先对字符串进行Hash转换为数值类型
2)分库键和分表键是同键
3)分表下标=分片值%(分库数量*分表数量)
4)分库下标=分表下标/分表数量
5)分库键和分表键是不同键
6)分表下标= 分片值%分表数量
7)分库下标= 分片值%分库数量
create table travelrecord ( .... ) ENGINE=InnoDB DEFAULT CHARSET=utf8 dbpartition by MOD_HASH (id) dbpartitions 6 tbpartition by MOD_HASH (id) tbpartitions 6; |
范围哈希分片 RANGE_HASH
1)RANGE_HASH(字段1, 字段2, 截取开始下标)
2)仅支持数值类型,字符串类型
3)当时字符串类型时候,第三个参数生效
4)计算时候优先选择第一个字段,找不到选择第二个字段
5)如果是字符串则根据下标截取其后部分字符串,然后该字符串hash成数值
6)根据数值按分片数取余
7)要求截取下标不能少于实际值的长度
8)两个字段的数值类型要求一致
create table travelrecord( ... )ENGINE=InnoDB DEFAULT CHARSET=utf8 dbpartition by RANGE_HASH(id,user_id,3) dbpartitions 3 tbpartition by RANGE_HASH(id,user_id,3) tbpartitions 3; |
字符串哈希分片 UNI_HASH
1)如果分片值是字符串则先对字符串进行hash转换为数值类型
2)分库键和分表键是同键
3)分库下标=分片值%分库数量
4)分表下标=(分片值%分库数量)*分表数量+(分片值/分库数量)%分表数量
5)分库键和分表键是不同键
6)分表下标= 分片值%分表数量
7)分库下标=分片值%分库数量
create table travelrecord ( .... ) ENGINE=InnoDB DEFAULT CHARSET=utf8 dbpartition by UNI_HASH (id) dbpartitions 6 tbpartition by UNI_HASH (id) tbpartitions 6; |
日期哈希分片 YYYYDD
1)仅用于分库
2)DD是一年之中的天数
3)(YYYY*366+DD)%分库数
create table travelrecord ( .... ) ENGINE=InnoDB DEFAULT CHARSET=utf8 dbpartition by YYYYDD(xxx) dbpartitions 8 tbpartition by xxx(xxx) tbpartitions 12; |
MyCAT2全局ID的生成方式
为什么要全局ID
在复杂的分布式系统中,需要对大量的数据和消息进行唯一标识。如在阿里,淘宝,支付,等系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息;还有如美团和饿了吗的骑手ID 商家ID 优惠券ID等,从以上可以得出,一个能够生成全局唯一ID的系统是非常必要的
在MyCAT2中,自动默认使用雪花片法生成全局序列号。
如果不需要MyCAT默认全局序列,可以通过配置关闭自动加全局序列;建表语句方式关闭全局序列。
如果不需要使用MyCAT的自增序列,而使用MySQL本身的自增主键的功能。需要在配置中更改对应的建表SQL。不设置auto_increment关键字,这样 MyCAT就不认为这个表有自增主键的功能。就不会使用MyCAT全局序列号,这样,对应的插入SQL在MySQL中去处理,由MySQL的自增主键功能补全。
雪花算法:引入了时间戳的ID保持自增的分布式ID生成算法。
使用默认的雪花算法验证
如果不需要使用MyCAT的自增序列,而使用MySQL本身的自增主键的功能,需要在配置中更改对应的建表SQL,不设置AUTO_INCREMENT关键字,这样MyCAT就不认为这个表有自增主键的功能,就不会使用MyCAT的全局序列号。对应的插入SQL在MySQL处理,由MySQL的自增主键功能补全自增值。
向订单表里面添加数据
INSERT INTO ORDERS(ORDER_TYPE,CUSTOMER_ID,AMOUNT) VALUES(103,102,102102); |
上面的SQL里面没有使用添加ID,那么后台是怎么处理的呢?查看后台数据发现使用的雪花算法
大厂实际业务体会
分库分表如何落地?
敲黑板……重点,重点,重点,重要的事情说三遍!!!
互联网业内有句经典名言”Talk is cheap.Show me your code”,理论讲再多,无法付诸实际生产都是空谈。
这里以某大厂社区电商订单业务的真实案例来分享如何落地分库分表。
场景:社区电商下每日 3000万下单场景
评估库和表的总数
一般评估的标准是:当前日订单峰值 M * 支持最大的爆发增长速率 R * 业务能支撑 Y 年发展 * 365天/年,单表存储 1000万数据按。
预估数据总数:日订单 3000万,一年按 365天计数,最大支持日订单 10倍的增长速度(即日订单量 1亿),业务能支撑 10年发展,因此,需要存储的总订单量 Total = 3000w * 365 * 10 * 10 ≈ 10000亿,万亿级。
评估库和表的总数:每张表按 1000万存储(库总数 * 表总数 = 10000亿 / 1000万),因此,库总数 * 表总数 = 10万,组合方式有『1个库 * 10万张表、10个库 * 1万张表、100个库 * 1000张表 等』,整体来看,”100个库 * 1000张表”这种组合数据离散比较均匀, 在计算机中,一般采用 2^n 来计数。所以,100个库 * 1000张表可以比较接近 2^7 * 2^10 = 128 * 1024,所以最终 128个库,每个库 1024张表。
分库分表字段的选择
在单库单表中,可以直接进行 join查询和分页操作,分库分表后,数据会分到不同的数据库和表上,可能会导致跨库查询等问题,因此,分表字段的选择,决定了能否将原本需要进行分页的数据划分到同一张表上,从而避免跨库查询。
So,如何选择分库分表字段?
有用过社区电商产品(橙心优选,美团优选,多多买菜,盒马邻里)的小伙伴应该知道,社区电商的模式是:当日购买,次日履约。
为了保证履约的时效,用户在下单时,商城端都是把订单下到最近的仓库,因此,可以根据仓库编码来分库。
在整个销售链路和履约链路中,有几个高发的订单查询场景,因此分表字段的选择必须满足这些场景:
用户视角:查询自己所有的订单,因此,可以通过 user_id 分表,把某用户所有的订单放到同一张表。
团长视角:查询用户下给自己的所有订单,因此,可以通过 tuan_user_id 分表,把某团长的所有的订单放到同一张表。
商家视角:查询用户下给自己的所有订单,因此,可以通过 merchant_id 分表,把某商家的所有的订单放到同一张表。
客服视角:通过订单号查询某个订单,因此,通过 order_id 分表能够快速查询订单信息。
上述 4种场景都是订单表高发查询的场景,但是目前常用的分库分表中间件都只能支持一个分表字段,该如何解决上面 4种查询问题呢?
通常的做法有:冗余数据和关系索引表。
其实在计算中的世界很多时候都是时间和空间的一个权衡问题,是拿时间换空间,还是拿空间换时间?冗余数据和关系索引表就很好的体现了时间和空间的权衡关系。
冗余数据: 相同的数据保存多份,每份数据使用不同的分表字段,从而满足各种查询需求。如下图所示:通过 user_id、tuan_user_id、merchant_id、order_id 4个字段来分表,因此需要冗余 4份相同数据的 order表。
很显然,冗余数据是通过空间换时间的做法,优点是只要一次查询请求就能满足业务需求,缺点就是相同数据保存多份,浪费了空间,增加了成本。
淘宝的订单表采用的是数据冗余,拆分买家库和卖家库两个库,一个订单,在买家和卖家库里都存储了一份。
关系索引表:它是指建立查询条件和基表分表键的索引关系。如下图,订单表是基表,通过建立 user_id 和 order_id,tuan_user_id 和 order_id,merchant_id 和 order_id 的关系索引表来满足几种查询场景:
很显然,关系索引表是通过时间换空间的做法,优点是相对数据冗余法节省了空间和成本,缺点是多了一次索引表的查询,因此时间相对就增加了。该方式额外增加的时间在高并发特别大的场景就能显现出来。
因此,最后分库分表模型是根据仓库编码 warehouse_code 来分库,根据分表字段路由到 order表,如下图:
疑问:
疑问1:上述案例的数据库只能支撑 10年,10年以后的数据怎么存储?
有过网购经验的小伙伴应该都很少去查询3年前的数据,因此,我们可以设置一个冷热数据,比如按 3年划分,3年内数据可以放到数据库做热数据,3年前的数据可以归档到 ElasticSearch/hive,做冷数据查询。
疑问2:如何查询某一段时间的订单?
可以同步到 ElasticSearch/hive,这样就可以很方便的按时间段来查询。
疑问3:上述案例完全是新业务,如果已经有线上服务和数据,该如何分库分表?
这个场景是很多公司面临的问题,因此这里给出一个切分的标准处理流程 SOP(Standard operating procedure:
立项讨论:
这个步骤需要完成和相关部门以及人员确认分库分表事项、实施日程、后期周知、风险以及应对方案等事宜。
技术方案:
技术方案需要给出详细迁移方案,包括分库分表方案,代码改造,服务器过渡到新库新表方案,数据迁移方案,风险处理方案等。
代码改造:
代码改造,主要会涉及到几个部分:服务如何过渡到新库新表,如何灵活支持灰度读写操作,如何进行数据全量迁移、一致性校验等任务。
分库分表方案:
分库分表方案需要确认分库分表的字段,库和表的数量等问题,可以参考上文 社区电商分库分表落地方案。
数据同步:
数据同步有全量数据迁移、增量数据同步以及数据校验、优化和补偿。
数据全量迁移常用方案:开发代码将老库数据迁移到新库;使用中间件同步工具(比如:阿里的 canal)将老库数据同步到新库。
增量数据同步常用方案:同步双写,在写数据库的地方修改成写两份数据;异步双写,写老库,监听binlog异步同步到新库;中间件同步:通过中间件(比如:阿里的 canal)将数据同步到目标库表。
数据校验常用方案:增量数据校验 和 全量数据校验 和 人工抽检。
数据校验核心流程:分别读取老库数据和新库数据,然后比较,数据一致则继续比较下一条数据,数据不一致则进行补偿。
数据补偿核心流程:新库存在老库不存在,则新库删除数据; 新库不存在老库存在,则新库插入数据;新库存在老库存在,则比较所有字段,不一致则将新库更新为老库数据。
风险处理方案:
风险处理包含部门间配合,技术方案的处理(服务回滚,数据修复等)
搭建分库分表架构
这里好鬼复杂,不记录了,想要搭建的自己找篇文章照着搭一下吧
理论学习下就好,工作中数据库分库分表的搭建一般也轮不到我们写Java的来搞,这些配置类的东西要么丢给DBA做,要么找篇文章一步步跟着来,咱儿重心还是放在CRUD上吧.....