CentOS7:mysql5.7的安裝&主從&讀寫分離
說明:本文是通過引用 + 原創的形式來介紹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層去設定
不然會不生效