在做项目时,当数据量很大需要将数据存在不同的数据库或者希望将不同类型数据存入不同数据库时,就需要利用Spring进行动态的数据库切换。
其原理如下图所示:
要实现动态切换数据源,首先需要将各个数据源配置好,如下所示:
之后,我们就需要利用spring中对动态选择数据源的支持,来实现将数据写入不同的数据库。
先把定义的多个数据库bean放一放,先来看下spring中对动态选择数据源的支持。
在spring中有一个抽象类AbstractRoutingDataSource类,通过这个类可以实现动态选择数据源。来看下这个类的成员变量。
private Map
targetDataSources中保存了key和数据库连接的映射关系,defaultTargetDataSource表示默认的链接,resolvedDataSources这个数据结构是通过targetDataSources构建而来,存储的结构也是数据库标识和数据源的映射关系。
下面需要继承AbstractRoutingDataSource类,实现我们自己的数据库选择逻辑DataSourceSwitcher类,先上代码:
public class DataSourceSwitcher extends AbstractRoutingDataSource{
private static final Logger LOGGER = LoggerFactory.getLogger("INTERACTIVE_LOGGER");
private static final ThreadLocal dataSourceKey = new ThreadLocal();
public static void clearDataSourceType() {
LOGGER.debug("thread:{},remove,dataSource:{}",Thread.currentThread().getName());
dataSourceKey.remove();
}
@Override
protected Object determineCurrentLookupKey() {
String s = dataSourceKey.get();
LOGGER.debug("thread:{},determine,dataSource:{}",Thread.currentThread().getName(),s);
return s;
}
public static void setDataSourceKey(String dataSource) {
LOGGER.debug("thread:{},set,dataSource:{}",Thread.currentThread().getName(),dataSource);
dataSourceKey.set(dataSource);
}
}
第5行,threadLocal的成员变量dataSource(由于不同的请求所需要的数据源可能不一样),用于存储数据源标识。
第8行,清除数据源操作.
第14行,该方法决定了需要使用哪个数据库,该方法是抽象方法,必须由我们实现,那么现在来看下这个方法是如何使用的。
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();//这里获取数据库标识
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 + "]");
}
return dataSource;
}
所以我们需要在determineCurrentLookupKey方法中返回数据库标识即可。
第20行,设置数据源方法。
现在把我们之前的数据库配置和DataSourceSwitcher进行合并,我在数据库的xml配置上添加如下配置:
可以看到,这里对targetDataSources进行了初始化,ds1对应了数据源childDataSource1;ds2对应了数据源childDataSource2。
使用的话只要调用DataSourceSwitcher.setDataSourceKey(“ds1”),即将数据源切换到了childDataSource1。
如果每次执行方法都要设置一下数据源实在是件很麻烦的事情,另外我们需要对某个key进行hash后选择数据库,这块也没有实现。现在借助spring切面的功能,可以解决这两个问题。
上面我们只是实现了 master-slave 数据源的选择。如果有多台 master 或者有多台 slave。多台master组成一个HA,要实现当其中一台master挂了是,自动切换到另一台master,这个功能可以使用LVS/Keepalived来实现,也可以通过进一步扩展ThreadLocalRountingDataSource来实现,可以另外加一个线程专门来每个一秒来测试mysql是否正常来实现。同样对于多台slave之间要实现负载均衡,同时当一台slave挂了时,要实现将其从负载均衡中去除掉,这个功能既可以使用LVS/Keepalived来实现,同样也可以通过近一步扩展ThreadLocalRountingDataSource来实现。