數據庫設定了主從同步后,單純的數據多點存放已經不能滿足我了(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選項是否關閉,否則會造成回滾失敗
- 而且要注意,這個命令只針對當前用戶,如果需要全局生效,則需要使用global關鍵字
-
還要注意的是,這個選項對于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中的配置監控,方便查看統計
- 先曬兩張監控圖
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等統計數據。