讀寫分離實現方式


引用:https://blog.csdn.net/zbw18297786698/article/details/54343188

           https://blog.csdn.net/jack85986370/article/details/51559232

           http://www.cnblogs.com/boothsun/p/7454901.html

很多大型網站,所處理的業務中,有大約70%是查詢(select)相關的業務操作,而剩下的30%是寫操作(insert、delete、update),故可使用讀寫分離的方式提升數據庫的負載能力。

將所有的查詢處理都放到從服務器上,寫處理放在主服務器。

一、使用Spring基於應用層實現

在進入Service之前,使用AOP來做出判斷,是使用寫庫還是讀庫,判斷依據可以根據方法名判斷,比如說以query、find、get等開頭的就走讀庫,其他的走寫庫。

繼承AbstractRoutingDataSource實現動態數據源切換

mybatis配置文件

<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本屬性 url、user、password -->
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1" />
        <property name="minIdle" value="1" />
        <property name="maxActive" value="20" />

        <!-- 配置獲取連接等待超時的時間 -->
        <property name="maxWait" value="60000" />

        <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />

        <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000" />

        <property name="validationQuery" value="SELECT 'x'" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />

        <!-- 打開PSCache,並且指定每個連接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
</bean>

<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- 基本屬性 url、user、password -->
    <property name="url" value="${jdbc.r.url}" />
    <property name="username" value="${jdbc.r.username}" />
    <property name="password" value="${jdbc.r.password}" />

    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="1" />
    <property name="minIdle" value="1" />
    <property name="maxActive" value="20" />

    <!-- 配置獲取連接等待超時的時間 -->
    <property name="maxWait" value="60000" />

    <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000" />

    <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="300000" />

    <property name="validationQuery" value="SELECT 'x'" />
    <property name="testWhileIdle" value="true" />
    <property name="testOnBorrow" value="false" />
    <property name="testOnReturn" value="false" />

    <!-- 打開PSCache,並且指定每個連接上PSCache的大小 -->
    <property name="poolPreparedStatements" value="true" />
    <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
</bean>

<bean id="dynamicDataSource" class="com.boothsun.util.datasource.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <!-- write -->
            <entry key="master" value-ref="masterDataSource"/>
            <!-- read -->
            <entry key="slave" value-ref="slaveDataSource"/>
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>

<!-- MyBatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dynamicDataSource"/>
    <!-- 顯式指定Mapper文件位置 -->
    <property name="mapperLocations" value="classpath*:xmlmapper/*.xml"/>
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.boothsun.mybatismapper"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dynamicDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false"/>

spring獲取數據源的源碼:

 

DynamicDataSource方法:

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbType();
    }
}

DbContextHolder方法

public class DbContextHolder { // 注意:數據源標識保存在線程變量中,避免多線程操作數據源時互相干擾 
private static final ThreadLocal<String> contextHolder=new ThreadLocal<String>();

public static void setDbType(String dbType){
contextHolder.set(dbType);
}

public static String getDbType(){
String dbType=(String) contextHolder.get();
return dbType;
}

public static void clearDbType(){
contextHolder.remove();
}
}

使用ThreadLocal實現簡單的讀寫分離

@Component
@Aspect
public class DataSourceMethodInterceptor {

    @Before("execution(* com.xxx.xxx.xxx.xxx.service.impl.*.*(..))")
    public void dynamicSetDataSoruce(JoinPoint joinPoint) throws Exception {
        String methodName = joinPoint.getSignature().getName();
        // 查詢讀從庫
        if (methodName.startsWith("select") || methodName.startsWith("load") || methodName.startsWith("get") || methodName.startsWith("count") || methodName.startsWith("is")) {
            DynamicDataSourceHolder.setDataSource("slave");
        } else { // 其他讀主庫
            DynamicDataSourceHolder.setDataSource("master");
        }
    }

}

 

優點:

1、多數據源切換方便,由程序自動完成;

2、不需要引入中間件;

3、理論上支持任何數據庫;

缺點:

1、由程序員完成,運維參與不到;

2、不能做到動態增加數據源;

二、使用中間件實現讀寫分離

要求:

  1. 一主兩從,做讀寫分離。
  2. 多個從庫之間實現負載均衡。
  3. 可手動強制部分讀請求到主庫上。(因為主從同步有延遲,對實時性要求高的系統,可以將部分讀請求也走主庫)

mybatis配置文件

<bean id="master" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${jdbc.url.master}"></property>
    <property name="username" value="${jdbc.username.master}"></property>
    <property name="password" value="${jdbc.password.master}"></property>
    <property name="maxActive" value="100"/>
    <property name="initialSize" value="10"/>
    <property name="maxWait" value="60000"/>
    <property name="minIdle" value="5"/>
</bean>

<bean id="slave1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${jdbc.url.slave1}"></property>
    <property name="username" value="${jdbc.username.slave1}"></property>
    <property name="password" value="${jdbc.password.slave1}"></property>
    <property name="maxActive" value="100"/>
    <property name="initialSize" value="10"/>
    <property name="maxWait" value="60000"/>
    <property name="minIdle" value="5"/>
</bean>

<bean id="slave2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${jdbc.url.slave2}"></property>
    <property name="username" value="${jdbc.username.slave2}"></property>
    <property name="password" value="${jdbc.password.slave2}"></property>
    <property name="maxActive" value="100"/>
    <property name="initialSize" value="10"/>
    <property name="maxWait" value="60000"/>
    <property name="minIdle" value="5"/>
</bean>

<bean id="randomStrategy" class="io.shardingjdbc.core.api.algorithm.masterslave.RandomMasterSlaveLoadBalanceAlgorithm" />

<master-slave:data-source id="shardingDataSource" master-data-source-name="master" slave-data-source-names="slave1,slave2" strategy-ref="randomStrategy" />

強制路由

使用讀寫分離,可能會有主從同步延遲的問題,對於一些實時性要求比較高的業務,需強制部分讀請求訪問主庫。

HintManager 分片鍵值管理器

我們可使用hintManager.setMasterRouteOnly() .
@Test
public void HintManagerTest() {

    HintManager hintManager = HintManager.getInstance() ;
    hintManager.setMasterRouteOnly();

    OrderExample example = new OrderExample();
    example.createCriteria().andBusinessIdEqualTo(112);
    List<Order> orderList = orderMapper.selectByExample(example);
    System.out.println(JSONObject.toJSONString(orderList));

    hintManager.close();
}

阿里的mycat或360的Atlas也可以實現分庫分表,讀寫分離和負載均衡等處理。

引用:https://www.cnblogs.com/liujiduo/p/5004691.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM