系統變遷之五--mysql讀寫分離

數據庫設定了主從同步后,單純的數據多點存放已經不能滿足我了(O(∩_∩)O)...
讀寫分離一直有各種方案,MySQL-Proxy也好,amoeba也罷,或者其他的中間件,都是在DB和application之間引入一個proxy,使用proxy來處理application中對DB的各種操作。
還有一種偽讀寫分離的方案,在項目中配置多個數據源,不同的操作連接不同的數據源。。。
不過我更傾向于在驅動層去處理這個問題,不太想再去額外維護一個中間件。而mysql的驅動目前已經支持使用ReplicationDriver來替代Driver,實現讀寫分離。

ReplicationDriver

官方關于ReplicationDriver的說明可以參考這里:http://dev.mysql.com/doc/connector-j/5.1/en/connector-j-master-slave-replication-connection.html

ReplicationDriver驅動分離的機制是靠判斷connection.setReadOnly(true)來決定是否訪問從庫,而Spring的事務管理,可以使用@Tranactional(readonly=true)來設置連接是否為只讀,基本上就這些,看起來挺簡單吧...一路還是不斷踩坑,且聽俺慢慢道來。。。

先提一下,數據庫連接池bonecp是不支持使用ReplicationDriver做讀寫分離的。最初項目為了求快和求簡,用了bonecp(配制簡單,塊頭小),不過在引入ReplicationDriver的時候,卻踩了坑,無論怎么配置都是訪問的主庫。后面切換為druid才解決,關于淘寶開源的數據庫連接池druid介紹,可以參考這里:https://github.com/alibaba/druid/wiki

在數據庫連接池中配置ReplicationDriver
  • 首先引入druid及ReplicationDriver的依賴

      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>${druid-version}</version>
      </dependency>
       <!-- ReplicationDriver跟Driver在同一路徑下 -->
      <dependency>    
          <groupId>mysql</groupId>    
          <artifactId>mysql-connector-java</artifactId>    
          <version>5.1.39</version>
     </dependency>
    
  • 數據庫連接池配置

      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">    
          <!--<property name="url" value="jdbc:mysql:replication://192.168.1.234:3306,192.168.1.164:3306/yxdb?useUnicode=true&characterEncoding=UTF-8" />-->    
         <property name="url" value="${dataSourceUrl}"/>    
         <property name="username" value="${username}"/>    
         <property name="password" value="${password}"/>    
         
         <!-- druid可以根據url自動識別數據庫類型并自己選擇驅動,不過默認會識別成Driver,所以這里強制指定ReplicationDriver -->
        <property name="driverClassName" value="com.mysql.jdbc.ReplicationDriver" />    
        
        <!-- druid監控相關設定,mergeStat--合并統計sql -->
        <!--<property name="filters" value="stat" />-->    
        <property name="filters" value="mergeStat"/>    
    
        <property name="maxActive" value="20" />    
        <property name="initialSize" value="1" />    
        <property name="maxWait" value="60000" />    
        <property name="minIdle" value="1" />   
        <property name="timeBetweenEvictionRunsMillis" value="60000" />    
        <property name="minEvictableIdleTimeMillis" value="300000" />    
        <property name="testWhileIdle" value="false" />    
        <property name="testOnBorrow" value="false" />    
        <property name="testOnReturn" value="false" />    
        <property name="poolPreparedStatements" value="true" />    
        <property name="maxOpenPreparedStatements" value="20" />
     </bean>
    

每個屬性的說明可以參考這里:
https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

  • 注意url中,mysql的數據庫連接要改成 jdbc:mysql:replication:// 開頭,而后面的IP地址,則依照masterIP:port,slave1IP:port,slave2IP:port的順序往下寫,主庫在先,從庫在后。
spring事務中對于連接的處理
  • 做讀寫分離,首先需要梳理哪些方法是只讀操作,哪些方法是讀寫一體或只寫操作。而對于比較關鍵的只讀操作,也不建議直接遷移到從庫,畢竟主從復制是有一定的時間延遲的。
  • 既然提到事務了,如果在一個事務中涉及到了多張表的操作,一定要看下mysql的autocommit選項是否關閉,否則會造成回滾失敗
Paste_Image.png
  • 而且要注意,這個命令只針對當前用戶,如果需要全局生效,則需要使用global關鍵字
Paste_Image.png
  • 還要注意的是,這個選項對于mysql的root用戶是不生效的。。。而且,如果是使用spring統一管理數據庫連接,這塊spring是在DataSourceTransactionManager.java中默認設置為false的,如下:

    // switch to manual commit if necessary. this is very expensive in some jdbc drivers,
    // so we don't want to do it unnecessarily (for example if we've explicitly
    // configured the connection pool to set it already).
    if (con.getautocommit()) {
        txobject.setmustrestoreautocommit(true);
    if (logger.isdebugenabled()) {
      logger.debug("switching jdbc connection [" + con + "] to manual commit");
    }
    con.setautocommit(false);
    }
    
  • 代碼級別的設置:
    在只需要訪問讀庫的方法上,添加注解 ,搞定,收工。。。

     @Transactional(readOnly = true,...) 
    
    • 此注解只能在public方法上使用才會生效
    • readOnly默認為false的,故對于需要訪問主庫的,這個屬性可以不設置
附上druid中的配置監控,方便查看統計
  • 先曬兩張監控圖
Paste_Image.png
Paste_Image.png

druid跟其他數據庫連接池除了在連接上面做了很多優化之外,亮點就是在監控這塊了

  • 配置druid web監控
    • 在項目的web.xml中加入filter配置

       <!-- druid相關監控數據 -->
       <filter>    
           <filter-name>DruidWebStatFilter</filter-name>    
           <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>    
           <init-param>        
                <param-name>exclusions</param-name>        
                <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>    
           </init-param>    
           <init-param>        
                <param-name>profileEnable</param-name>        
                <param-value>true</param-value>    
           </init-param>
        </filter>
        <filter-mapping>    
               <filter-name>DruidWebStatFilter</filter-name>    
               <url-pattern>/*</url-pattern>
       </filter-mapping>
      
    • 在web.xml中加入servlet配置

        <!-- 設置druid數據源相關監控servlet -->
        <servlet>    
             <servlet-name>DruidStatView</servlet-name>    
             <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>    
             <init-param>        
                    <!-- 允許清空統計數據 -->        
                    <param-name>resetEnable</param-name>        
                    <param-value>true</param-value>    
            </init-param>    
            <init-param>        
                    <!-- 用戶名,自己指定 -->        
                    <param-name>loginUsername</param-name>        
                    <param-value>druid</param-value>    
            </init-param>    
            <init-param>        
                    <!-- 密碼 -->        
                    <param-name>loginPassword</param-name>        
                    <param-value>****</param-value>    
            </init-param>
        </servlet>
      
        <servlet-mapping>    
            <servlet-name>DruidStatView</servlet-name>    
            <url-pattern>/druid/*</url-pattern>
        </servlet-mapping>
      

重啟后,直接訪問http://ip/domainname[:port]/appname/druid ,使用上述指定的用戶名及密碼就可以登錄了,可以查看sql及bean相關的統計,以及慢sql等統計數據。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容