1、数据库的主从分离原理
现在大型的网站服务,在数据库层面大多采用读写分离技术,就是一个数据库负责数据的创建、更新和删除以及实时查询,这个数据库成为主数据库;另外的数据库主要负责非实时数据的查询,称为从数据库。因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据通常耗时比较长,占用数据库服务器的CPU较多,影响用户体验。把查询从主库中抽取出来,采用多个从库,使用负载均衡,可以减轻每个从库的查询压力。
2、数据库主从分离的实现
在开发中,怎么实现数据库主从的读写分离呢?常见的方式有:定义2个数据库连接,一个是MasterDataSource,另一个是SlaveDataSource。更新数据时读取MasterDataSource,查询数据时读取SlaveDataSource。那么,问题来了,以往在spring和hibernate或者Mybatis框架中总是配置一个数据源,因而sessionFactory的dataSource属性总是指向这个数据源并且恒定不变,所有DAO在使用sessionFactory的时候都是通过这个数据源访问数据库。但是现在怎样很方便的切换多个数据源呢?
文章如何在spring框架中解决多数据源的问题 给出了一个比较好的解决方案。
1)掌握配置的前提知识
Spring对数据库的连接进行了封装,使得在利用spring配置数据源的时候,可以很方便的进行。
spring 的AbstractRoutingDataSource 类。
public abstract AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
}
2)所以核心思想就是在选择数据库链接的时候,先选择出数据源。可以对AbstractRoutingDataSource进行扩展,在determineCurrentLookupKey中选择合适的数据源。结合思想有:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getType();
}
}
DataSourceContextHolder由线程绑定,
public class DataSourceContextHolder {
public static final String DATA_SOURCE_1 = "ds1";
public static final String DATA_SOURCE_2 = "ds2";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
// 设置数据源类型
public static void setType(String type) {
contextHolder.set(type);
}
// 获取数据源类型
public static String getType() {
return (contextHolder.get());
}
// 清除数据源类型
public static void clearType() {
contextHolder.remove();
}
}
什么时候设置线程绑定变量呢?可以u借助AOP,在执行方法之前设置好holder。
@Component
@Aspect
public class DynamicDataSourceAspect {
@Pointcut("execution (* com.chen.test.mapper.ds1.*.*(..))")
public void dsMethodPointcut() {}
@Pointcut("execution (* com.chen.test.mapper.ds2.*.*(..))")
public void rdsMethodPointcut() {}
@Before("dsMethodPointcut()")
public void switchReadDataSource(){
DataSourceContextHolder.setType(DataSourceContextHolder.DATA_SOURCE_DS1);
}
@Before("rdsMethodPointcut()")
public void switchWriteDataSource(){
DataSourceContextHolder.setType(DataSourceContextHolder.DATA_SOURCE_DS2);
}
}
但是,数据源i涉及到事务问题,所以需要在选择数据源之后,事务才被生效。可以通过aop的顺序实现这个。
在Aspect设置顺序为transaction order -1;