说明:本文是通过引用 + 原创的形式来介绍CentOS7的mysql5.7的安装&主从&读写分离
1.mysql5.7的 安装
下面的文章介绍了如果已经安装了MySQL该如何卸载,以及一些可以直接复制的常规操作(懒得记),本人已在阿里云上测试通过了
2.mysql5.7的主从
我是直接在阿里云上开了两台ECS完成的,如果你是想在一台虚拟机上开多个mysql实例,那你可能要换篇文章看看了
下文是来自简书文章,简单易懂,并且也已经通过测试
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层去设置
不然会不生效