随着业务不断发展, 数据量变得越来越大, 数据库的压力也与日俱增, 这时我们会考虑SQL优化, 分表分库, 读写分离这些策略来提高数据库的性能. 本篇博客这里只介绍读写分离策略, 什么是读写分离策略呢?
比如我们把数据库分为一个Master库, n个Slave库, Master库用来处理写操作和实时性高的读操作, Slave库用来处理实时性不高的读操作, 这样就有效地减轻数据库压力.
使用Spring实现读写分离
技术
- Spring
- Mybatis
- MySQL
方案
以前我们都是直接配置c3p0, druid等这些第三方数据源, 现在我们需要使用自定义的数据源.
(1) 以前的配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
(2) 现在的配置
<!--注册Master数据源-->
<bean id="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--注册Slave1数据源-->
<bean id="dataSourceSlave1" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--注册Slave2数据源-->
<bean id="dataSourceSlave2" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.tyshawn.muti_datasource.DynamicDataSource">
<property name="defaultTargetDataSource" ref="dataSourceMaster"/>
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="dataSourceMaster"/>
<entry key="slave1" value-ref="dataSourceSlave1"/>
<entry key="slave2" value-ref="dataSourceSlave2"/>
</map>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
DynamicDataSource类就是我们自己定义的数据源, 它需要继承AbstractRoutingDataSource类, 重写其中一部分的方法. 我们先来看下AbstractRoutingDataSource类的内部实现.
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
public AbstractRoutingDataSource() {
}
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
Iterator var1 = this.targetDataSources.entrySet().iterator();
while(var1.hasNext()) {
Entry<Object, Object> entry = (Entry)var1.next();
Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
protected abstract Object determineCurrentLookupKey();
}
上面是AbstractRoutingDataSource类的核心代码, 为了便于阅读, 我删除了一部分代码. 在配置文件中我们配置了defaultTargetDataSource和targetDataSources的初始值. AbstractRoutingDataSource的实现类在初始化时会执行afterPropertiesSet()方法, 其功能是将defaultTargetDataSource转化为resolvedDefaultDataSource, 将targetDataSources转化为resolvedDataSources. 请求每一次调用数据源时都会执行determineTargetDataSource()方法, 它决定访问的数据源是哪个.
determineTargetDataSource()方法是如何决定访问哪个数据源的呢? 注意下面这两行代码
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
通过调用determineCurrentLookupKey()方法来获取key值, 然后根据key值从resolvedDataSources中获取数据源. 我们要做的就是重写determineCurrent