您好,登錄后才能下訂單哦!
分布式環境下數據庫的讀寫分離策略是解決數據庫讀寫性能瓶頸的一個關鍵解決方案,更是最大限度了提高了應用中讀取 (Read)數據的速度和并發量。
在進行數據庫讀寫分離的時候,我們首先要進行數據庫的主從配置,最簡單的是一臺Master和一臺Slave(大型網站系統的話,當然會很復雜,這里只是分析了最簡單的情況)。通過主從配置主從數據庫保持了相同的數據,我們在進行讀操作的時候訪問從數據庫Slave,在進行寫操作的時候訪問主數據庫Master。這樣的話就減輕了一臺服務器的壓力。
在進行讀寫分離案例分析的時候。首先,配置數據庫的主從復制,可以選擇下面的這個方法:
MySQL5.6 數據庫主從(Master/Slave)同步安裝與配置詳解
當然,只是簡單的為了看一下如何用代碼的方式實現數據庫的讀寫分離,完全不必要去配置主從數據庫,只需要兩臺安裝了 相同數據庫的機器就可以了。
具體到開發中,實現讀寫分離常用的有兩種方式:
下面會詳細的介紹實現方式。
1、項目代碼地址
目前該Demo的項目地址在開源中國 碼云 上邊:http://git.oschina.net/xuliugen/aop-choose-db-demo
2、項目結構
上圖中,除了標記的代碼,其他的主要是配置代碼和業務代碼。
3、具體分析
該項目是SSM框架的一個demo,Spring、Spring MVC和MyBatis,具體的配置文件不在過多介紹。
(1)UserContoller模擬讀寫數據
<pre style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; word-break: break-word;">
/**
* 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讀寫數據源配置
<pre style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; word-break: break-word;">
<?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 這個是進行數據庫選擇的。
<pre style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; word-break: break-word;">
<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
<pre style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; word-break: break-word;">
/**
* 獲取數據源,用于動態切換數據源
*/
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攔截
<pre style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; word-break: break-word;">
/**
* 切換數據源(不同方法調用不同數據源)
*/
@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類
<pre style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; word-break: break-word;">
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();
}
}
主要代碼,如上所述。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。