一、前言
分布式環境下數據庫的讀寫分離策略是解決數據庫讀寫性能瓶頸的一個關鍵解決方案,更是最大限度了提高了應用中讀取 (Read)數據的速度和并發量。
在進行數據庫讀寫分離的時候,我們首先要進行數據庫的主從配置,最簡單的是一臺Master和一臺Slave(大型網站系統的話,當然會很復雜,這里只是分析了最簡單的情況)。通過主從配置主從數據庫保持了相同的數據,我們在進行讀操作的時候訪問從數據庫Slave,在進行寫操作的時候訪問主數據庫Master。這樣的話就減輕了一臺服務器的壓力。
在進行讀寫分離案例分析的時候。首先,配置數據庫的主從復制,MySQL5.6 數據庫主從(Master/Slave)同步安裝與配置詳解
當然,只是簡單的為了看一下如何用代碼的方式實現數據庫的讀寫分離,完全不必要去配置主從數據庫,只需要兩臺安裝了 相同數據庫的機器就可以了。
二、實現讀寫分離的兩種方法
具體到開發中,實現讀寫分離常用的有兩種方式:
1、第一種方式是我們最常用的方式,就是定義2個數據庫連接,一個是MasterDataSource,另一個是SlaveDataSource。更新數據時我們讀取MasterDataSource,查詢數據時我們讀取SlaveDataSource。這種方式很簡單,我就不贅述了。
2、第二種方式動態數據源切換,就是在程序運行時,把數據源動態織入到程序中,從而選擇讀取主庫還是從庫。主要使用的技術是:Annotation,spring AOP ,反射。
下面會詳細的介紹實現方式。
三、Aop實現主從數據庫的讀寫分離案例
1、項目代碼地址
目前該Demo的項目地址:demo
2、項目結構
上圖中,除了標記的代碼,其他的主要是配置代碼和業務代碼。
3、具體分析
該項目是SSM框架的一個demo,Spring、Spring MVC和MyBatis,具體的配置文件不在過多介紹。
(1)UserContoller模擬讀寫數據
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/** * Created by xuliugen on 2016/5/4. */ @Controller @RequestMapping (value = "/user" , produces = { "application/json;charset=UTF-8" }) public class UserController { @Inject private IUserService userService; //http://localhost:8080/user/select.do @ResponseBody @RequestMapping (value = "/select.do" , method = RequestMethod.GET) public String select() { User user = userService.selectUserById( 123 ); return user.toString(); } //http://localhost:8080/user/add.do @ResponseBody @RequestMapping (value = "/add.do" , method = RequestMethod.GET) public String add() { boolean isOk = userService.addUser( new User( "333" , "444" )); return isOk == true ? "shibai" : "chenggong" ; } } |
模擬讀寫數據,調用IUserService 。
(2)spring-db.xml讀寫數據源配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
<? xml version = "1.0" encoding = "UTF-8" ?> < beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns:aop = "http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> < bean id = "statFilter" class = "com.alibaba.druid.filter.stat.StatFilter" lazy-init = "true" > < property name = "logSlowSql" value = "true" /> < property name = "mergeSql" value = "true" /> </ bean > <!-- 數據庫連接 --> < bean id = "readDataSource" class = "com.alibaba.druid.pool.DruidDataSource" destroy-method = "close" init-method = "init" lazy-init = "true" > < property name = "driverClassName" value = "${driver}" /> < property name = "url" value = "${url1}" /> < property name = "username" value = "root" /> < property name = "password" value = "${password}" /> <!-- 省略部分內容 --> </ bean > < bean id = "writeDataSource" class = "com.alibaba.druid.pool.DruidDataSource" destroy-method = "close" init-method = "init" lazy-init = "true" > < property name = "driverClassName" value = "${driver}" /> < property name = "url" value = "${url}" /> < property name = "username" value = "root" /> < property name = "password" value = "${password}" /> <!-- 省略部分內容 --> </ bean > <!-- 配置動態分配的讀寫 數據源 --> < bean id = "dataSource" class = "com.xuliugen.choosedb.demo.aspect.ChooseDataSource" lazy-init = "true" > < property name = "targetDataSources" > < map key-type = "java.lang.String" value-type = "javax.sql.DataSource" > <!-- write --> < entry key = "write" value-ref = "writeDataSource" /> <!-- read --> < entry key = "read" value-ref = "readDataSource" /> </ map > </ property > < property name = "defaultTargetDataSource" ref = "writeDataSource" /> < property name = "methodType" > < map key-type = "java.lang.String" > <!-- read --> < entry key = "read" value = ",get,select,count,list,query" /> <!-- write --> < entry key = "write" value = ",add,create,update,delete,remove," /> </ map > </ property > </ bean > </ beans > |
上述配置中,配置了readDataSource和writeDataSource兩個數據源,但是交給SqlSessionFactoryBean進行管理的只有dataSource,其中使用到了:com.xuliugen.choosedb.demo.aspect.ChooseDataSource 這個是進行數據庫選擇的。
1
2
3
4
5
6
7
8
|
< property name = "methodType" > < map key-type = "java.lang.String" > <!-- read --> < entry key = "read" value = ",get,select,count,list,query" /> <!-- write --> < entry key = "write" value = ",add,create,update,delete,remove," /> </ map > </ property > |
配置了數據庫具體的那些是讀哪些是寫的前綴關鍵字。ChooseDataSource的具體代碼如下:
(3)ChooseDataSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/** * 獲取數據源,用于動態切換數據源 */ public class ChooseDataSource extends AbstractRoutingDataSource { public static Map<String, List<String>> METHOD_TYPE_MAP = new HashMap<String, List<String>>(); /** * 實現父類中的抽象方法,獲取數據源名稱 * @return */ protected Object determineCurrentLookupKey() { return DataSourceHandler.getDataSource(); } // 設置方法名前綴對應的數據源 public void setMethodType(Map<String, String> map) { for (String key : map.keySet()) { List<String> v = new ArrayList<String>(); String[] types = map.get(key).split( "," ); for (String type : types) { if (StringUtils.isNotBlank(type)) { v.add(type); } } METHOD_TYPE_MAP.put(key, v); } } } |
(4)DataSourceAspect進行具體方法的AOP攔截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
/** * 切換數據源(不同方法調用不同數據源) */ @Aspect @Component @EnableAspectJAutoProxy (proxyTargetClass = true ) public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger( this .getClass()); @Pointcut ( "execution(* com.xuliugen.choosedb.demo.mybatis.dao.*.*(..))" ) public void aspect() { } /** * 配置前置通知,使用在方法aspect()上注冊的切入點 */ @Before ( "aspect()" ) public void before(JoinPoint point) { String className = point.getTarget().getClass().getName(); String method = point.getSignature().getName(); logger.info(className + "." + method + "(" + StringUtils.join(point.getArgs(), "," ) + ")" ); try { for (String key : ChooseDataSource.METHOD_TYPE_MAP.keySet()) { for (String type : ChooseDataSource.METHOD_TYPE_MAP.get(key)) { if (method.startsWith(type)) { DataSourceHandler.putDataSource(key); } } } } catch (Exception e) { e.printStackTrace(); } } } |
(5)DataSourceHandler,數據源的Handler類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.xuliugen.choosedb.demo.aspect; /** * 數據源的Handler類 */ public class DataSourceHandler { // 數據源名稱線程池 public static final ThreadLocal<String> holder = new ThreadLocal<String>(); /** * 在項目啟動的時候將配置的讀、寫數據源加到holder中 */ public static void putDataSource(String datasource) { holder.set(datasource); } /** * 從holer中獲取數據源字符串 */ public static String getDataSource() { return holder.get(); } } |
主要代碼,如上所述。
本文代碼:demo
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://blog.csdn.net/xlgen157387/article/details/53930382