Spring------mysql讀寫分離

來源:互聯網
上載者:User

標籤:訪問   識別   cin   com   指定   counter   tac   通過   title   

1. 為什麼要進行讀寫分離

大量的JavaWeb應用做的是IO密集型任務, 資料庫的壓力較大, 需要分流

大量的應用情境, 是讀多寫少, 資料庫讀取的壓力更大

一個很自然的思路是使用一主多從的資料庫叢集: 一個是主庫,負責寫入資料;其它都是從庫,負責讀取資料. 主從庫資料同步.

 

mysql原生支援主從複製

mysql主(稱master)從(稱slave)複製的原理:
1、master將資料改變記錄到二進位日誌(bin log)中,  這些記錄叫binary log events
2、slave將master的binary log events拷貝到它的中繼日誌(relay log)
3、slave重做中繼日誌中的事件, 將改變反映它自己的資料(資料重演)

 

解決讀寫分離的方案大致有兩種:

1)在應用程式層給讀/寫分別指定資料庫

好處是資料來源切換方便, 不用引入其他組件. 但是不能動態添加資料來源.

2)使用中介軟體解決

好處是來源程式不需要做任何改動, 還可以動態添加資料來源. 但中介軟體會帶來一定的效能損失.

目前有mysql-proxy, mycat, altas等

 

2. MySQL主從配置

主庫配置

修改my.ini:
#開啟主從複製,主庫的配置
log-bin = mysql3306-bin
#指定主庫serverid
server-id=101
#指定同步的資料庫,如果不指定則同步全部資料庫
binlog-do-db=mydb

執行以下SQL:
SHOW MASTER STATUS;

記錄下Position值,需要在從庫中設定同步起始值。

 

#授權從庫使用者slave01使用123456密碼登入本庫
grant replication slave on *.* to ‘slave01‘@‘127.0.0.1‘ identified by ‘123456‘;
flush privileges;

 

從庫配置

修改my.ini:

#指定serverid
server-id=102

執行以下SQL:

CHANGE MASTER TO
 master_host=‘127.0.0.1‘,
 master_user=‘slave01‘,
 master_password=‘123456‘,
 master_port=3306,
 master_log_file=‘mysql3306-bin.000006‘,   #設定主庫時記下的Position
 master_log_pos=1120;

#啟動slave同步
START SLAVE;

#查看同步狀態    Slave_IO_Running和Slave_SQL_Running都為Yes說明同步成功
SHOW SLAVE STATUS;

 

3. Spring動態資料源+AOP實現讀寫分離

這裡採用的是應用程式層的讀寫分離方案

使用AOP, 在執行Service方法前判斷,是使用寫庫還是讀庫
可以根據方法名作為依據判斷,比如說以query、find、get等開頭的就走讀庫,其他的走寫庫

切面類:

/** * 如果在spring配置了事務的策略,則標記了ReadOnly的方法用從庫Slave, 其它使用主庫Master。 * 如果沒有配置事務策略, 則採用方法名匹配, 以query、find、get開頭的方法用Slave,其它用Master。 */public class DataSourceAspect {    private List<String> slaveMethodPattern = new ArrayList<String>();  //儲存有readonly屬性的帶萬用字元方法名    private static final String[] defaultSlaveMethodStartWith = new String[]{"query", "find", "get" };    private String[] slaveMethodStartWith;  //儲存有slaveMethodStartWith屬性的方法名頭部        //注入    public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception {        if (txAdvice == null) {            // 沒有配置事務策略            return;        }        //從txAdvice擷取策略配置資訊        TransactionAttributeSource transactionAttributeSource = txAdvice.getTransactionAttributeSource();        if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource)) {            return;        }        //使用反射技術擷取到NameMatchTransactionAttributeSource對象中的nameMap屬性值        NameMatchTransactionAttributeSource matchTransactionAttributeSource = (NameMatchTransactionAttributeSource) transactionAttributeSource;        Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");        nameMapField.setAccessible(true); //設定該欄位可訪問        //擷取nameMap的值        Map<String, TransactionAttribute> map = (Map<String, TransactionAttribute>) nameMapField.get(matchTransactionAttributeSource);        //遍曆nameMap        for (Map.Entry<String, TransactionAttribute> entry : map.entrySet()) {            if (!entry.getValue().isReadOnly()) {   // 定義了ReadOnly的策略才加入到slaveMethodPattern                continue;            }            slaveMethodPattern.add(entry.getKey());        }    }    // 切面 before方法    public void before(JoinPoint point) {        // 擷取到當前執行的方法名        String methodName = point.getSignature().getName();        boolean isSlave = false;        if (slaveMethodPattern.isEmpty()) {            // 沒有配置read-only屬性,採用方法名匹配方式            isSlave = isSlaveByMethodName(methodName);        } else {            // 配置read-only屬性, 採用萬用字元匹配            for (String mappedName : slaveMethodPattern) {                if (isSlaveByConfigWildcard(methodName, mappedName)) {                    isSlave = true;                    break;                }            }        }        if (isSlave) {            // 標記為讀庫            DynamicDataSource.markMaster(true);        } else {            // 標記為寫庫            DynamicDataSource.markMaster(false);        }    }    // 匹配以指定名稱開頭的方法名, 配置了slaveMethodStartWith屬性, 或使用預設    private Boolean isSlaveByMethodName(String methodName) {        return StringUtils.startsWithAny(methodName, getSlaveMethodStartWith());    }    // 匹配帶萬用字元"xxx*", "*xxx" 和 "*xxx*"的方法名, 源自配置了readonly屬性的方法名    protected boolean isSlaveByConfigWildcard(String methodName, String mappedName) {        return PatternMatchUtils.simpleMatch(mappedName, methodName);    }    // 注入    public void setSlaveMethodStartWith(String[] slaveMethodStartWith) {        this.slaveMethodStartWith = slaveMethodStartWith;    }    public String[] getSlaveMethodStartWith() {        if(this.slaveMethodStartWith == null){            // 沒有配置slaveMethodStartWith屬性,使用預設            return defaultSlaveMethodStartWith;        }        return slaveMethodStartWith;    }}

Spring的RoutingDataSource

/** * 使用Spring的動態資料源,需要實現AbstractRoutingDataSource * 通過determineCurrentLookupKey方法拿到識別key來判斷選擇讀/寫資料來源 * token顯然是多例的, 所以引入ThreadLocal儲存 */public class DynamicDataSource extends AbstractRoutingDataSource {    // 讀庫總數    private Integer slaveCount;      // 讀庫輪詢計數, 初始為-1, 本類為單例, AtomicInteger安全執行緒    private AtomicInteger counter = new AtomicInteger(-1);    // 儲存讀庫的識別key sl1ve01, slave02...  寫庫識別key為master    private List<Object> slaveDataSources = new ArrayList<Object>();        //當前線程的寫庫/讀庫token    private static final ThreadLocal<Boolean> tokenHolder = new ThreadLocal<>();        public static void markMaster(boolean isMaster){        tokenHolder.set(isMaster);    }        @Override    protected Object determineCurrentLookupKey() {        if (tokenHolder.get()) {            return "master";   // 寫庫        }        // 輪詢讀庫, 得到的下標為:0、1、2...        Integer index = counter.incrementAndGet() % slaveCount;        if (counter.get() > 99999) { // 以免超出Integer範圍            counter.set(-1);         }        return slaveDataSources.get(index);    }    @Override    public void afterPropertiesSet() {        super.afterPropertiesSet();        // 父類的resolvedDataSources屬性是private, 需要使用反射擷取        Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources");        field.setAccessible(true); // 設定可訪問        try {            Map<Object, DataSource> resolvedDataSources = (Map<Object, DataSource>) field.get(this);            // 讀庫數等於dataSource總數減寫庫數            this.slaveCount = resolvedDataSources.size() - 1;            for (Map.Entry<Object, DataSource> entry : resolvedDataSources.entrySet()) {                if ("master".equals(entry.getKey())) {                    continue;                }                slaveDataSources.add(entry.getKey());            }        } catch (Exception e) {            e.printStackTrace();        }    }}

spring設定檔

<!-- 定義事務策略 -->    <tx:advice id="txAdvice" transaction-manager="transactionManager">        <tx:attributes>            <!--所有以query開頭的方法都是唯讀 -->            <tx:method name="query*" read-only="true" />  <!-- readonly屬性 -->            <!--其他方法使用預設事務策略 -->            <tx:method name="*" />        </tx:attributes>    </tx:advice>        <!-- 定義AOP切面處理器 -->    <bean class="com.zx.DataSourceAspect" id="dataSourceAspect">        <!-- 注入事務策略 -->        <property name="txAdvice" ref="txAdvice"/>        <!-- 指定slave方法的首碼(非必須) -->        <property name="slaveMethodStartWith" value="query,find,get"/>    </bean>    <aop:config>        <aop:pointcut id="myPointcut" expression="execution(* com.zx.service.*.*(..))" />        <!-- 將切面應用到自訂的切面處理器上,-9999保證該切面優先順序最高執行 -->        <aop:aspect ref="dataSourceAspect" order="-9999">            <aop:before method="before" pointcut-ref="myPointcut" />        </aop:aspect>    </aop:config>        <!-- 定義資料來源,繼承了spring的動態資料源 -->    <bean id="dataSource" class="com.zx.DynamicDataSource">        <!-- 設定多個資料來源 -->        <property name="targetDataSources">            <map key-type="java.lang.String">                <!-- 這些設定的key和determineCurrentLookupKey方法拿到的key相比對, 根據匹配選擇資料來源 -->                <entry key="master" value-ref="masterDataSource"/>   <!-- value-ref指向資料來源 -->                <entry key="slave01" value-ref="slave01DataSource"/>                <entry key="slave02" value-ref="slave02DataSource"/>                <entry key="slave03" value-ref="slave03DataSource"/>            </map>        </property>        <!-- 設定預設的資料來源,這裡預設走寫庫 -->        <property name="defaultTargetDataSource" ref="masterDataSource"/>    </bean>

 

Spring------mysql讀寫分離

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.