读写分离,基本原理是让主数据库处理事务性增、改、删操作,而从数据库处理查询操作。数据库复制被用来把事务性操作导致的变更同步到集群的从数据库。
一般常用实现方式有以下两种:
1,主从分离,更新操作主数据库,查询操作从数据库
2,动态数据源切换。
下面利用Spring的AbstractRoutingDataSource 类简单实现主从分离。
首先DynamicDataSource 继承AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
//线程安全的加减
private AtomicInteger counter = new AtomicInteger();
/**
* master库 dataSource
*/
private DataSource master;
/**
* slaves
*/
private List<DataSource> slaves;
@Override
protected Object determineCurrentLookupKey() {
//该方法可以返回数据源标识
// return DataSourceHolder.getDataSource();
return null;
}
@Override
public void afterPropertiesSet() {
//do nothing
}
/**
* 根据标识
* 获取数据源
*/
@Override
protected DataSource determineTargetDataSource() {
DataSource returnDataSource;
if (DataSourceHolder.isMaster()) {
returnDataSource = master;
} else if (DataSourceHolder.isSlave()) {
int count = counter.incrementAndGet();
if (count > 1000000) {
counter.set(0);
}
int n = slaves.size();
int index = count % n;
returnDataSource = slaves.get(index);
} else {
returnDataSource = master;
}
// if (returnDataSource instanceof BoneCPDataSource) {
// BoneCPDataSource source = (BoneCPDataSource) returnDataSource;
// String jdbcUrl = source.getJdbcUrl();
// logger.error("jdbcUrl:" + jdbcUrl);
// }
return returnDataSource;
}
public DataSource getMaster() {
return master;
}
public void setMaster(DataSource master) {
this.master = master;
}
public List<DataSource> getSlaves() {
return slaves;
}
public void setSlaves(List<DataSource> slaves) {
this.slaves = slaves;
}
}
上面示例重写了determineTargetDataSource,该方法根据标识获取数据源。(determineCurrentLookupKey获取数据源标识)源代码如下:
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
重写后根据DataSourceHolder 进行数据源判断,DataSourceHolder类如下:
public class DataSourceHolder {
private static final String MASTER = "master";
private static final String SLAVE = "slave";
/**
* dataSource master or slave
*/
private static final ThreadLocal<String> dataSources = new ThreadLocal<>();
/**
* master local
*/
private static final ThreadLocal<DataSource> masterLocal = new ThreadLocal<>();
/**
* master local
*/
private static final ThreadLocal<DataSource> slaveLocal = new ThreadLocal<>();
/**
* 设置数据源
*
* @param dataSourceKey
* @author
*/
private static void setDataSource(String dataSourceKey) {
dataSources.set(dataSourceKey);
}
/**
* 获取数据源
*
* @return
* @author
*/
private static String getDataSource() {
return dataSources.get();
}
/**
* 标志为master
*/
public static void setMaster() {
setDataSource(MASTER);
}
/**
* 标志为slave
*/
public static void setSlave() {
setDataSource(SLAVE);
}
/**
* 将master放入threadlocal
*
* @param master
*/
public static void setMaster(DataSource master) {
masterLocal.set(master);
}
/**
* 将slave放入threadlocal
*
* @param slave
*/
public static void setSlave(DataSource slave) {
slaveLocal.set(slave);
}
public static boolean isMaster() {
return getDataSource() == MASTER;
}
public static boolean isSlave() {
return getDataSource() == SLAVE;
}
/**
* 清除thread local中的数据源
*
* @author
*/
public static void clearDataSource() {
dataSources.remove();
masterLocal.remove();
slaveLocal.remove();
}
}
DataSourceHolder 定义了一些数据源判断及操作方法。并且通过ThreadLocal保证了线程安全。
下面需要继承DataSourceTransactionManager进行事务管理
public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
/**
* 只读事务到从库
* 读写事务到主库
*/
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
boolean readOnly = definition.isReadOnly();
if (readOnly) {
DataSourceHolder.setSlave();
} else {
DataSourceHolder.setMaster();
}
super.doBegin(transaction, definition);
}
/**
* 清理本地线程的数据源
*/
@Override
protected void doCleanupAfterCompletion(Object transaction) {
super.doCleanupAfterCompletion(transaction);
DataSourceHolder.clearDataSource();
}
}
至此,有关主从分离的逻辑处理已经完成,下面需要定义两个数据源。
<bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
<property name="idleMaxAge" value="${jdbc.idleMaxAge}"/>
<property name="maxConnectionsPerPartition" value="${jdbc.maxConnectionsPerPartition}"/>
<property name="minConnectionsPerPartition" value="${jdbc.minConnectionsPerPartition}"/>
<property name="partitionCount" value="${jdbc.partitionCount}"/>
<property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
<property name="statementsCacheSize" value="${jdbc.statementsCacheSize}"/>
<property name="releaseHelperThreads" value="${jdbc.releaseHelperThreads}"/>
</bean>
<bean id="slaveDataSource1" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.slave1.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
<property name="idleMaxAge" value="${jdbc.idleMaxAge}"/>
<property name="maxConnectionsPerPartition" value="${jdbc.maxConnectionsPerPartition}"/>
<property name="minConnectionsPerPartition" value="${jdbc.minConnectionsPerPartition}"/>
<property name="partitionCount" value="${jdbc.partitionCount}"/>
<property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
<property name="statementsCacheSize" value="${jdbc.statementsCacheSize}"/>
<property name="releaseHelperThreads" value="${jdbc.releaseHelperThreads}"/>
</bean>
同时需要将数据放入DynamicDataSource中,并使用自定义的类进行事务管理
<bean id="dynamicDataSource" class="com.nzs.datasource.DynamicDataSource">
<property name="master" ref="masterDataSource"/>
<property name="slaves">
<list>
<ref bean="slaveDataSource1"/>
</list>
</property>
</bean>
<!-- 配置mybitasSqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- 配置SqlSessionTemplate -->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!-- 事务配置 -->
<bean id="transactionManager" class="com.nzs.datasource.DynamicDataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource"/>
</bean>
<!-- 使用annotation注解方式配置事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>