1. 程式人生 > >Spring 簡單實現讀寫分離

Spring 簡單實現讀寫分離

讀寫分離,基本原理是讓主資料庫處理事務性增、改、刪操作,而從資料庫處理查詢操作。資料庫複製被用來把事務性操作導致的變更同步到叢集的從資料庫。

一般常用實現方式有以下兩種:
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"/>