1 什么是 MyCat
MyCAT
是一款由阿里 Cobar 演变而来的用于支持数据库,读写分离
、分表分库
的分布式中间件。
MyCAT
支持 Oracle
、MSSQL
、MYSQL
、PG
、DB2
关系型数据库,同时也支持 MongoDB
等非关系型数据库。
MyCAT
原理 MyCAT
主要是通过对 SQL
的拦截,然后经过一定规则的 分片解析
、路由分析
、读写分离分析
、缓存分析
等,然后将 SQL
发给后端真实的数据块,并将返回的结果做适当处理返回给客户端。
官方网站:http://www.mycat.io/
2 MyCat 实现读写分离原理
读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。
主数据库进行写操作后,数据及时同步到所读的数据库,尽可能保证读、写数据库的数据一致,比如 MySQL
的 主从复制
、Oracle
的 data guard
、SQL Server
的 复制订阅
等。
3 Linux
环境安装 MyCat
实现读写分离
需要先安装
JDK
1、下载
MyCat
cd /usr/local/
wget http://dl.mycat.io/1.6.5/Mycat-server-1.6.5-release-20180122220033-linux.tar.gz
2、解压
tar -zxvf Mycat-server-1.6.5-release-20180122220033-linux.tar.gz
3、启动
/usr/local/mycat/bin/mycat start
查看日志:
cat /usr/local/mycat/logs/wrapper.log
/usr/local/mycat/bin/mycat stop
4、修改配置文件
文件 | 说明 |
---|---|
server.xml |
MyCAT 的配置文件,设置账号、参数等 |
schema.xml |
MyCAT 对应的物理数据库和数据库表的配置 |
rule.xml |
MyCAT 分片(分库分表)规则 |
schema.xml
:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- TESTDB1 是mycat的逻辑库名称,链接需要用的 -->
<schema name="test" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"></schema>
<!-- database 是MySQL数据库的库名 -->
<dataNode name="dn1" dataHost="localhost1" database="test" />
<!--
dataNode节点中各属性说明:
name:指定逻辑数据节点名称;
dataHost:指定逻辑数据节点物理主机节点名称;
database:指定物理主机节点上。如果一个节点上有多个库,可使用表达式db$0-99, 表示指定0-99这100个数据库;
dataHost 节点中各属性说明:
name:物理主机节点名称;
maxCon:指定物理主机服务最大支持1000个连接;
minCon:指定物理主机服务最小保持10个连接;
writeType:指定写入类型;
0,只在writeHost节点写入;
1,在所有节点都写入。慎重开启,多节点写入顺序为默认写入根据配置顺序,第一个挂掉切换另一个;
dbType:指定数据库类型;
dbDriver:指定数据库驱动;
balance:指定物理主机服务的负载模式。
0,不开启读写分离机制;
1,全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡;
2,所有的readHost与writeHost都参与select语句的负载均衡,也就是说,当系统的写操作压力不大的情况下,所有主机都可以承担负载均衡;
-->
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- 可以配置多个主从 -->
<writeHost host="hostM1" url="192.168.153.101:3306" user="root" password="123456">
<!-- 可以配置多个从库 -->
<readHost host="hostS2" url="192.168.153.102:3306" user="root" password="123456" />
</writeHost>
</dataHost>
</mycat:schema>
server.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<!-- 读写都可用的用户 -->
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">test</property>
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<schema name="TESTDB" dml="0110" >
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges>
-->
</user>
<!-- 只读用户 -->
<user name="user">
<property name="password">user</property>
<property name="schemas">test</property>
<property name="readOnly">true</property>
</user>
</mycat:server>
5、启动
关闭防火墙:
systemctl stop firewalld
systemctl disable firewalld
/usr/local/mycat/bin/mycat start
查看日志:
cat /usr/local/mycat/logs/wrapper.log
测试
主表创建表和添加记录,从表自动同步数据
4 SpringBoot
项目整合动态数据源(读写分离)
- 配置多个数据源,根据业务需求访问不同的数据,指定对应的策略:增加,删除,修改操作访问对应数据,查询访问对应数据,不同数据库做好的数据一致性的处理。由于此方法相对易懂,简单,不做过多介绍。
- 动态切换数据源,根据配置的文件,业务动态切换访问的数据库:此方案通过
Spring
的AOP
,AspactJ
来实现动态织入,通过编程继承实现Spring
中的AbstractRoutingDataSource
,来实现数据库访问的动态切换,不仅可以方便扩展,不影响现有程序,而且对于此功能的增删也比较容易。 - 通过
mycat
来实现读写分离:使用mycat
提供的读写分离功能,mycat
连接多个数据库,数据源只需要连接mycat
,对于开发人员而言他还是连接了一个数据库(实际是mysql
的mycat
中间件),而且也不需要根据不同业务来选择不同的库,这样就不会有多余的代码产生。
4.1 动态数据源核心配置
在 Spring 2.0.1
中引入了 AbstractRoutingDataSource
,该类充当了 DataSource
的路由中介,能在运行时,根据某种 key
值来动态切换到真正的 DataSource
上。
- 项目中需要集成多个数据源分别为读和写的数据源,绑定不同的
key
。 - 采用
AOP
技术进行 拦截业务逻辑层方法,判断方法的前缀是否需要写或者读的操作。 - 如果方法的前缀是写的操作的时候,直接切换为写的数据源,反之切换为读的数据源
也可以自己定义注解进行封装。
4.2 动态数据源与多数据源区别
多数据源
:以分包形式动态数据源
:在JVM
中不断地进行切换
4.3 实例
Ⅰ 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.23</version>
</dependency>
Ⅱ application.yml
spring:
datasource:
###可读数据源
select:
jdbc-url: jdbc:mysql://192.168.153.103:8066/test
driver-class-name: com.mysql.jdbc.Driver
username: user
password: user
####可写数据源
update:
jdbc-url: jdbc:mysql://192.168.153.103:8066/test
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
Ⅲ DataSourceContextHolder
package com.snow.mysql.config;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* @author yang-windows
* @Title: snow-parent
* @Package com.snow.mysql.config
* @Description: 本地数据源
* @date 2020/3/29 20:23
*/
@Component
@Lazy(false)
public class DataSourceContextHolder {
// 采用ThreadLocal 保存本地多数据源
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源类型
public static void setDbType(String dbType) {
contextHolder.set(dbType);
}
public static String getDbType() {
return contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
Ⅳ DataSourceConfig
package com.snow.mysql.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* @author yang-windows
* @Title: snow-parent
* @Package com.snow.mysql.config
* @Description: 创建数据源配置文件
* @date 2020/3/29 20:22
*/
@Configuration
public class DataSourceConfig {
// 创建可读数据源
@Bean(name = "selectDataSource")
@ConfigurationProperties(prefix = "spring.datasource.select") // application.yml中对应属性的前缀
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
// 创建可写数据源
@Bean(name = "updateDataSource")
@ConfigurationProperties(prefix = "spring.datasource.update") // application.yml中对应属性的前缀
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
}
Ⅴ DynamicDataSource
package com.snow.mysql.config;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
/**
* @author yang-windows
* @Title: snow-parent
* @Package com.snow.mysql.config
* @Description: 配置数据源信息
* 在Spring 2.0.1中引入了AbstractRoutingDataSource, 该类充当了DataSource的路由中介,
* 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。
* @date 2020/3/29 20:23
*/
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
@Autowired
@Qualifier("selectDataSource")
private DataSource selectDataSource;
@Autowired
@Qualifier("updateDataSource")
private DataSource updateDataSource;
/**
* 这个是主要的方法,返回的是生效的数据源名称
*/
@Override
protected Object determineCurrentLookupKey() {
System.out.println("DataSourceContextHolder:::" + DataSourceContextHolder.getDbType());
return DataSourceContextHolder.getDbType();
}
/**
* 配置数据源信息
*/
@Override
public void afterPropertiesSet() {
Map<Object, Object> map = new HashMap<>();
map.put("selectDataSource", selectDataSource);
map.put("updateDataSource", updateDataSource);
setTargetDataSources(map);
setDefaultTargetDataSource(updateDataSource);
super.afterPropertiesSet();
}
}
Ⅶ SwitchDataSourceAOP
package com.snow.mysql.aop;
import com.snow.mysql.config.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author yang-windows
* @Title: snow-parent
* @Package com.snow.mysql.aop
* @Description: 切换数据源的AOP
* @date 2020/3/29 20:24
*/
@Aspect
@Component
@Lazy(false)
@Order(0) // Order设定AOP执行顺序 使之在数据库事务上先执行
public class SwitchDataSourceAOP {
// 这里切到你的方法目录
@Before("execution(* com.snow.mysql.service.*.*(..))")
public void process(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
if (methodName.startsWith("get") || methodName.startsWith("count") || methodName.startsWith("find")
|| methodName.startsWith("list") || methodName.startsWith("select") || methodName.startsWith("check")) {
// 读
DataSourceContextHolder.setDbType("selectDataSource");
} else {
// 切换dataSource
DataSourceContextHolder.setDbType("updateDataSource");
}
}
}
测试
分别触发:http://127.0.0.1/app-snow-mysql/user/findUser
和 http://127.0.0.1/app-snow-mysql/user/insertUser?name=lisi
接口,控制台打印:
DataSourceContextHolder:::updateDataSource
2020-03-29 21:29:07.326 INFO 18156 --- [nio-8400-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-03-29 21:29:07.506 INFO 18156 --- [nio-8400-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
i === 1
DataSourceContextHolder:::selectDataSource
2020-03-29 21:29:18.004 INFO 18156 --- [nio-8400-exec-3] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting...
2020-03-29 21:29:18.015 INFO 18156 --- [nio-8400-exec-3] com.zaxxer.hikari.HikariDataSource