-
- 主从复制
概述
主从复制是指将主库的DDL和DML操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。
MySQL支持一台主库同时向多台从库进行复制,从库同时也可以作为其他从服务器的主库,实现链状复制。
MySQL 复制的优点主要包含以下三个方面:
失败迁移:主库出现问题,可以快速切换到从库提供服务。
读写分离,降低主库的访问压力。
从库备份,避免备份期间影响主库服务。
原理
MySQL主从复制的核心就是二进制日志,具体的过程如下:
Master主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。
从库IO线程请求并读取主库Binlog Dump线程发送的二进制日志更新,然后写入到从库的中继日志Relay Log。
从库SQL线程重做中继日志中的数据变更,从而与主库保持一致。
主从复制模式
MySQL的主从复制模式有异步复制、半同步复制、GTID复制。
异步模式
异步模式是MySQL的默认复制模式,主要是指主库在执行完客户端提交的事务后,只要将执行逻辑写入到binlog后,就立即返回给客户端,并不关心从库是否执行成功。这种模式效率高但容易造成从库数据丢失:当同步未完成时主库挂了,从库被强行提升为主库,这个时候就有可能造成数据丢失。
同步模式
MySQL从5.5版本开始通过以插件的形式开始支持同步的主从复制模式:当主库执行完客户端提交的事务后,需要等到所有从库也都执行完这一事务后,才返回给客户端执行成功。这种模式效率低。
半同步模式
主库在执行完客户端提交的事务后,要等待至少一个从库接收到binlog并写入relay log且回复主库ACK后才返回给客户端成功结果。
当主库在规定的时间内未接收到从库的ACK时,将自动降级为异步复制,直到异常修复后再自动变为半同步复制。
半同步复制的隐患:当事务在主库提交完后等待从库ACK的过程中,如果Master宕机了,这个时候就会有两种情况:
- 主库提交完事务,binlog还没发送到从库时宕机。客户端请求超时,重新提交事务,事务在新的主库上执行。主库恢复后以从库身份加入到集群中,此时事务将被再次执行(前次是作为主库时执行)
- 主库提交完事务,发送完binlog,等待从库ACK时宕机(从库收到binlog且提交了事务)。客户端请求超时,重新提交事务,此时事务就会在已执行事务的新主库上再次执行(前次是作为从库时执行)。
为了解决上面的隐患,MySQL从5.7版本开始,增加了一种新的半同步方式。新的半同步方式的执行过程是将“Storage Commit”这一步移动到了“Write Slave dump”后面。这样保证了只有收到Slave的事务ACK后,主库才提交事务。MySQL5.7.2版本新增了一个参数来进行配置:rpl_semi_sync_master_wait_point,此参数有两个值可配置:
AFTER_SYNC:默认值,代表采用的是新的半同步复制模式。
AFTER_COMMIT:代表采用的是旧的半同步复制模式。
新的AFTER_SYNC半同步模式不能解决上面的第2)中异常情况
几个的半同步复制模式的参数:
rpl_semi_sync_master_enabled | ON 半同步复制模式开关
rpl_semi_sync_master_timeout | 10000 半同步复制超时毫秒数,超过此时间后,自动切换为异步复制模式
rpl_semi_sync_master_trace_level | 32
rpl_semi_sync_master_wait_for_slave_count | 1 (始于5.7.3)需要等待多少个slave应答,才能返回给客户端,默认为1
rpl_semi_sync_master_wait_no_slave | ON 表示当前集群中的slave数量是否还能够满足当前配置的半同步复制模式,默认为ON,当不满足半同步复制模式后,全部Slave切换到异步复制,此值也会变为OFF
rpl_semi_sync_master_wait_point | AFTER_SYNC 半同步复制提交事务的方式,5.7.2之后,默认为AFTER_SYNC
rpl_stop_slave_timeout | 31536000
GTID模式
MySQL从5.6版本开始推出了GTID复制模式,GTID即全局事务ID(global transaction identifier)的简称,GTID是由UUID+TransactionId组成的,UUID是单个MySQL实例的唯一标识,在第一次启动MySQL实例时会自动生成一个server_uuid,并且写入到数据目录文件中(mysql/data/auto.cnf)。TransactionId是该MySQL上执行事务的数量,随着事务数量增加而递增。这样保证了GTID在一组复制中,全局唯一。
这样通过GTID可以清晰地看到,当前事务是从哪个实例上提交的第几个事务。
GTID可用命令show master status查询(Executed_Gtid_Set列)
GTID的工作原理
当从服务器连接主服务器时,把自己执行过的GTID( Executed_Gtid_Set: 即已经执行的事务编码 )以及获取到GTID( Retrieved_Gtid_Set: 即从库已经接收到主库的事务编号 )都传给主服务器。主服务器会从服务器缺少的GTID以及对应的transactionID都发送给从服务器,让从服务器补全数据。当主服务器宕机时,会将同步数据最成功的那台从服务器提升为主服务器。若是强制要求某一台不是同步最成功的一台从服务器为主,会先通过change命令到最成功的那台服务器,将GTID进行补全,然后再把强制要求的那台机器提升为主。
主要数据同步机制可以分为这几步:
master更新数据时,在事务前生产GTID,一同记录到binlog中。
slave端的i/o线程,将变更的binlog写入到relay log中。
sql线程从relay log中获取GTID,然后对比Slave端的binlog是否有记录。
如果有记录,说明该GTID的事务已经执行,slave会忽略该GTID。
如果没有记录,Slave会从relay log中执行该GTID事务,并记录到binlog。
在解析过程中,判断是否有主键,如果没有主键就使用二级索引,再没有二级索引就扫描全表。
当Master挂掉后,Slave-1执行完了Master的事务,Slave-2延时一些,所以没有执行完Master的事务,这个时候提升Slave-1为主,Slave-2连接了新主(Slave-1)后,将最新的GTID传给新主,然后Slave-1就从这个GTID的下一个GTID开始发送事务给Slave-2。 这种自我寻找复制位置的模式减少事务丢失的可能性以及故障恢复的时间。
GTID的优点
每一个事务对应一个执行ID,一个GTID在一个服务器上只会执行一次;
GTID是用来代替传统复制的方法,GTID复制与普通复制模式的最大不同就是不需要指定二进制文件名和位置;
减少手工干预和降低服务故障时间,当主机挂了之后通过软件从众多的备机中提升一台备机为主机;
GTID的缺点
首先不支持非事务的存储引擎;
不支持create table ... select 语句复制(主库直接报错);(原理: 会生成两个sql, 一个是DDL创建表SQL, 一个是insert into 插入数据的sql; 由于DDL会导致自动提交, 所以这个sql至少需要两个GTID, 但是GTID模式下, 只能给这个sql生成一个GTID)
不允许一个SQL同时更新一个事务引擎表和非事务引擎表;
在一个MySQL复制群组中,要求全部开启GTID或关闭GTID。
开启GTID需要重启 (mysql5.7除外);
开启GTID后,就不再使用原来的传统复制方式(不像半同步复制,半同步复制失败后,可以降级到异步复制);
对于create temporary table 和 drop temporary table语句不支持;
不支持sql_slave_skip_counter;
开启GTID的必备条件
MySQL 5.6 版本,在my.cnf文件中添加:
gtid_mode=on (必选) #开启gtid功能
log_bin=log-bin=mysql-bin (必选) #开启binlog二进制日志功能
log-slave-updates=1 (必选) #也可以将1写为on
enforce-gtid-consistency=1 (必选) #也可以将1写为on
MySQL 5.7或更高版本,在my.cnf文件中添加:
gtid_mode=on (必选)
enforce-gtid-consistency=1 (必选)
log_bin=mysql-bin (可选) #高可用切换,最好开启该功能
log-slave-updates=1 (可选) #高可用切换,最好打开该功能
一主一从搭建
安装CentOS7虚拟机
使用Vagrant安装一台CentOS7虚拟机(参见Vagrant章节),在Vagrant的配置文件Vagrantfile中配置3个私有静态IP:
config.vm.network "private_network", ip: "192.168.56.20"
config.vm.network "private_network", ip: "192.168.56.21"
config.vm.network "private_network", ip: "192.168.56.22"
其中
192.168.56.20作为MySQL中间件服务器的IP
192.168.56.21作为MySQL主库master1的IP
192.168.56.22作为MySQL从库slave1的IP
开放MySQL端口或关闭防火墙(不开也没事?)
开放端口:
firewall-cmd --zone=public --add-port=3306/tcp -permanent
firewall-cmd -reload
或者关闭防火墙:
systemctl stop firewalld
systemctl disable firewalld
安装Docker并启动
参见Docker安装章节
安装MySQL
在两台服务器上分别安装MySQL:
主服务器master安装MySQL | 从服务器slave安装MySQL | 注: |
sudo docker run \ -p 192.168.56.21:3306:3306 \ --name mysql-master1 \ -e MYSQL_ROOT_PASSWORD=root \ -v /mydata/mysql/master1/conf:/etc/mysql/conf.d \ -v /mydata/mysql/master1/data:/var/lib/mysql \ -v /mydata/mysql/master1/log:/var/log/mysql \ -d mysql:8.0.29 | sudo docker run \ -p 192.168.56.22:3306:3306 \ --name mysql-slave1 \ -e MYSQL_ROOT_PASSWORD=root \ -v /mydata/mysql/slave1/conf:/etc/mysql/conf.d \ -v /mydata/mysql/slave1/data:/var/lib/mysql \ -v /mydata/mysql/slave1/log:/var/log/mysql \ -d mysql:8.0.29 | 查看下载的镜像: docker images 查看运行中的容器: docker ps 查看所有状态的容器: docker ps -a 查看启动失败容器的日志: docker logs 容器ID 进入容器内部: docker exec -it 容器名称 /bin/bash |
如果安装时提示WARNING: IPv4 forwarding is disabled. Networking will not work
解决:
#修改配置文件:
vim /usr/lib/sysctl.d/00-system.conf
#追加内容:
net.ipv4.ip_forward=1
#重启网络:
systemctl restart network
主库配置
创建配置文件
vi /mydata/mysql/master1/conf/my.cnf
配置以下内容
[mysqld]
# 服务器唯一id,默认值1
server-id=1
# 二进制日志名,默认binlog
# log-bin=binlog
#是否只读,0代表读写,1代表只读
read-only=0
# 设置需要同步的数据库,默认同步全部数据库
binlog-do-db=db01
# 设置不需要同步的数据库
#binlog-ignore-db=mysql
#binlog-ignore-db=infomation_schema
设置MySQL服务随系统启动
docker update mysql-master1 --restart=always
重启MySQL容器
docker restart mysql-master1
注:如果不是容器部署,则重启MySQL服务:systemctl restart <mysql服务名>d
登录MySQL,创建远程连接的账号,并授予主从复制权限
#创建slave1用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'slave1'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
#为 'slave01'@'%' 用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'%';
# 刷新权限
FLUSH PRIVILEGES;
查看二进制日志坐标(File列和Position列的值),配置从库时要用到。执行完此命令后从库配置完成前不要再操作,以防坐标发生变化
mysql> show master status;
字段含义:
file:从哪个日志文件开始推送日志文件
position:从哪个位置开始推送日志
从库配置
创建配置文件
vi /mydata/mysql/slave1/conf/my.cnf
配置以下内容
[mysqld]
#服务器唯一id,默认值1
server-id=2
#是否只读,1 代表只读, 0 代表读写
read-only=1
# 中继日志名,默认xxxxxxxxxxxx-relay-bin
#relay-log=relay-bin
注:read-only=1对拥有super和connection_admin这两个权限的用户无效,如要限制这两类权限用户,增加配置:super-read-only=1
设置MySQL服务随系统启动
docker update mysql-slave1 --restart=always
重启MySQL容器
docker restart mysql-slave1
注:如果不是容器部署,则重启MySQL服务:systemctl restart <mysql服务名>d
登录MySQL,设置主库信息
注:如果是中途开始使用主从复制,在设置主库信息前需先把主库数据导出导入到从库,保证两者数据一致。
CHANGE MASTER TO MASTER_HOST='192.168.56.21', MASTER_USER='slave1', MASTER_PASSWORD='123456', MASTER_LOG_FILE='binlog.000003', MASTER_LOG_POS=887;
注意这里的MASTER_LOG_FILE和MASTER_LOG_POS参数值要和上面主库查出来的二进制日志坐标(File列和Position列的值)保持一致。
如果主库端口不是默认的3306,要添加参数:MASTER_PORT=主库端口
开启同步操作
start slave;
查看主从同步状态
show slave status;
重点确认Slave_IO_Running和Slave_SQL_Running两项信息是否都为Yes
如果不是Yes,查看Last_IO_Error、Last_SQL_Error列信息解决。
常见错误信息:Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Client requested master to start replication from position > file size'
解决:
-- 在从机停止slave
SLAVE STOP;
-- 在主机查看mater状态
SHOW MASTER STATUS;
-- 在主机刷新日志
FLUSH LOGS;
-- 再次在主机查看mater状态(会发现File和Position发生了变化)
SHOW MASTER STATUS;
-- 修改从机连接主机的SQL,并重新连接即可
...
注:
1)自8.0.23起,设置主库信息的语法改为(旧语法不赞成使用):
CHANGE REPLICATION SOURCE TO SOURCE_HOST='192.168.56.21', SOURCE_USER='slave1', SOURCE_PASSWORD='123456', SOURCE_LOG_FILE='binlog.000003', SOURCE_LOG_POS=887;
2)自8.0.22起,开启同步操作和查看主从同步状态的语法改为(旧语法不赞成使用):
start replica;
show replica status;
查询结果列Slave_IO_Running和Slave_SQL_Running被代替为Replica_IO_Running和Replica_SQL_Running
测试
在主库192.168.56.21创建数据库、表,并插入数据
在从库192.168.56.22查询数据,验证主从是否同步
停止和重置
需要的时候,可以使用如下SQL语句
在从库上执行
# 停止同步:停止I/O 线程和SQL线程的操作。
stop slave;
# 重置从库:删除SLAVE库的relaylog日志文件,并重新启用新的relaylog文件。
reset slave;
在主库上执行
# 重置主库:删除所有的binglog日志文件,并将日志索引文件清空,重新开始所有新的日志文件。
reset master;
双主双从搭建
一主一从当主库宕机时无法修改数据,因此需要双主双从。
架构
一个主机Master1用于处理所有写请求,它的从机Slave1和另一台主机Master2及其从机Slave2负责所有读请求。
当Master1主机宕机后,Master2 主机负责写请求,Master1、Master2互为备机。
两个主机都增加了log-slave-updates选项(在作为从数据库的时候,有写入操作也要更新二进制日志文件),
另外就是两个主机之间也要互相复制同步,注意 ip,日志文件和同步位置 不要弄错了
服务器配置
角色 IP地址 my.cnf中的 server-id 端口号
主机 0 MyCat服务器 172.16.208.210 8066
主机 1 Master1 172.16.208.211 1 3306
从机 1 Slave1 172.16.208.212 2 3306
主机 2 Master2 172.16.208.213 3 3306
从机 2 Slave2 172.16.208.214 4 3306
所有服务器开放端口或关闭防火墙
开放端口:
firewall-cmd --zone=public --add-port=端口号8066或3306/tcp -permanent
firewall-cmd -reload
或者关闭防火墙:
systemctl stop firewalld
systemctl disable firewalld
my.cnf配置
两台主机/etc/my.cnf配置
注意server-id不一样,其余都一样
Master1的/etc/my.cnf中追加以下参数: | Master2的/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 | #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 |
两台从机my.cnf配置
配置server-id即可
Slave1的/etc/my.cnf中追加以下参数: | Slave2的/etc/my.cnf中追加以下参数: |
#MySQL服务ID,集群中唯一,取值范围1至2(32次方)-1,默认1 server-id=2 | #MySQL服务ID,集群中唯一,取值范围1至2(32次方)-1,默认1 server-id=4 |
建立主从同步
两台主库配置
# 登录MySQL,创建远程连接的账号,并授予主从复制权限
CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
# 刷新权限
flush privileges;
# 查询master的状态,记录下File和Position的值,执行完此步骤后不要再操作主服务器MySQL,防止主服务器状态值变化
show master status;
两台从库配置
# 登录MySQL,指定当前从库对应的主库的IP地址,用户名,密码,从哪个日志文件开始的哪个位置开始同步日志
CHANGE MASTER TO
MASTER_HOST='<主库IP地址>',
MASTER_USER='slave',
MASTER_PASSWORD='Root@123456',
MASTER_LOG_FILE='<主库日志File>',
MASTER_LOG_POS=<主库日志Position>;
# 启动同步
START SLAVE;
# 停止同步:STOP SLAVE;
# 查看同步状态,确认Replica_IO_Running和Replica_SQL_Running两项信息是否都为Yes
SHOW SLAVE STATUS \G;
两台主库互相复制配置
方法同“两台从库配置”,注意IP、日志文件和同步位置不要弄错了
配置好后双主机、双从机重启mysql服务
双主双从主从复制搭建失败重新搭建命令
如果搭建失败,需要重新配置的话,可以执行下面的命令
### 在两台主机上执行 ###
# 停止同步
STOP SLAVE;
# 重置主机
RESET MASTER;
### 在两台从机上执行 ###
# 停止同步
STOP SLAVE;
# 重置从机
RESET SLAVE;
阿里巴巴Java开发手册建议,若预期单表行数超过500万或单表容量超过2GB时,考虑分库分表。
当单表行数超过500万,字段超过20个,且包含超长Varchar、Text、Clob、Blob字段
拆分策略
垂直分库:根据业务将不同表拆分到不同库中。
涉及难题:分布式事务问题
垂直分表:将一个表的不同字段拆分到不同表中。适用于单条记录内容多的表
水平分库:将一个库的表数据拆分到多个库中。
水平分表:将一个表的数据拆分到多个表中。适用于记录行数大的表
涉及难题:扩容问题、分页问题、主键问题
分片策略
分表策略生产实践
订单表分表策略
业务分析
订单服务有如下两类典型业务需求:
1、用户侧:前台访问
根据uid查询订单列表;根据订单ID查询订单。
用户侧的查询,访问量较大,服务需要高可用,并且对一致性要求较高。
2、运营侧:后台访问
通过订单ID、用户ID、商家ID、交易时间等维度来进行查询。
运营侧的查询,基本上是分页查询,访问量低,对可用性和一致性要求不高。
解决方案:
- 用户侧:
映射法:
- 根据uid分片,通过uid搜索时直接定位分片。
- 建立其他字段到uid的映射关系(放缓存中提高性能),其他字段搜索时,先根据映射关系查到uid,再根据uid定位分片。
业务因子法:
- 从uid中根据特定函数生成业务因子,融入到订单ID的尾部。
- 根据业务因子分片,查询条件有订单ID或uid时,都能定位分片。
- 运营侧:
- 用户侧和运营侧订单表分离解耦,避免后台低效查询引发前台查询抖动。
- 可以采用“外置索引”(例如ES搜索系统)或者“大数据处理”(例如HIVE)来满足后台变态的查询需求
实现技术
ShardingSphere-JDBC:基于AOP原理,在应用程序中对本地执行的SQL进行拦截,解析、改写、路由处理。需要自行编码配置实现,只支持java语言,性能较高。
ShardingSphere-Proxy:数据库中间件,目前只支持MySQL和PostgreSQL。
MyCat:源于阿里的数据库分库分表中间件,不用调整代码即可实现分库分表,支持多种语言,性能不及前者(中间件网络通信耗时)。
注意问题
- 如果查询条件不带上分片字段,就会从所有分片中查询数据然后汇总,容易导致内存溢出。
- 5
介绍
ShardingSphere:源于当当网的Apache开源分布式数据库生态项目,分为三个产品:
ShardingSphere-JDBC:轻量级Java数据访问层框架,完全兼容JDBC和各种ORM框架。应用程序直接访问数据库。
ShardingSphere-Proxy:数据库中间件,目前只支持MySQL和PostgreSQL。应用程序不直接访问数据库。
ShardingSphere-Sidecar(规划中):Kubernetes的云原生数据库代理
适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
支持任意实现JDBC规范的数据库。目前支持 MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。
添加依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.1</version>
</dependency>
application.properties添加配置项
# 内存模式
spring.shardingsphere.mode.type=Memory
# 配置真实数据源
spring.shardingsphere.datasource.names=master1,slave1,slave2
# 配置第 1 个数据源
spring.shardingsphere.datasource.master1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.master1.jdbc-url=jdbc:mysql://192.168.56.21:3306/db01
spring.shardingsphere.datasource.master1.username=root
spring.shardingsphere.datasource.master1.password=root
# 配置第 2 个数据源
spring.shardingsphere.datasource.slave1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.slave1.jdbc-url=jdbc:mysql://192.168.56.22:3306/db01
spring.shardingsphere.datasource.slave1.username=root
spring.shardingsphere.datasource.slave1.password=root
# 配置第 3 个数据源
spring.shardingsphere.datasource.slave2.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.slave2.jdbc-url=jdbc:mysql://192.168.56.25:3306/db01
spring.shardingsphere.datasource.slave2.username=root
spring.shardingsphere.datasource.slave2.password=root
# 读写分离类型,如:Static,Dynamic
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.type=Static
# 写数据源名称
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.write-data-source-name=master1
# 读数据源名称,多个从数据源用逗号分隔
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.read-data-source-names=slave1,slave2
# 负载均衡算法名称
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.load-balancer-name=alg_round
# 负载均衡算法配置
# 负载均衡算法类型
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_round.type=ROUND_ROBIN
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_random.type=RANDOM
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.type=WEIGHT
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave1=1
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave2=2
# 打印SQl,在日志中会打印Logic SQL和Actual SQL(5.x版本前sql-show参数要调整为sql.show)
spring.shardingsphere.props.sql-show=true
测试读写分离
@Test
public void testInsert(){
User user = new User();
user.setUname("张三丰");
userMapper.insert(user);
}
测试事务
为了保证主从库间的事务一致性,避免跨服务的分布式事务,ShardingSphere-JDBC的主从模型中,事务中的数据读写均用主库。
@Transactional//开启事务
@Test
public void testTrans(){
User user = new User();
user.setUname("铁锤");
userMapper.insert(user);
List<User> users = userMapper.selectList(null);
}
不添加@Transactional:insert对主库操作,select对从库操作
添加@Transactional:则insert和select均对主库操作
注意:在JUnit环境下的@Transactional注解,默认情况下就会对事务进行回滚(即使没加注解@Rollback,也会对事务回滚)
测试负载均衡
@Test
public void testSelectAll(){
List<User> users = userMapper.selectList(null);
List<User> users = userMapper.selectList(null);//执行第二次测试负载均衡
users.forEach(System.out::println);
}
介绍
Mycat是开源的、基于Java语言编写的MySQL数据库中间件。可以像使用mysql一样来使用mycat,对于开发人员来说根本感觉不到mycat的存在。
开发人员只需要连接MyCat即可,而具体底层用到几台数据库,每一台数据库服务器里面存储了什么数据,都无需关心。具体的分库分表的策略,只需要在MyCat中配置即可。
整体结构
逻辑结构:逻辑库、逻辑表、分片规则、分片节点
物理结构:节点主机
缺点
1)分页查询加大内存压力
在分库分表的情况下,MyCat分页查询会查询各个分表的结束行之前的所有行返回合并后再分页,容易导致内存溢出。
例如,原始的limit子句LIMIT 10000,1000会被MyCat处理为LIMIT 0,11000
因此,在分库分表的情况,不要用MyCat进行大批量的数据分页查询,通过条件过滤,减小分页的数据量大小!
2)子查询结果偶尔不完整
当通过某些条件,筛选订单项数据时,某些数据偶尔出现不完整。例如
SQL | 预期结果 | 有时候的实际结果 |
select id,productName from orderItem where orderId in ( select id from order where userName = '张三' ) | 1,"巧克力" 2,"可乐" 3,"果冻" 4,"苹果手机" | 1,"巧克力" 2,"可乐" 4,"苹果手机" |
MyCat子查询除了偶尔查询不到完整数据外,还会出现MyCat内部死锁,因此尽量在代码中不要使用子查询,而是采用主键ID或是索引字段进行单表查询,这样效率会大大提升!
3)跨分片join问题
MyCat目前只支持两张分片表的join,如果要支持多张表需要自己改造程序代码或者改造Mycat的源代码。
4)部分SQL语法不支持
复制插入(不支持)
insert into......select.....
复杂更新(不支持)
update a, b set a.remark='备注' where a.id=b.id;
复杂删除(不支持)
delete a from a join b on a.id=b.id;
还有就是不支持跨库连表操作!
5)不支持存储过程创建和调用
安装
服务器配置
服务器 | 安装软件 | 说明 |
192.168.91.166 | JDK、MyCat、MySQL | MyCat中间件服务器、分片服务器 |
192.168.91.167 | MySQL | 分片服务器 |
192.168.91.168 | MySQL | 分片服务器 |
下载:
http://dl.mycat.org.cn/
解压:
tar -zxvf Mycat-server-1.6.7.3-release-20190828135747-linux.tar.gz -C /usr/local
更新JDBC驱动包:
cd /usr/local/mycat/lib/
rm -rf mysql-connector-java-5.1.35.jar
MySQL驱动包下载地址:https://downloads.mysql.com/archives/c-j/
将下载好的驱动包通过XFTP工具上传到/usr/local/mycat/lib/
修改驱动包访问权限:chmod 777 驱动包文件名
开放8066端口或关闭防火墙:
服务器都开放端口:
firewall-cmd --zone=public --add-port=8066/tcp -permanent
firewall-cmd -reload
或者关闭防火墙:
systemctl stop firewalld
systemctl disable firewalld
启动MyCat:
切换到Mycat的安装目录,执行如下指令,启动Mycat:
#启动
bin/mycat start
#停止
bin/mycat stop
Mycat启动之后,占用端口号 8066。
启动完毕之后,可以查看logs目录下的启动日志,查看Mycat是否启动完成。
[root@MySQL-Master mycat]# tail -10 logs/wrapper.log
修改了配置文件需重启MyCat或重新加载配置文件才能生效,重新加载配置文件方法:通过MyCat管理控制台(9066端口)执行reload @@config命令。
连接MyCat:
通过如下指令,就可以连接并登陆MyCat,然后像操作MySQL一样操作MyCat:
mysql -h 192.168.91.166 -P 8066 -u root -p123456
配置文件详解
MyCat主要配置文件有三个:
schema.xml:用于配置逻辑库、逻辑表、分片节点及节点主机。(<schema>、<datanode>、<datahost>)
server.xml:用于配置MyCat的系统信息和用户、访问密码,以及用户针对于逻辑库、逻辑表的权限信息。(<system>、<user>)
rule.xml:用于配置分片规则,在使用过程中可以灵活的使用分片算法,或者对同一个分片算法使用不同的参数。(<tableRule>、<function>)
schema.xml配置文件:
/usr/local/mycat/conf/schema.xml是MyCat最重要的配置文件,涵盖了MyCat的逻辑库、逻辑表、分片规则、分片节点及节点主机的配置。
主要包含三组标签:<schema>、<datanode>、<datahost>
schema标签:用于定义MyCat实例中的逻辑库,一个MyCat实例可以有多个逻辑库,通过schema标签来划分不同的逻辑库。
核心属性:
name:指定自定义的逻辑库库名,区分大小写
checkSQLschema:在SQL语句操作时指定了数据库名称,执行时是否自动去除;true:自动去除,false:不自动去除
sqlMaxLimit:如果SQL语句未指定limit,列表查询时获取最多多少条记录
子标签:
table标签:定义MyCat中逻辑库schema下的逻辑表,所有需要拆分的表都需要在table标签中定义。
核心属性:
name:定义逻辑表表名,在该逻辑库下唯一
dataNode:定义逻辑表所属的dataNode,与dataNode标签中name对应,多个dataNode逗号分隔
rule:分片规则的名字,分片规则名字是在rule.xml中定义的,分片规则适用于分表,不适用于分库
primaryKey:逻辑表对应真实表的主键
type:逻辑表的类型,目前逻辑表只有全局表和普通表,如果未配置,就是普通表;全局表,配置为global
datanode标签:
核心属性:
name:定义数据节点名称
dataHost:数据库实例主机名称,与dataHost标签中name对应
database:定义分片所属数据库
datahost标签:定义具体的数据库实例、读写分离、心跳语句。
核心属性:
name:唯一标识,供上层标签使用
maxCon/minCon:最大连接数/最小连接数
balance:负载均衡策略,取值 0,1,2,3
writeType:写操作分发方式(0:写操作转发到第一个writeHost,第一个挂了,切换到第二个;1:写操作随机分发到配置的writeHost)
dbDriver:数据库驱动,支持 native、jdbc
server.xml配置:
/usr/local/mycat/conf/server.xml配置文件包含了MyCat的系统配置信息。
主要有两个重要的标签:<system>、<user>
<system>主要配置MyCat中的系统配置信息,对应的系统配置项及其含义如下:
属性 | 取值 | 含义 |
charset | utf8mb4 | 设置Mycat字符集,需与MySQL的保持一致 |
nonePasswordLogin | 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固定线程池的大小; | |
packetHeaderSize | 指定MySQL协议中的报文头长度, 默认4个字节 | |
maxPacketSize | 指定MySQL协议可以携带的数据最大大小, 默认值为16M | |
idleTimeout | 30 | 指定连接的空闲时间的超时长度;如果超时,将关闭资源并回收, 默认30分钟 |
txIsolation | 1,2,3,4 | 初始化前端连接的事务隔离级别,默认为REPEATED_READ , 对应数字为3 |
sqlExecuteTimeout | 300 | 执行SQL的超时时间, 如果SQL语句执行超时, |
serverPort | 8066 | 定义MyCat的使用端口, 默认8066 |
managerPort | 9066 | 定义MyCat的管理端口, 默认9066 |
<user>配置MyCat中的用户、访问密码,以及用户针对于逻辑库、逻辑表的权限信息。
rule.xml配置:
/usr/local/mycat/conf/rule.xml中定义所有拆分表的规则,在使用过程中可以灵活的使用分片算法,或者对同一个分片算法使用不同的参数。主要包含两类标签:<tableRule>、<function>。
<tableRule>的name属性定义分片规则名称,该属性值被schema.xml中<schema>下<table>的rule属性引用
<tableRule>下的<rule>下的<id>指定分片处理所依据的表字段
<tableRule>下的<rule>下的<algorithm>指定分片函数,内容引用自<function>的name属性
<function>的name属性定义分片函数名称,该属性值被<tableRule>下的<rule>下的<algorithm>引用
<function>的class属性指定该分片函数对应的类
<function>下的<property>为分片函数的参数,不同分片函数参数的个数和名称不尽相同
配置示例:
其他配置... |
范围分片
按照分片字段的取值范围进行划分,将数据划分到不同节点。适用于数字类型的分片字段。
优点:方便节点扩容,无需迁移数据。
缺点:负载不均衡,新增节点的查询量比旧节点(历史数据)大。
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> |
mapFile属性:指定外部配置文件
defaultNode属性:默认节点。枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点,如果没有默认节点则报错。
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万零1-1000万之间的数据存储在1号数据节点; 1000万零1-1500万的数据节点存储在2号节点; 超出1500万的值,报错!!! |
取模分片
根据分片字段值与节点数量进行求模运算,根据运算结果来决定该数据属于哪一个分片。适用于数字类型的分片字段。
如果分片字段为主键id,可使用Redis生成全局唯一且递增的主键id。
优点:数据分布均匀。
缺点:不利扩容,数据迁移难度大。
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> |
count属性:指定数据节点的数量
一致性hash分片
相同的哈希因子计算值总是被划分到相同的分区表中。
优点:定位迅速,分布均匀,负载均衡
缺点:不利扩容。
schema.xml逻辑表分片规则配置:
<table name="tb_order" dataNode="dn4,dn5,dn6" rule="sharding-by-murmur" /> |
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="sharding-by-murmur"> <rule> <columns>id</columns> <algorithm>murmur</algorithm> </rule> </tableRule> <function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash"> <property name="seed">0</property> <property name="count">3</property> <property name="virtualBucketTimes">160</property> </function> |
seed属性:创建murmur_hash对象的种子,默认0
count属性:要分片的数据库节点数量,必须指定,否则没法分片
virtualBucketTimes属性:一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍;virtualBucketTimes*count就是虚拟结点数量 ;
weightMapFile属性:节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替
bucketMapPath属性:用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西
枚举分片
通过在配置文件中配置可能的枚举值,指定数据分布到不同数据节点上。该分片规则适用于按照省份、性别、状态拆分数据等业务。
schema.xml逻辑表分片规则配置:
<table name="tb_user" dataNode="dn4,dn5,dn6" rule="sharding-by-intfile-enumstatus"/> |
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 --> <tableRule name="sharding-by-intfile"> <rule> <columns>sharding_id</columns> <algorithm>hash-int</algorithm> </rule> </tableRule> <!-- 自己增加按status字段枚举分片 tableRule --> <tableRule name="sharding-by-intfile-user-status"> <rule> <columns>status</columns> <algorithm>hash-int-user-status</algorithm> </rule> </tableRule> <function name="hash-int-user-status" class="io.mycat.route.function.PartitionByFileMap"> <property name="defaultNode">2</property> <property name="mapFile">partition-hash-int-user-status.txt</property> </function> |
defaultNode属性:默认节点,小于0表示不设置默认节点。枚举分片时,如果碰到和枚举不匹配的字段值,就让它路由到默认节点,如果没有默认节点则报错。
mapFile属性:指定外部配置文件
partition-hash-int-user-status.txt外部配置文件配置:
1=0 2=1 3=2 | 含义: 枚举值=分片号 |
应用指定算法分片
运行阶段由应用自主决定路由到哪个分片:直接根据字符子串(必须是数字)决定分片号。
schema.xml逻辑表分片规则配置:
<table name="tb_app" dataNode="dn4,dn5,dn6" rule="sharding-by-substring" /> |
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="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> |
startIndex属性:字符子串起始索引
size属性:字符长度
partitionCount属性:分片数量
defaultPartition属性:字符子串表示的分片号不在分片数量内时,使用默认分片
示例说明:
id=050001,在此配置中代表根据id中从startIndex=0开始,截取siz=2位数字即05,05就是获取的分片号,如果没找到对应的分片则默认分配到defaultPartition。
固定分片hash算法分片
该算法类似于十进制的求模运算,但是是二进制的按位与运算求出分片范围。例如,取id的二进制低10位与1111111111进行按位与运算,位与运算最小值为0000000000,最大值为1111111111,转换为十进制,也就是位于0-1023之间。
特点
如果是求模,连续的值,分别分配到各个不同的分片;但是此算法会将连续的值可能分配到相同的分片,降低事务处理的难度。
可以均匀分配,也可以非均匀分配。
分片字段必须为数字类型。
schema.xml逻辑表分片规则配置:
<table name="tb_longhash" dataNode="dn4,dn5,dn6" rule="sharding-by-long-hash" /> |
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="sharding-by-long-hash"> <rule> <columns>id</columns> <algorithm>sharding-by-long-hash</algorithm> </rule> </tableRule> <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> |
partitionCount属性:分片个数列表
partitionLength属性:分片范围列表
上面两个参数的意思是:前2个分片长度都是256,后1个分片长度是512,总共长度为1024。根据此分片规则,MyCat会生成一个长度为1024的数组,索引0-255的元素值为0,索引256-511的元素值为1、索引512-1023的元素值为2。根据分片字段id值二进制低10位与1023的二进制(10个1)按位与运算,结果转回十进制作为数组索引,查得元素值即为分片节点。
约束:
分片长度:默认最大2^10为1024;
partitionCount和partitionLength的数组长度必须一致;
以上分为三个分区:0-255、256-511、512-1023
示例说明:
id=515,515 & 1023 = 10 0000 0011 & 11 1111 1111 = 10 0000 0011 = 515,对应数组索引515上的元素值为2(512-1023索引范围元素值均为2)即为分片号。
字符串hash解析算法分片
截取字符串中的指定位置的子字符串进行hash运算和位运算算出分片。
schema.xml逻辑表分片规则配置:
<table name="tb_strhash" dataNode="dn4,dn5" rule="sharding-by-stringhash" /> |
schema.xml数据节点配置:
<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> |
partitionLength属性:hash求模基数。partitionLength * partitionCount = 1024(出于性能考虑)
partitionCount属性:分区数
hashSlice属性:hash运算位,格式start:end。如果end为0,表示截到末尾;为-1表示截到倒数第二位;大于0只代表数字自身;
示例说明:
world.hashSlice(0:2)-》wor-》hash运算得26629-》位运算26629 & 1023 = 5-》数组索引5的元素值为0-》落入分片号为0的分片
按天分片
按照日期及对应的时间周期来分片。
schema.xml逻辑表分片规则配置:
<table name="tb_datepart" dataNode="dn4,dn5,dn6" rule="sharding-by-date" /> |
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="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> |
dateFormat属性:日期格式
sBeginDate属性:开始日期
sEndDate属性:结束日期。如果配置了结束日期,表示数据到达了这个日期的分片后,会重复从开始分片插入
sPartionDay属性:分区天数。默认值10,从开始日期算起,每个10天一个分区
上面配置表示从开始时间开始,每10天为一个分片,到达结束时间之后,会重复开始分片插入。
sBeginDate、sEndDate、sPartionDay算出的分片数,必须和schema.xml中<table>的dataNode属性指定的节点列表数量一致,否则启动报错。
自然月分片
按照自然月及对应的时间周期来分片。
缺点:分布可能不均匀(如订单淡季旺季、双十二、618)
schema.xml逻辑表分片规则配置:
<table name="tb_monthpart" dataNode="dn4,dn5,dn6" rule="sharding-by-month" /> |
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="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> |
dateFormat属性:日期格式
sBeginDate属性:开始日期
sEndDate属性:结束日期。如果配置了结束日期,表示数据到达了这个日期的分片后,会重复从开始分片插入
从开始时间开始,一个月为一个分片,到达结束时间之后,会重复开始分片插入。
sBeginDate、sEndDate算出的分片数,必须和schema.xml中<table>的dataNode属性指定的节点列表数量一致,否则启动报错。
-
- 分库分表示例
水平分表示例
场景:
对tb_order表进行水平拆分,分片数为3
建库:
在3台数据库中创建数据库db01。
schema.xml配置(替换所有内容):
<?xml version="1.0"?> |
server.xml配置(替换掉<mycat:server>下的所有<user>)
<user name="root" defaultAccount="true"> | 配置说明: 该配置表示,定义了两个用户 root 和 user ,都可以访问 DB01 逻辑库,但是root用户可读可写,user用户只读。 <property name="schemas">:该用户能访问的逻辑库,多个逻辑库用逗号分割 <privileges check="true">:是否开启DML权限检查,默认false dml="0110":对应IUSD(增改查删)的权限 <property name="readOnly">:是否只读,默认false |
rule.xml配置(已有,无需修改):
<tableRule name="auto-sharding-long"> |
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万零1-1000万之间的数据存储在1号数据节点; 1000万零1-1500万的数据节点存储在2号节点; 超出1500万的值,报错!!! |
数据测试:
MyCat初始逻辑库表在/usr/local/mycat/conf/schema.xml中定义,真实库表需手动在MyCat中执行创建语句来创建。
默认分片规则auto-sharding-long按id分片(autopartition-long.txt):
如果id的值在1-500w之间,数据将会存储在第一个分片数据库中。
如果id的值在500w-1000w之间,数据将会存储在第二个分片数据库中。
如果id的值在1000w-1500w之间,数据将会存储在第三个分片数据库中。
如果id的值超出1500w,在插入数据时,将会报错。
垂直分库示例
场景
将商品相关表、订单相关表和用户相关表作为普通表拆分到不同节点主机。
省市区表属于数据字典表,在多个业务模块中都可能会遇到。由于MyCat关联查询不能跨节点(报错为invalid route in sql即无法路由到节点),所以将省市区表设置为全局表,在所有节点主机中都存放(全局表更新数据时会同步更新)。
建库:
在3台数据库中创建数据库shopping。
schema.xml配置(替换所有内容)
<?xml version="1.0"?> |
server.xml配置(替换掉<mycat:server>下的所有<user>)
<user name="root" defaultAccount="true"> |
测试
重启MyCat或重新加载配置文件后,在mycat的命令行中,通过source指令导入建表语句,以及对应的数据插入语句,查看数据分布情况是否与预期的一致。
MyCat原理
在MyCat中,当执行一条SQL语句时,MyCat需要进行SQL解析、分片分析、路由分析、读写分离分析等操作,最终经过一系列的分析决定将当前的SQL语句到底路由到那几个(或哪一个)节点数据库,数据库将数据执行完毕后,如果有返回的结果,则将结果返回给MyCat,最终还需要在MyCat中进行结果合并、聚合处理、排序处理、分页处理等操作,最终再将结果返回给客户端。
而在MyCat的使用过程中,MyCat官方也提供了一个管理监控平台MyCat-eye(MyCat-Web)。MyCat-eye 是 Mycat 可视化运维的管理和监控平台,弥补了 Mycat 在监控上的空白。帮 Mycat分担统计任务和配置管理任务。MyCat-eye 引入了 ZooKeeper 作为配置中心,可以管理多个节点。MyCat-eye 主要管理和监控 Mycat 的流量、连接、活动线程和内存等,具备 IP 白名单、邮件告警等模块,还可以统计 SQL 并分析慢 SQL 和高频 SQL 等。为优化 SQL 提供依据。
MyCat管理控制台
MyCat的两个端口:
Mycat默认开通2个端口,可以在server.xml中进行修改:
8066 数据访问端口,即进行 DML 和 DDL 操作。
9066 数据库管理端口,即 mycat 服务管理控制功能,用于管理mycat的整个集群状态
连接MyCat的管理控制台:
mysql -h 192.168.91.166 -P 9066 -u root -p123456
常用命令:
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-eye
介绍
Mycat-eye(Mycat-web)是对mycat-server提供监控服务,功能不局限于对mycat-server使用。他通过JDBC连接对Mycat、Mysql监控,监控远程服务器(目前仅限于linux系统)的CPU、内存、网络、磁盘。
安装启动
Mycat-eye运行过程中需要依赖zookeeper,因此需要先安装zookeeper。
安装启动zookeeper | 安装启动MyCat-eye |
解压zookeeper安装包: tar -zxvf zookeeper-3.4.6.tar.gz -C /usr/local/ 创建数据存放目录: cd /usr/local/zookeeper-3.4.6/ mkdir data 创建配置文件: cd conf cp zoo_sample.cfg zoo.cfg 修改配置文件: vi zoo.cfg 配置项dataDir值改为上面创建的data文件夹的路径: dataDir=/usr/local/zookeeper-3.4.6/data 启动zookeeper: cd .. bin/zkServer.sh start bin/zkServer.sh status | 解压: tar -zxvf Mycat-web.tar.gz -C /usr/local/ 启动mycat-web: cd /usr/local/mycat-web/ sh start.sh 注:如果Zookeeper与Mycat-web不在同一台服务器上,需要设置Zookeeper的地址,在/usr/local/mycat-web/mycat-web/WEB-INF/classes/mycat.properties文件中修改以下配置项: zookeepr=localhost:2181 访问主页: http://192.168.91.166:8082/mycat |
目录介绍
etc ----> jetty配置文件
lib ----> 依赖jar包
mycat-web ----> mycat-web项目
readme.txt
start.jar ----> 启动jar
start.sh ----> linux启动脚本
功能介绍
主要为SQL监控:SQL统计、各SQL表统计、SQL监控、高频SQL、慢SQL统计、SQL解析(解析SQL路由情况)
-
- 读写分离
MyCat支持MySQL、Oracle和SQL Server的读写分离。
一主一从
主从复制搭建
主机 角色 用户名 密码
192.168.91.166 master root 123456
192.168.91.167 slave root 123456
schema.xml配置
<?xml version="1.0"?> |
writeHost代表的是写操作对应的数据库,readHost代表的是读操作对应的数据库。所以我们要想实现读写分离,就得配置writeHost关联的是主库,readHost关联的是从库。
此外还需要配置负载均衡参数:schema.xml文件datahost标签的balance属性。取值有4种,具体含义如下:
0 (默认值)不开启读写分离机制,所有读操作都发送到当前可用的writeHost上
1 全部的readHost与备用的writeHost都参与select语句的负载均衡(主要针对于双主双从模式)
2 所有的读写操作都随机在writeHost , readHost上分发
3 所有的读请求随机分发到writeHost对应的readHost上执行, writeHost不负担读压力
所以,在一主一从模式的读写分离中,balance配置1或3都是可以完成读写分离的。
在读写分离模式下,可以不用配置<schema>下的<table>标签。
server.xml配置
配置root用户可以访问SHOPPING、ITCAST 以及 ITCAST_RW逻辑库。
<user name="root" defaultAccount="true"> |
双主双从读写分离
服务器配置
角色 IP地址 my.cnf中的 server-id 端口号
主机 0 MyCat服务器 172.16.208.210 8066
主机 1 Master1 172.16.208.211 1 3306
从机 1 Slave1 172.16.208.212 2 3306
主机 2 Master2 172.16.208.213 3 3306
从机 2 Slave2 172.16.208.214 4 3306
schema.xml配置
MyCat控制后台数据库的读写分离和负载均衡由schema.xml文件datahost标签的balance属性控制,通过writeType及switchType来完成失败自动切换的。
属性说明:
balance="1":全部的readHost与备用的writeHost都参与select语句的负载均衡(主要针对于双主双从模式)
writeType:0表示写操作都转发到writeHost1,writeHost1挂了则切换到writeHost2上;1表示写操作都随机发送到配置的writeHost上
switchType:-1表示失败不自动切换,1表示失败自动切换
<?xml version="1.0"?> |
server.xml配置
配置root用户可以访问ITCAST_RW2逻辑库。
<user name="root" defaultAccount="true"> |
测试
登录MyCat,测试查询及更新操作,判定是否能够进行读写分离,以及读写分离的策略是否正确。当主库挂掉一个之后,是否能够自动切换。