Spring 簡單實現讀寫分離
阿新 • • 發佈:2019-01-31
讀寫分離,基本原理是讓主資料庫處理事務性增、改、刪操作,而從資料庫處理查詢操作。資料庫複製被用來把事務性操作導致的變更同步到叢集的從資料庫。
一般常用實現方式有以下兩種:
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"/>