1 -【 MySQL 集群 】- 3 MyCat 实现读写分离与动态数据源切换

1 什么是 MyCat

MyCAT 是一款由阿里 Cobar 演变而来的用于支持数据库,读写分离分表分库 的分布式中间件。

MyCAT 支持 OracleMSSQLMYSQLPGDB2 关系型数据库,同时也支持 MongoDB 等非关系型数据库。

MyCAT 原理 MyCAT 主要是通过对 SQL 的拦截,然后经过一定规则的 分片解析路由分析读写分离分析缓存分析 等,然后将 SQL 发给后端真实的数据块,并将返回的结果做适当处理返回给客户端。

官方网站:http://www.mycat.io/

2 MyCat 实现读写分离原理

读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。

主数据库进行写操作后,数据及时同步到所读的数据库,尽可能保证读、写数据库的数据一致,比如 MySQL主从复制Oracledata guardSQL 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 项目整合动态数据源(读写分离)

  1. 配置多个数据源,根据业务需求访问不同的数据,指定对应的策略:增加,删除,修改操作访问对应数据,查询访问对应数据,不同数据库做好的数据一致性的处理。由于此方法相对易懂,简单,不做过多介绍。
  2. 动态切换数据源,根据配置的文件,业务动态切换访问的数据库:此方案通过 SpringAOPAspactJ 来实现动态织入,通过编程继承实现 Spring 中的 AbstractRoutingDataSource,来实现数据库访问的动态切换,不仅可以方便扩展,不影响现有程序,而且对于此功能的增删也比较容易。
  3. 通过 mycat 来实现读写分离:使用 mycat 提供的读写分离功能,mycat 连接多个数据库,数据源只需要连接 mycat,对于开发人员而言他还是连接了一个数据库(实际是 mysqlmycat 中间件),而且也不需要根据不同业务来选择不同的库,这样就不会有多余的代码产生。

4.1 动态数据源核心配置

Spring 2.0.1 中引入了 AbstractRoutingDataSource,该类充当了 DataSource 的路由中介,能在运行时,根据某种 key 值来动态切换到真正的 DataSource 上。

  1. 项目中需要集成多个数据源分别为读和写的数据源,绑定不同的 key
  2. 采用 AOP 技术进行 拦截业务逻辑层方法,判断方法的前缀是否需要写或者读的操作
  3. 如果方法的前缀是写的操作的时候,直接切换为写的数据源,反之切换为读的数据源
    也可以自己定义注解进行封装。

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/findUserhttp://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
发布了687 篇原创文章 · 获赞 229 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/weixin_42112635/article/details/105123393