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,写操做

相关文章
相关标签/搜索