(1)理论基础
Mycat是数据库中间件,所谓中间件,是一类连接软件组件和应用的计算机软件,以便软件各部件之间的通信。
例如 tomcat,web的中间件。而数据库中间件是连接Java应用程序和数据库中间的软件。
为什么要用Mycat
Java与数据库的紧耦合
我们现在普遍的Java应用程序都是直接连接了MySQL软件进行读写操作,也就是我们在Java中的配置文件等定义了mysql的数据源,直接连接到了我们的mysql软件,但是当某些情况下我们可能需要用到了多个数据库,这个时候我们可能就需要配多个数据源去连接我们的多个数据库,这个时候我们进行sql操作的时候就会很麻烦,因为Java与数据库有了一个紧密的耦合度,但是如果我们在Java应用程序与mysql中间使用了mycat,我们只需要访问mycat就可以了,至于数据源等问题,mycat会直接帮我们搞定。
高访问量高并发对数据库的压力
再来说一下高访问量高并发,我们都知道mysql数据库实际上在数据查询上是有一个瓶颈的,当我们的数据太多的时候,已经互联网上有高并发的请求的时候,这个时候对我们mysql的压力是非常大的,当访问量一大,就可能会出现查不出数据,响应的时间太长等,这个时候我们可能需要有多个服务器对数据库进行读写分离,以及对数据库进行集群,这个时候我们的sql语句要进行分类,哪个sql语句要访问哪个数据库,这个时候只要交给mycat就可以了。
读写请求数据不一致
最后说一下,使用多个数据库的时候我们就会遇到一个读写数据不一致的问题,这个时候同样mycat可以进行主从复制,保证了数据的一致性。
mycat能干什么
1、读写分离
2、数据分片
3、多数据源整合
Mycat原理
Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
这种方式把数据库的分布式从代码中解耦出来,程序员察觉不出来后台使用 Mycat 还是MySQL。
基本概念:
物理数据库:真实的数据库
物理表:真实的表
逻辑数据库:相对于物理数据库,是数据节点聚合后的结果
逻辑表:相对于物理表,是分片表聚合后的结果,对于客户端来说跟真实的表没有区别
(2)schema.xml配置
注意:schema的name必须和mysql数据库名字一样一样!
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="db01" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"></schema>
<dataNode name="dn1" dataHost="localhost1" database="db01" />
<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.31.215:3306" user="root" password="root">
<!-- 可以配置多个从库 -->
<readHost host="hostS2" url="192.168.31.83:3306" user="root" password="root" />
</writeHost>
</dataHost>
</mycat:schema>
(3)server.xml配置
<property name="serverPort">8066</property> <property name="managerPort">9066</property>
<!-- 读写都可用的用户 -->
<user name="root" defaultAccount="true">
<property name="password">root</property>
<property name="schemas">db01</property>
</user>
<!-- 只读用户 -->
<user name="user">
<property name="password">user</property>
<property name="schemas">db01</property>
<property name="readOnly">true</property>
<property name="defaultSchema">TESTDB</property>
</user>
(4)运行
启动Mycat服务器:startup_nowrap.bat
读写账号
ip:192.168.31.215
port:8066
账号:root
密码:root
只读账号
ip:192.168.31.215
port:8066
账号:user
密码:user
(5)配置
<dependency>
<groupId>io.mycat</groupId>
<artifactId>mycat-dao</artifactId>
<version>0.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
spring:
datasource:
####写数据源
update:
jdbc-url: jdbc:mysql://192.168.31.215:8066/db01?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
###读数据源
select:
jdbc-url: jdbc:mysql://192.168.31.215:8066/db01?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: user
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
(6)切面
@Aspect
@Component
@Lazy(false)
// Order设定AOP执行顺序 使之在数据库事务上先执行
@Order(0)
public class DataSourceAOP {
//横切点
@Before("execution(* com.zhaoyang.controller.*.*(..))")
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")) {
System.out.println("使用的是读数据源");
DataSourceContextHolder.setDbType("selectDataSource");
} else {
System.out.println("使用的是写数据源");
DataSourceContextHolder.setDbType("updateDataSource");
}
}
}
/**
* 配置读写数据源
*/
@Configuration
public class DataSourceConfig {
@Bean(name = "selectDataSource")
@ConfigurationProperties(prefix = "spring.datasource.select")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "updateDataSource")
@ConfigurationProperties(prefix = "spring.datasource.update")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
}
/**
* 保存本地多数据源
*/
@Component
@Lazy(false)
public class DataSourceContextHolder {
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();
}
}
/**
* 该类继承自 AbstractRoutingDataSource 类,在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key
*/
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
@Autowired
@Qualifier("selectDataSource")
private DataSource selectDataSource;
@Autowired
@Qualifier("updateDataSource")
private DataSource updateDataSource;
/**
* 返回生效的数据源名称
*/
@Override
protected Object determineCurrentLookupKey() {
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();
}
}
(7)使用
@RestController
public class PersonController {
@Autowired
PersonService personService;
@RequestMapping("/getPerson")
public Person getPerson(int id) {
return personService.getById(id);
}
@RequestMapping("/findPerson")
public Person findPerson(int id) {
return personService.getById(id);
}
@RequestMapping("/save")
public boolean save(Person person) {
return personService.save(person);
}
}