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,分表:适合针对单个的表数据量比较大的情况,分成多张表保存,比如系统日志
例如:
系统日志表以这样的形式保存,每个月一张表。
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>