CentOS7:mysql5.7的安装&主从&读写分离

说明:本文是通过引用 + 原创的形式来介绍CentOS7的mysql5.7的安装&主从&读写分离

1.mysql5.7的 安装

下面的文章介绍了如果已经安装了MySQL该如何卸载,以及一些可以直接复制的常规操作(懒得记),本人已在阿里云上测试通过了

https://blog.csdn.net/z13615480737/article/details/78906598

2.mysql5.7的主从

我是直接在阿里云上开了两台ECS完成的,如果你是想在一台虚拟机上开多个mysql实例,那你可能要换篇文章看看了

下文是来自简书文章,简单易懂,并且也已经通过测试

https://www.jianshu.com/p/24c1995e9bd8

3.下面是MYSQL的读写分离:

首先为什么要去做读写分离?

原因是:

应用就算做了前端和后端的分离,但是要求可是一点都没有减少

就算有缓存帮着挡一下,但是也档不了写的操作啊!

如果有海量的写操作和读操作过来,单单一台MySQL服务器是挡不住的

所以此时就有了读写分离的需求
在这里插入图片描述

读写分离是什么?

最简单的解释就是将写操作专门放到一台机器上,然后将读操作放到另一台机器上

在这里插入图片描述

如果要读取数据,那么去找数据库B

如果要写数据,那么去找数据库A,

但是一旦在数据库A上写了数据,那么就得立即去通知数据库B,让B数据库做对应的更改

读写分离的缺陷

读写分离的缺陷,我想大家也应该知道了,就是很容易读取到脏数据,所以读写分离的从数据库,一般都是接受一些对于脏数据容忍度比较高的数据的读取

这里提到了脏数据容忍度的问题

那么哪些数据是脏数据容忍度比较高的数据呢?

比如:IP登录记录,首页新闻等这些数据就算读到脏数据也没有关系,所以就可以从 从数据库 中读取数据,而不用从 主数据库 中读取数据了

其一,读写分离的原则

如果我们确定要去做读写分离的操作了,那么首先要明白的一个原则是:

一个Service方法只能对应一个数据库

坚决不能出现,一个Service方法既对应主数据库,又对应从数据库的情况出现

其二,在既有写又有读的情况下,数据库选择主数据库

	public class IplogServiceImpl implements IIplogService{ 

		PageResult query(IpLogQueryObject qo){

			int count = mapper.queryForCount(qo);

			List list = mapper.query(qo);
		}

		void add(IpLog ll){

			mapper.insert(ll);
		}		

	}

比如query的方法只是读就可以对应 从数据库

insert方法涉及到写就可以对应 主数据库

但是query中假如加了个写的方法,那么此时该对应哪个数据库?

答案是:主数据库,很简单,你又查又写,那当然是选择主数据库了!

假如在 从数据库上 又查又写,那么从数据库上数据就改变了!

你想只有主数据库通知从数据库的,哪里有从数据库通知主数据库的?

这样就是双向更新了,我们的读写分离,原则上是单向更新的

简单的来说就是:只准 主数据库 放火,不准 从数据库 点灯

其三,读写分离的核心原理

现在的问题就是我要如何通知Service方法,更换数据库呢?

关键就是DataSource和SqlSessionFactory

想一下,我们平时在Spring中写JDBC四要素的时候是在哪里写的?

当然是在DataSource中定义四要素的!

所以我们接下来的思路可以变成这样:
在这里插入图片描述

在这里插入图片描述

等于说,此处我们需要一个类似于路由器的东西,我们可以给它一个参数,让它知道应该访问哪个数据库,然后由它来实现数据库的切换

其四,AbstractRoutingDataSource

其原理这边文章已经有具体的说明:https://blog.csdn.net/x2145637/article/details/52461198

这里我只做Spring下的最简单的演示,并不涉及AOP切换数据库

首先是创建一个类去继承AbstractRoutingDataSource

下面的CurrentDataSourceContext是一个LocalThread类型的实例,用于绑定现在的线程

package cn.csdn.rw_sep.util;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


public class RWRoutingDataSource extends AbstractRoutingDataSource {
    /**
        这个方法需要访问当前的目标的DS的名字
     */
    @Override
    protected Object determineCurrentLookupKey() {

        return CurrentDataSourceContext.get();
    }
}

数据库切换的关键:CurrentDataSourceContext

简单的来说就是当前线程告诉CurrentDataSourceContext我要用什么样的数据库:

比如slaveDs,比如masterDs等,意思是线程决定数据库的类型,线程要做的仅仅是

传入数据库的名字即可

public class CurrentDataSourceContext {

    private static ThreadLocal<String> dataSourcePool = new ThreadLocal<>();

    public static void set(String dsName){
        dataSourcePool.set(dsName);
    }

    public static String get(){
        return dataSourcePool.get();
    }
}

接下来只要在Controller中说明数据库即可

但是这个只是基本配置RWRoutingDataSource还必须交给Spring管理,并且还需要获取Spring管理的DateSource供它进行切换,并且还需要替换Spring的SqlSession的DataSource,换成它,只有这样做才能实现动态切换数据库

所以下面的Spring的具体的配置文件:

	<!-- 连接主dataSource -->
	<bean id="masterDs" class="com.alibaba.druid.pool.DruidDataSource"
		  init-method="init" destroy-method="close">
		<property name="driverClassName" value="${master.driverClassName}" />
		<property name="username" value="${master.username}" />
		<property name="url" value="${master.url}" />
		<property name="password" value="${master.password}" />
	</bean>

	<!-- 连接从dataSource -->
	<bean id="slaveDs" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="driverClassName" value="${slave.driverClassName}" />
		<property name="username" value="${slave.username}" />
		<property name="url" value="${slave.url}" />
		<property name="password" value="${slave.password}" />
	</bean>


	<!--配置SqlSessionFactory所需要使用的DataSource-->
	<bean id="dataSource" class="cn.csdn.rw_sep.util.RWRoutingDataSource">
		<!--配置路由器DS的目标DS-->
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<entry key="masterDs" value-ref="masterDs"></entry>
				<entry key="slaveDs" value-ref="slaveDs"></entry>
			</map>
		</property>

		<!--配置默认的目标DS-->
		<property name="defaultTargetDataSource" ref="masterDs"/>
	</bean>


	<!-- 配置SessionFactory -->
	<bean id="sessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 1:连接池 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 2:读取MyBatis总配置文件 -->
		<property name="configLocation" value="classpath:mybatis.xml"/>
		<!-- 3:配置别名扫描 -->
		<property name="typeAliasesPackage" value="cn.csdn.rw_sep.domain"/>
		<!-- 4:加载mapper文件 -->
		<property name="mapperLocations" value="classpath:cn/csdn/rw_sep/mapper/UserMapper.xml"/>
	</bean>

后面是具体的配置文件:

master.driverClassName=com.mysql.jdbc.Driver
master.url=jdbc:mysql://xx.xx.xx.xx:3306/主数据库名?characterEncoding=utf8&useSSL=false
master.username=root
master.password=xx

slave.driverClassName=com.mysql.jdbc.Driver
slave.url=jdbc:mysql://xx.xx.xx.xx:3306/从数据库名?characterEncoding=utf8&useSSL=false
slave.username=root
slave.password=xx

接下就是新建一个Controller做测试了

import cn.csdn.rw_sep.domain.User;
import cn.csdn.rw_sep.service.IUserService;
import cn.csdn.rw_sep.util.ConstProperties;
import cn.csdn.rw_sep.util.CurrentDataSourceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {

    @Autowired
    private IUserService userService;

    @RequestMapping("/testSelect")
    public String testSelect(Long id){

        CurrentDataSourceContext.set(ConstProperties.SLAVE_DS);

        User user = userService.selectById(id);

        System.out.println(user);

        return "select";
    }

    @RequestMapping("/testInsert")
    public String testInsert(User user){

        CurrentDataSourceContext.set(ConstProperties.MASTER_DS);

        userService.insert(user);

        return "insert";
    }
}

在上面你很明显看到了ConstProperties.MASTER_DS这个东西,

这个是为了便于管理而抽取出来的常量,由于这个在项目中确实到处都要用

一旦修改就是大工程,故将其抽取

public class ConstProperties {

    private ConstProperties(){}

    public static final String MASTER_DS = "masterDs";
    public static final String SLAVE_DS = "slaveDs";
}

启动Tomcat,开始测试:

首先是读测试:首先修改从数据库数据,再观察究竟是从哪个数据库读的数据

在这里插入图片描述

测试结果:

在这里插入图片描述

接下来是写测试:

在这里插入图片描述

至此读写测试完成

下面总结一下读写测试常犯的一些错误:

注意:需要在Controller层去设置要访问的数据库名,而不是在Service层去设置

不然会不生效

猜你喜欢

转载自blog.csdn.net/fenghuoliuxing990124/article/details/84951598