MySQL:MyCat中间件实现动态数据源、读写分离,分库分表

一、MyCat介绍

1.1、什么是MyCat

来自阿里的用于支持数据库读写分离、分表分库的分布式中间件。

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

1.2、MyCat原理

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

有点类似于Nginx反向代理,它可以隐藏数据库真实IP地址,可以实现读写分离、分表分库(注意:主从复制是MySQL自己实现的)

1.3、实际使用MyCat写SQL怎么执行的

注意:上图的select * 实际是不带条件的 ,如果带条件,分析如下:

(1)查询带分片字段,比如ID(效率高,只会查一个表):

如果过是select * from user_info where id=1,1%3=1,1在DB2,则MyCat只会发一条指令查一个DB2(可在Linux查看mycat.log查看日志信息)。

(2)查询非分片字段,假如三个表都有name=“小瑞”(效率低,可能会查多个表):

select * from user_info where name="小瑞"

(3)查询分页:MyCat会发送三条语句查3个表,查看日志就知道,而且返回结果随机的,可能有(3,6)、(1,4)、(2,5),性能不好

select * from user_info limit 0,2;

(4)如果分页加排序:则也是会查三个表,然后返回MyCat,看谁最小或者最大 返回两个给客户端,比如(1,2)、(4,5)

select * from user_info order by id desc limit 0,2 ;

1.4、什么是读写分离

读写分离就是把数据库的读和写操作分开,以对应不同的数据库服务。主数据库提供写操作,从数据库提供读操作。主数据进行写操作后,数据及时同步到所读的数据库,尽可能保证读、写数据库一致。

1.5、Linux环境安装MyCat实现读写分离

1、上传安装Mycat-server-1.6.5-release-20180122220033-linux.tar
2、解压安装包tar –zxvf  
3、配置schema.xml 和server.xml
4、客户端连接端口号: 8066

配置文件介绍:

文件

说明

server.xml

Mycat的配置文件,设置账号、参数等

schema.xml

Mycat对应的物理数据库和数据库表的配置

rule.xml

Mycat分片(分库分表)规则

5、进入bin目录 
  启动MyCat ./mycat start 
  停止MyCat ./mycat stop
6、查看/usr/local/mycat/logs wrapper.log日志 如果是为successfully 则启动成功

关闭防火墙:systemctl stop firewalld.service
只可读的账号      user  user  端口号8066
可读可写的账号    root  123456  端口号8066

二、SpringBoot项目整合动态数据源

动态数据源与多数数据源区别:

  • 多数据源---以分包形式
  • 动态数据在jvm进行不断地进行切换

2.1、配置多个数据源方式:

配置多个数据源,根据业务需求访问不同的额数据,指定对应的策略:增加、删除、修改操作访问对应数据,查询又访问另外数据,不同的数据库做好数据一致性处理。

2.2、动态切换数据源方式:

根据配置文件业务动态切换访问的数据库,此方案通过SpringAOP实现,使用AspactJ来实现动态织入,通过编程继承实现Spring中的AbstractRoutingDataSource,来实现数据库访问的动态切换。

原理:

(1)首先在项目中,有两个数据源,分别是读数据源和写数据源;

(2)使用AOP技术拦截业务逻辑层方法的前缀,如前缀为select、get、find等,判断方法是否做读或者写操作;

(3)如果写操作的时候,传递一个key给RoutingDataSource指明使用写的数据源。

使用AOP技术判断业务逻辑层的方法,判断方法的前缀是否为读或者写的操作。

(4)如果为写操作的时候,会给RountingDataSource传递key为updateDataSource

原理图:

步骤:

  • (1)创建读和写数据源
  • (2)读和写数据源注册到RoutingDataSource
  • (3)使用AOP技术拦截业务层方法,判断方法的前缀是否需要做读或者写

Maven依赖信息:

application.yml:

spring:
  datasource:
    ###可读数据源
    select:
      jdbc-url: jdbc:mysql://192.168.212.204:8066/TESTDB1
      driver-class-name: com.mysql.jdbc.Driver
      username: user
      password: user
    ####可写数据源  
    update:
      jdbc-url: jdbc:mysql://192.168.212.204:8066/TESTDB1
      driver-class-name: com.mysql.jdbc.Driver
      username: user
      password: user
    type: com.alibaba.druid.pool.DruidDataSource

 DataSourceContextHolder:

@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:

@Configuration
public class DataSourceConfig {

	// 创建可读数据源
	@Bean(name = "selectDataSource")
	@ConfigurationProperties(prefix = "spring.datasource.select") // application.properteis中对应属性的前缀
	public DataSource dataSource1() {
		return DataSourceBuilder.create().build();
	}

	// 创建可写数据源
	@Bean(name = "updateDataSource")
	@ConfigurationProperties(prefix = "spring.datasource.update") // application.properteis中对应属性的前缀
	public DataSource dataSource2() {
		return DataSourceBuilder.create().build();
	}

}

 DynamicDataSource:

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

@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:

@Aspect
@Component
@Lazy(false)
@Order(0) // Order设定AOP执行顺序 使之在数据库事务上先执行
public class SwitchDataSourceAOP {
	// 这里切到你的方法目录
	@Before("execution(* com.mayikt.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");
		}
	}
}

 

2.3、MyCat实现读写分离方式:

MyCat连接多个数据库,数据源只需要连接MyCat,对于开发人员而言他还是连接了一个数据库(实际上是mysql的mycat中间件),而且也不需要根据不同业务选择不同的库,这样不会有多余代码产生。

三、数据库分表分库策略

3.1、数据库集群会产生哪些问题

(1)自增ID问题;

问题:数据库集群如果自动增长ID产生重复,如何解决?

答:

  • a、可以通过UUID形式,但是UUID是无需的,不太适合用String类型做ID,性能不好,但是因为如果用Oracle要设置自动增长要搞个序列比较麻烦,所以oracle数据库环境String类的ID还是比较常见;
  • b、还可以设置数据库步长;
  • c、雪花算法;
  • d、redis。

(2)数据库关联查询问题(水平拆分产生);

(3)数据同步问题(可通过主从复制解决)。

3.2、分表分库原则:垂直拆分和水平拆分

(1)垂直拆分——最基本的,大公司基本都会用

什么是垂直拆分:

根据不同的业务,分为不同的数据库,比如会员数据库、订单数据库、支付数据库等。

优点:

拆分后业务清晰,拆分规则明确,系统之间整合或者扩展容易

缺点:

跨数据库查询只能通过接口形式通讯,比如订单服务和会员服务的数据交流需要编写接口出来给对方调用,工作量比较大。提高了系统复杂度,还会存在分布式事务问题

使用场景:大型互联网项目、电商项目、微服务项目

(2)水平拆分——分表、分库

什么是水平拆分:把同一个表拆分到不同的数据库中。

水平拆分不是将表的数据做分类,而是按照某个字段的某种规则来分散到多个库中,每个表中包含一部分数据。主要是按照数据的行切分,把表的某些行切分到一个数据库,而另外某些行又切分到其他数据库。主要有分表和分库两种模式

优点:

提高了系统稳定性、负载能力

缺点:

跨库关联性能较差较复杂

(3)垂直拆分和水平拆分区别垂直拆分是把不同的表发放在不同数据库;水平拆分是把同一个表拆到不同的数据库,或者把一张表数据拆分到n多个小表。

3.3、使用阿里MyCat实现水平分片策略

(1) MyCat支持10种分片策略

  • 1、求模算法
  • 2、分片枚举
  • 3、范围约定
  • 4、日期指定
  • 5、固定分片hash算法
  • 6、通配取
  • 7、ASCII码求模通
  • 8、编程指定
  • 9、字符串拆分hash解析

详细文档介绍可访问:http://www.mycat.io/document/mycat-definitive-guide.pdf

(2)原理分析(取模算法)

a、MyCat中的路由结果是通过 分片字段和分片方法 来确定的,如果查询条件中有id字段的情况还好,查询将会落到某个具体的分片。

b、如果查询没有分片的字段,会向所有的db都会查询一遍,然后封装结果集给客户端。

(3)环境搭建

a、先定义枚举(比如地区),每个地区指定数据库存放位置;

b、

(2)地区分片环境搭建

各种配置文件百度即可;

分片案例:比如某些业务需要按省份或者区县来保存,而全国的省份区县固定的。配置如下:

案例步骤:
1、创建数据库userdb_1 、 userdb_2、userdb_3 
2、
wuhan=0
shanghai=1
suzhou=2
上面columns 标识是将要分片的表字段,algorithm 分片函数,
其中分片函数配置中,mapFile标识配置文件名称,type默认值为0,0表示Integer,非零表示String,
所有的节点配置都是从0开始,及0代表节点1
3、
defaultNode 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点,结点为指定的值
默认节点的作用:
枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点
                如果不配置默认节点(defaultNode值小于0表示不配置默认节点),碰到
                不识别的枚举值就会报错,
                like this:can't find datanode for sharding column:column_name val:ffffffff 

4、范围约定 此分片适用于提前规划好分片字段某个范围属于哪个分片
5、求摸法 : 根据id进行十进制求摸运算,运算结果为分区索引
注意:数据库节点分片数量无法更改。

 rule.xml 文件新增

<tableRule name="role1">
        <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>

schema.xml
<table name="user_info" dataNode="dn1,dn2,dn3" rule="role1"/>

数据库表结构:

CREATE TABLE `user_info` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

6、日期列分区法 按日期(天)分片

关闭防火墙

systemctl stop firewalld.service
yum -y install net-tools
netstat -tunlp | grep 1984
kill -9  2408

CREATE TABLE `user_info` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
发布了52 篇原创文章 · 获赞 116 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/RuiKe1400360107/article/details/103971064