1. 程式人生 > >spring動態資料來源分庫

spring動態資料來源分庫

web專案的瓶頸:服務端(伺服器壓力+資料庫壓力)


伺服器解決辦法:程式優化,提高程式碼執行率


資料庫壓力:快取、分表、分庫




1,分表:適合針對單個的表資料量比較大的情況,分成多張表儲存,比如系統日誌
例如:
系統日誌表以這樣的形式儲存,每個月一張表。
logs_2012_1
logs_2012_2
logs_2012_3




2,分庫
----水平分庫(同構:庫中表結構相同,範圍不同。例如:按地區分,按照省份生成資料庫)
----垂直分庫(異構:庫中表結構不同。例如:按模組分,將某些表單獨儲存到另一個數據庫)

當然,水平分庫與垂直分庫並不是絕對的。也可以混合
例如:
有2個數據庫a、b
a庫中有x、y、z三張表,
b庫中有x、k兩張表
a、b庫中的x表結構完全一致,在某些情況存到a庫,在另外的情況下存到b庫


分庫的原理:動態資料庫路由器+令牌(ThreadLoac)



分庫實現流程:
----------------------------------------------------------------------------
action中資料分發,決定儲存到那個資料庫,繫結令牌到當前執行緒。
比如當前資料決定存放到a庫,繫結一個令牌到當前執行緒,該令牌代表a庫的key
---->
service層
---->
開事務獲取連線
----->
資料庫路由器
----->
獲取當前執行緒中的臨牌
----->
資料庫路由器,通過令牌及其對應的spring檔案中路由的配置獲取到要連線的庫
----->
資料儲存到當前連線到的庫中




要注意的是:
令牌的繫結與解除順序,本次資料儲存成功後應該立即把當前繫結的令牌解除。
最好在獲取令牌的方法中,就解除令牌、以保證下次從新獲取正確的連線。


分庫結構圖:


分庫執行順序:







實現小例子:

1,定義資料庫路由器

package cn.test.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 自定義資料來源路由器
 */
public class SurveyparkDataSourceRouter extends AbstractRoutingDataSource {

	protected Object determineCurrentLookupKey() {
		//資料令牌類,獲取當前繫結的令牌,更具令牌資訊返回一個String的key值,
		//對應spring配置檔案中,某個資料庫連線的key
		String token = SurveyToken.getCurrentToken();
		if(token != null){
			//資料來源的key==token
			//解除繫結,就這裡解除,以保證其他連結的資訊正確
			SurveyToken.unbindToken();
			return token;
		}
		return null;
	}
}


2,定義資料庫令牌生成器,使用者繫結令牌

package cn.test.datasource;
/**
 * 調查令牌,繫結到當前的執行緒,傳播到資料來源路由器.進行分庫判斷
 */
public class SurveyToken {
	//資料來源的key 代表每個資料庫對應的key	
	public static fnail String ds_1 = "even";
	public static fnail String ds_2 = "odd" ;
	
	private static ThreadLocal<SurveyToken> t = new ThreadLocal<SurveyToken>();

	/**
	 * 將令牌物件繫結到當前執行緒
	 */
	public static void bindingToken(String token){
		t.set(token);
	}
	
	/**
	 * 從當前執行緒取得繫結的令牌物件
	 */
	public static String getCurrentToken(){
		return t.get() ;
	}
	
	/**
	 * 解除令牌的繫結
	 */
	public static void unbindToken(){
		t.remove() ;
	}
}




3,action中決定使用那個資料庫,繫結令牌。

public class TestAction{
	//測試資料來源
	public String testDS_1(){

		//繫結令牌到當前執行緒
		//設定令牌:ds_1 = "even"; 代表當前資料將存到"even"對應的資料庫中
		token.setCurrentSurvey(token.ds_1);
		SurveyToken.bindingToken(SurveyToken.ds_1);
		
		//執行service操作,儲存資料到當前設定的庫中
		service.save(User u);
	}
	//測試資料來源
	public String testDS_2(){
		//繫結令牌到當前執行緒
		//設定令牌:ds_2 = "odd"; 代表當前資料將存到"odd"對應的資料庫中
		SurveyToken.bindingToken(SurveyToken.ds_2);
		
		//執行service操作,儲存資料到當前設定的庫中
		service.save(User u);
	}
}


4,spring檔案的配置

<?xml version="1.0"?>
<beans>
	<!-- 資料來源(主庫) -->
	<bean id="dataSource_main" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driverclass}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />

		<property name="maxPoolSize" value="${c3p0.pool.size.max}" />
		<property name="minPoolSize" value="${c3p0.pool.size.min}" />
		<property name="initialPoolSize" value="${c3p0.pool.size.ini}" />
		<property name="acquireIncrement" value="${c3p0.pool.size.increment}" />
	</bean>

	<!-- 資料來源(從庫)  id與主庫不一樣,連線與主庫不一樣,其他資訊通過parent="dataSource_main"繼承主庫的資訊-->
	<bean id="dataSource_1" parent="dataSource_main">
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ds_2" />
	</bean>

	<!-- 資料來源路由器 -->
	<bean id="dataSource_router"
		class="cn.test.datasource;.SurveyparkDataSourceRouter">
		<property name="targetDataSources">
			<map>
				<!-- 程式中資料庫路由器SurveyparkDataSourceRouter.determineCurrentLookupKey()方法,
					返回的key值對應這裡的key ,代表一個數據源,
					所以程式中的令牌繫結的令牌必須按照這裡定義的key返回
				-->
				<entry key="odd" value-ref="dataSource_main" />
				<entry key="even" value-ref="dataSource_1" />
			</map>
		</property>
		<!-- 預設的庫,如果程式中沒有繫結令牌,則預設連線此資料來源,將資訊儲存到此庫中 -->
		<property name="defaultTargetDataSource" ref="dataSource_main" />
	</bean>

	<!-- 本地會話工廠bean,spring整合hibernate的核心入口 -->
	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<!-- 注入資料來源   這裡注入資料庫路由器,由路由器決定具體的連線那個庫-->
		<property name="dataSource" ref="dataSource_router" />
	</bean>
	
	<!-- 事務配置  -->

</beans>