Mysql讀寫分離

主庫負責寫,從庫負責讀 

 

實現步驟:

1.數據庫層面的主從配置實現java

2.代碼層面的讀寫分離實現(無需改動現有代碼)mysql

 

主從同步如何工做:

主服務器(Master)將對數據的操做串行記錄到二進制日誌當中(binary log),寫入完畢後主服務器通知數據存儲引擎提交事務,提交好了以後進入第第二步;spring

這裏須要補充,咱們隊數據的操做稱之爲一次二進制的日誌事件,binary log將日誌拷貝到中繼日誌中(relay log)。收線,slave先開啓一個工做線程(IO Thread),IO縣城在master上打開一個鏈接,而後將binary log拷貝到 IO thread中,若是已經讀取到binary log的日誌,就會睡眠並等待binary log產生新的事件,IO縣城將這些事件寫入到relay log中,第二步完成。sql

第三步,slave重作relay log,也就是好比主服務器更新數據,slave尚未,那麼就更新,SQL Thread就是幹這個事情。數據庫

總結:主服務器添加了數據A,操做被記錄到binary log中,從服務器將添加A的操做同步到relay log中,SQL thread將同步的操做執行:向從服務器中插入數據Aapache

 

數據庫配置

主服務器:

    1.打開二進制日誌vim

     vim /etc/my.cnf服務器

    

    server-id=1session

    服務器ID,用於標識
    log-bin=master-bin    打開二進制日誌
    log-bin-index=master-bin.index    打開二進制日誌索引mybatis

    2.重啓數據庫,進入數據庫

    show master status;

    

    能夠看到,裏面有個file,使咱們剛纔指定好的master-bin.index的,Position表示開始的位置

    3.同時須要新建一個用戶(或者不用也能夠?由於從服務器須要指定一個用戶)

        create user repl;

        受權

        GRANT REPLICATION SLAVE ON *.* TO 'repl'@'從服務器IP地址' IDENTIFIED BY 'mysql';

        刷新

        flush privileges;

從服務器

1.打開二進制日誌

     vim /etc/my.cnf

2.打開relay log

3.關聯主服務器

   change master to master_host='主服務器IP地址',master_port=3306,master_user='repl',master_password='mysql',master_log_file='master-bin.000001',master_log_pos=0;

4.開啓主從跟蹤

    start slave;

5.查看從服務器狀態

    show slave status \G;

    

    發現報錯:說主從服務器 server id 相同。

    解決:

        stop slave;

        exit;

        vim /etc/my.cnf

        發現確實id變了。

代碼層面

    代碼寫完之後,在src/main/resource裏面的mybatis-config.xml裏面

    

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!-- 配置全局屬性 -->
	<settings>
		<!-- 使用jdbc的getGeneratedKeys獲取數據庫自增主鍵值 -->
		<setting name="useGeneratedKeys" value="true" />

		<!-- 使用列別名替換列名 默認:true -->
		<setting name="useColumnLabel" value="true" />

		<!-- 開啓駝峯命名轉換:Table{create_time} -> Entity{createTime} -->
		<setting name="mapUnderscoreToCamelCase" value="true" />
		<!-- 打印查詢語句 -->
		<setting name="logImpl" value="STDOUT_LOGGING" />
	</settings>
	
	<!-- 讀寫分離 -->
	<plugins>
		<plugin interceptor="com.wby.o2o.dao.split.DynamicDataSourceInterceptor">
		</plugin>
	</plugins>
</configuration>

而後添加日誌代碼,在src/main/resource/spring中的spring-dao.xml

    原文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.3.xsd">
	<!-- 配置整合mybatis過程 -->
	<!-- 1.配置數據庫相關參數properties的屬性:${url} -->
	<context:property-placeholder location="classpath:jdbc.properties"/>
	
	<!-- 2.數據庫鏈接池 -->
	<bean id="dataSource"  class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 配置鏈接池屬性 -->
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />

		<!-- c3p0鏈接池的私有屬性 -->
		<property name="maxPoolSize" value="30" />
		<property name="minPoolSize" value="10" />
		<!-- 關閉鏈接後不自動commit -->
		<property name="autoCommitOnClose" value="false" />
		<!-- 獲取鏈接超時時間 -->
		<property name="checkoutTimeout" value="10000" />
		<!-- 當獲取鏈接失敗重試次數 -->
		<property name="acquireRetryAttempts" value="2" />


	<!-- 3.配置SqlSessionFactory對象 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 注入數據庫鏈接池 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
		<property name="configLocation" value="classpath:mybatis-config.xml" />
		<!-- 掃描entity包 使用別名 -->
		<property name="typeAliasesPackage" value="com.wby.o2o.entity" />
		<!-- 掃描sql配置文件:mapper須要的xml文件 -->
		<property name="mapperLocations" value="classpath:mapper/*.xml" />
	</bean>

	<!-- 4.配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中 -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 注入sqlSessionFactory -->
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
		<!-- 給出須要掃描Dao接口包 -->
		<property name="basePackage" value="com.wby.o2o.dao" />
	</bean>
</beans>

新配置的文件:主要在鏈接池這塊

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.3.xsd">
	<!-- 配置整合mybatis過程 -->
	<!-- 1.配置數據庫相關參數properties的屬性:${url} -->
	<context:property-placeholder location="classpath:jdbc.properties"/>
	
	<!-- 2.數據庫鏈接池 -->
	<bean id="abstractDataSource"  abstract="true" destroy-method="close"
	class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- c3p0鏈接池的私有屬性 -->
		<property name="maxPoolSize" value="30" />
		<property name="minPoolSize" value="10" />
		<!-- 關閉鏈接後不自動commit -->
		<property name="autoCommitOnClose" value="false" />
		<!-- 獲取鏈接超時時間 -->
		<property name="checkoutTimeout" value="10000" />
		<!-- 當獲取鏈接失敗重試次數 -->
		<property name="acquireRetryAttempts" value="2" />
	</bean>	
	<bean id="master" parent="abstractDataSource">
		<!-- 配置鏈接池屬性 -->
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.mater.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>
	<bean id="slave" parent="abstractDataSource">
		<!-- 配置鏈接池屬性 -->
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.slave.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>
	<!-- 配置動態數據源這裏的targetDataSource就是路由數據源所對應的數據名稱 -->
	<bean id="dynamicDataSource" class="com.wby.o2o.dao.split.DynamicDataSource">
		<property name="targetDataSources">
			<map>
				<entry value-ref="master" key="master"></entry>
				<entry value-ref="slave" key="slave"></entry>				
			</map>
		</property>
	</bean>
	<!-- 由於這些操做是在程序執行以後,在mybatis生成sql語句以後,才決定數據源,所以
		引入懶加載 -->
	<bean id="dataSorce" 
		class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
		<property name="targetDataSource">
			<ref bean="dynamicDataSource"/>
		</property>
	</bean>

	<!-- 3.配置SqlSessionFactory對象 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 注入數據庫鏈接池 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
		<property name="configLocation" value="classpath:mybatis-config.xml" />
		<!-- 掃描entity包 使用別名 -->
		<property name="typeAliasesPackage" value="com.wby.o2o.entity" />
		<!-- 掃描sql配置文件:mapper須要的xml文件 -->
		<property name="mapperLocations" value="classpath:mapper/*.xml" />
	</bean>

	<!-- 4.配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中 -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 注入sqlSessionFactory -->
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
		<!-- 給出須要掃描Dao接口包 -->
		<property name="basePackage" value="com.wby.o2o.dao" />
	</bean>
</beans>

執行測試類

AreaDaoTest

package com.wby.o2o.dao;

import static org.junit.Assert.assertEquals;

import java.util.List;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import com.wby.o2o.BaseTest;
import com.wby.o2o.entity.Area;

public class AreaDaoTest extends BaseTest{
	@Autowired
	private AreaDao areaDao;
	@Test
	public void testQueryArea(){
		List<Area> areaList=areaDao.queryArea();
		assertEquals(4, areaList.size());
	}
}

顯示報錯

是由於DynamicDataSourceInterceptor類沒有添加註解

package com.wby.o2o.dao.split;

import java.util.Locale;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * 攔截器。
 * 若是是insert,update等寫操做,使用主數據庫
 * 若是是query,select等讀操做,使用從數據庫
 * @author Administrator
 *
 */
//增刪改的操做都封裝在了update裏面,所以update+query就能夠了
@Intercepts({@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class}),
	@Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})
	})
public class DynamicDataSourceInterceptor implements Interceptor{	
	/**
	 * 主要的攔截方法
	 */
	//日誌部分
	private static Logger logger =LoggerFactory.getLogger(
			DynamicDataSourceInterceptor.class);
	
	private static final String REGEX=".*insert\\uoo2o.*|.*delete\\u0020.*|.*update\\u0020.*";
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		boolean synchronizationAction=TransactionSynchronizationManager.isActualTransactionActive();		
		Object[] objects=invocation.getArgs();
		MappedStatement ms=(MappedStatement) objects[0];
		String lookupKey=DynamicDataSourceHolder.DB_MASTER;
		if (synchronizationAction!=true) {
			
			//讀方法
			if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
				//selectKey爲自增Id查詢主鍵(SELECT LAST_INSERT_ID())方法
				//使用主庫
				if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
					lookupKey=DynamicDataSourceHolder.DB_MASTER;
				}else {
					BoundSql boundSql=ms.getSqlSource().getBoundSql(objects[1]);
					String sql=boundSql.getSql()
							.toLowerCase(Locale.CHINA)
							.replaceAll("[\\t\\n\\r]", "");
					//匹配正則是否是增刪改的
					if (sql.matches(REGEX)) {
						lookupKey=DynamicDataSourceHolder.DB_MASTER;
					}else {
						lookupKey=DynamicDataSourceHolder.DB_SLAVE;
					}
				}
			}
		}else {
			lookupKey=DynamicDataSourceHolder.DB_MASTER;
		}
		logger.debug("設置方法[{}] use[{}] Strategy,SqlCommanType [{}]...",
				ms.getId(),lookupKey,ms.getSqlCommandType().name());
		DynamicDataSourceHolder.setDbType(lookupKey);
		return invocation.proceed();
	}

	/**
	 * 返回封裝好的對象,或者返回代理對象
	 * 若是攔截的對象target類型是Executor,就攔截,將intercept給他包裝到
	 * .wrap(target, this);裏面去。
	 * 若是不是,直接返回本體
	 * 爲何攔截Executor類型?
	 * 由於在mybatis中,Executor是用來支持一系列增刪改查操做的。而後經過intercept()
	 * 決定使用哪個數據源
	 */
	public Object plugin(Object target) {
		if (target instanceof Executor) {
			return Plugin.wrap(target, this);
		}else {
			return target;
		}
	}
	
	/**
	 * 類初始化的時候作一些相關的配置。非必要
	 */
	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		
	}
	
}

再次執行測試類AreaDaoTest,讀操做 運行正確

執行測試類ShopCategoryDaoTest,讀操做,運行,結果以下

執行測試類,shopDaoTest,寫操做,運行,結果以下

接下來測試service層

測試AreaServiceTest,讀操做

測試ShopServiceTest,寫操做

相關文章
相關標籤/搜索