主從分離之SSM與Mysql

  大型網站爲了軟解大量的併發訪問,除了在網站實現分佈式負載均衡,遠遠不夠。到了數據業務層、數據訪問層,若是仍是傳統的數據結構,或者只是單單靠一臺服務器扛,如此多的數據庫鏈接操做,數據庫必然會崩潰,數據丟失的話,後果更是 不堪設想。這時候,咱們會考慮如何減小數據庫的聯接,一方面採用優秀的代碼框架,進行代碼的優化,採用優秀的數據緩存技術如:redis,若是資金豐厚的話,必然會想到假設服務器羣,來分擔主數據庫的壓力。Ok切入今天微博主題,利用MySQL主從配置,實現讀寫分離,減輕數據庫壓力。這種方式,在現在不少網站裏都有使用,也不是什麼新鮮事情,今天總結一下,方便你們學習參考一下。java

  原理:主服務器(master)負責寫操做(包括增刪改),有且僅有一臺;從服務器(slave)負責讀操做,能夠配置n臺,寫入數據的時候,首先master會把數據寫在本地 Binary Log 文件中,而後經過I/O 寫入 slave 的 relayLog 日誌文件中,以後才同步數據庫中,實現主從同步mysql

  在這裏我使用的是兩臺CenterOS 6.5 的虛擬機進行配置,master IP :192.168.1.111 , Slave IP :192.168.1.112,開始了:redis

 

一: Mysql 配置主從分離spring

1.下載安裝 Mysql 數據庫

 1.1  # yum list | grep mysql     --咱們經過命令能夠查看yum上提供下載的mysql的版本信息

  

 1.2  # yum install -y mysql-server mysql mysql-deve  --運行命令開始安裝直到安裝完成

 

2. 配置Mysql數據庫的 master 和 slave

  2.1  首先配置 master: 

    # vi /etc/my.cnf  -- Mysql 的配置通常都是在這,直接運行命令進行修改sql

    在【mysqld】添加如下:數據庫

server-id=1   
log-bin=master-bin
log-bin-index=master-bin.index

 

    隨後開啓數據庫apache

    # service mysqld start;緩存

    登陸進去數據庫:服務器

    # mysql -uroot -p;session

    進去以後建立一個用戶用來主從數據庫的通訊

    # create user manager;

    授予 REPLICATION SLAVE 權限就夠了

    # grant replication slave on *.* to 'manager'@'192.168.1.112' identified by '123456';

    # flush privileges;

    以後查看一下master日誌

    # show master status;

    +-------------------+----------+--------------+------------------+
    | File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
    +-------------------+----------+--------------+------------------+
    | master-bin.000001 | 1285 | | |
    +-------------------+----------+--------------+------------------+
    1 row in set (0.00 sec)

    好了 ,master 配置完成了。

  2,2 配置slave

    和 master 同樣,首先修改配置文件

    # vi /etc/my.cnf 

    在【mysqld】添加如下: 

server-id=2
relay-log-index=slave-relay-bin.index
relay-log=slave-relay-bin

 

    而後開啓 mysql 服務,登陸進去以後,鏈接master

   # change master to master_host='192.168.1.111', //Master 服務器Ip
    master_port=3306,
    master_user='manager',
    master_password='123456', 
    master_log_file='master-bin.000001',//Master服務器產生的日誌
    master_log_pos=0;

    啓動 slave

    # start slave 

    查看一下slave運行有沒有錯誤,若是沒有就說明已經配置好了,主和從已經正常工做了

    # show slave status \G;

 

二: 配置Spring 和 Mybatis

  首先修改一下 jdbc.properties ,配置兩條鏈接數據庫URL

jdbc.driver=com.mysql.jdbc.Driver jdbc.slave.url=jdbc:mysql://192.168.237.111/test?useUnicode=true&characterEncoding=utf8 jdbc.master.url=jdbc:mysql://192.168.237.112/test?useUnicode=true&characterEncoding=utf8 jdbc.username=xxxxx jdbc.password=xxxxx

 

 

   定義一個攔截器,實現 import org.apache.ibatis.plugin.Interceptor 接口

package com.smy.dao.split; import java.util.Properties; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.keygen.SelectKeyGenerator; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.support.TransactionSynchronizationManager; @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) }) public class DynamicDataSourceInterceptor implements Interceptor { // 數據庫操做字符串的匹配,insert,update,delete
    private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*"; private static Logger log = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { // 判斷是否被事務管理
        boolean synchronization = TransactionSynchronizationManager.isActualTransactionActive(); Object[] objects = invocation.getArgs(); MappedStatement ms = (MappedStatement) objects[0]; String lookupKey = DynamicDataSourceHolder.DB_MASTER;; // 判斷是否被事務管理
        if (!synchronization) { // 讀操做
            if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) { // selectKey 爲自增id查詢主鍵(SELECT LAST_INSERT_ID())方法,使用主庫
                if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) { lookupKey = DynamicDataSourceHolder.DB_MASTER; } else { // 若是執行到了這裏說明就是沒有被事務管理也沒有指定主鍵Key,只能對sql // 語句進行匹配規則 
                    BoundSql boundSql = ms.getBoundSql(objects[1]); String sql = boundSql.getSql().toLowerCase().replaceAll("\\t\\n\\r", " "); if (sql.matches(REGEX)) { lookupKey = DynamicDataSourceHolder.DB_MASTER; } else { lookupKey = DynamicDataSourceHolder.DB_SLAVE; } } } } else { // 若是被事務管理說明 就是增刪改,須要在 master 中操做
            lookupKey = DynamicDataSourceHolder.DB_MASTER; } log.debug("設置方法[{}] use [{}] Strategy, SqlCommanType [{}]..", ms.getId(), lookupKey, ms.getSqlCommandType().name()); //設置訪問數據庫類型 master 或者 slave
 DynamicDataSourceHolder.setDbType(lookupKey); return invocation.proceed(); } @Override public Object plugin(Object target) { // 若是執行的是增刪改的操做就使用本攔截,若是不是就直接返回
        if (target instanceof Executor) { return Plugin.wrap(target, this); } return target; } @Override public void setProperties(Properties properties) { } }

 

  

  定義一個類 DynamicDataSourceHolder 來管理 咱們的master 和 slave 常量,也就是管理咱們的數據源

package com.smy.dao.split; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DynamicDataSourceHolder { private static Logger log = LoggerFactory.getLogger(DynamicDataSourceHolder.class); private static ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static final String DB_MASTER = "master"; public static final String DB_SLAVE = "slave"; /** * 獲取數據源 * @return
     */
    public static String getDbType() { String db = contextHolder.get(); if(db==null) { db = DB_MASTER; } return db; } /** * 設置數據源 * @param dbType */
    public static void setDbType(String dbType) { log.debug("所使用的數據源"+dbType); contextHolder.set(dbType); } /** * 清理數據源 */
    public static void clearDbType() { contextHolder.remove(); } }

 

 

  接下來 定義一個類 繼承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 類,由於這個類可以動態路由到數據源

package com.smy.dao.split; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDbType(); } }

 

   實現這個類的一個抽象方法,查看AbstractRoutingDataSource 類源碼有這麼一個方法:

/** * Retrieve the current target DataSource. Determines the * {@link #determineCurrentLookupKey() current lookup key}, performs * a lookup in the {@link #setTargetDataSources targetDataSources} map, * falls back to the specified * {@link #setDefaultTargetDataSource default target DataSource} if necessary. * @see #determineCurrentLookupKey() */
    protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); // 這個方法就肯定了要使用哪一個數據源,然而AbstractRoutingDataSource 類中,這個方法是抽象的,因此咱們要實現這個類並實現該方法 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; }

 

  OK ,開始咱們在Spring 和 Mybatis 配置文件中配置咱們的攔截器 和 動態數據源Bean 

  Mybatis.xml 中配置添加plugin:

<plugins>
        <plugin interceptor="com.smy.dao.split.DynamicDataSourceInterceptor" />
</plugins>

 

  Spring-dao.xml 中:

<context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 2.數據庫鏈接池 -->
    <!-- 定義抽象數據源,使其它數據源 Bean 繼承該數據源 -->
    <bean id="abstractDataSource" abstract="true" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

        <!-- c3p0鏈接池的私有屬性 -->
        <property name="maxPoolSize" value="30" />
        <property name="minPoolSize" value="10" />
        <!-- 關閉鏈接後不自動commit -->
        <property name="autoCommitOnClose" value="false" />
        <!-- 獲取鏈接超時時間 -->
        <property name="checkoutTimeout" value="10000" />
        <!-- 當獲取鏈接失敗重試次數 -->
        <property name="acquireRetryAttempts" value="2" />
        <property name="maxStatements" value="0" />
    </bean>
    
    <!-- 主數據源 master -->
    <bean id="master" parent="abstractDataSource">
        <!-- 配置鏈接池屬性 -->
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.master.url}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    
    <!-- 從數據源 slave -->
    <bean id="slave" parent="abstractDataSource">
        <!-- 配置鏈接池屬性 -->
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.slave.url}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    <!-- 配置動態數據源,這兒targetDataSources就是路由數據源所對應的名稱 -->
    <bean id="dynamicDataSource" class="com.smy.dao.split.DynamicDataSource">
        <property name="targetDataSources">
            <map>
                <entry value-ref="master" key="master"></entry>
                <entry value-ref="slave" key="slave"></entry>
            </map>
        </property>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
        <property name="targetDataSource">
            <ref bean="dynamicDataSource" />
        </property>
    </bean>

 

  至於爲何這麼配置, 相信你們看過源碼以後就會很清楚了。。

  

        The   End 。。。。。。。。。。。。。。。。。。

相關文章
相關標籤/搜索