读写分离,基本原理是让主数据库处理事务性增、改、删操作,而从数据库处理查询操作。数据库复制被用来把事务性操作导致的变更同步到集群的从数据库。
一般常用实现方式有以下两种: 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"/>