Druid 多数据场景下如何进行选择?
druid的多个数据源场景下,提供了三种数据源选择器,分别是按照名字、随机、粘性随机数据源选择器。
数据源选择器DataSourceSelector
// 数据源选择器DataSourceSelector
public interface DataSourceSelector {
/**
* Return a DataSource according to the implemention.
* 返回数据源
*/
DataSource get();
/**
* Set the target DataSource name to return.
* Wether to use this or not, it's decided by the implemention.
* 设置targer为数据源的名称,具体是否使用该属性由实现层去决定
*/
void setTarget(String name);
/**
* Return the name of this DataSourceSelector.
* e.g. byName
* 返回数据源选择器的名称
*/
String getName();
/**
* Init the DataSourceSelector before use it.
* 初始化数据源选择器
*/
void init();
/**
* Destroy the DataSourceSelector, maybe interrupt the Thread.
* 销毁数据源选择器
*/
void destroy();
}
NamedDataSourceSelector
// 根据名称选择数据源
// 类说明:使用方通过ThreadLocal传递需要使用的数据源的name来获取对应的DataSource。如果使用方有传递这个名称,则按照这个名来从数据源map(这个是构造时入参HighAvailableDataSource自带的)中取出来。如果没有这个名称,则会取默认的“default”,然后再取数据源。
// 适用场景:主从数据源。通过提前设置数据源名称(高可用数据源设置方法),做到数据源的切换。或者固定的读写分离数据源,或者指定不同的业务适用不同的数据源
public class NamedDataSourceSelector implements DataSourceSelector {
// 名称选择器数据源的构造方法。传入 HighAvailableDataSource
public NamedDataSourceSelector(HighAvailableDataSource highAvailableDataSource) {
this.highAvailableDataSource = highAvailableDataSource;
}
@Override
public void init() {
}
@Override
public void destroy() {
}
......
//获取数据源
@Override
public DataSource get() {
// highAvailableDataSource包含多个数据源对象的数据源类
// highAvailableDataSource属性private Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<String, DataSource>();封装了多个数据源
if (highAvailableDataSource == null) {
return null;
}
// 获取highAvailableDataSource数据源集合
Map<String, DataSource> dataSourceMap = highAvailableDataSource.getAvailableDataSourceMap();
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
return null;
}
if (dataSourceMap.size() == 1) {
// 只有一个数据源直接返回
for (DataSource v : dataSourceMap.values()) {
return v;
}
}
//获取数据源的name
String name = getTarget();
if (name == null) {
// 名称为空,取默认数据源名称,根据名称获取数据源
if (dataSourceMap.get(getDefaultName()) != null) {
return dataSourceMap.get(getDefaultName());
}
} else {
// 根据名称获取数据源
return dataSourceMap.get(name);
}
return null;
}
@Override
public void setTarget(String name) {
targetDataSourceName.set(name);
}
......
}
HighAvailableDataSource
//包含多个数据源对象的数据源类
//类说明:三个Selector的构造入参。它实现了javax.sql.DataSource。它的实例化工作在getConnection()方法,调用的时候,会初始化一个数据源集合(初始化逻辑通过DataSourceCreator),然后初始化数据源选取器,可以设置选取器类型,如果没有设置,默认使用随机类型。初始化完毕后,使用数据源选取器获取数据源,然后返回该数据源的一个链接(这个是正常的jdbc获取链接的思路)。
public class HighAvailableDataSource extends WrapperAdapter implements DataSource {
......
// init操作
public void init() {
if (inited) {
return;
}
synchronized (this) {
// 同步代码块
if (inited) {
// double check
return;
}
// 数据源map为空的时候
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
poolUpdater.setIntervalSeconds(poolPurgeIntervalSeconds);
poolUpdater.setAllowEmptyPool(allowEmptyPoolWhenUpdate);
// 池更新器PoolUpdater初始化 具体代码解析见下面
poolUpdater.init();
// 节点集合 具体里面的实现采用的是观察者模式 增加observer
createNodeMap();
}
if (selector == null) {
// 数据源选择器为空的场景下 默认是随机的数据源选择器
setSelector(DataSourceSelectorEnum.RANDOM.getName());
}
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
LOG.warn("There is NO DataSource available!!! Please check your configuration.");
}
// 初始化完成
inited = true;
}
}
public void close() {
destroy();
}
// 销毁数据源选择器
public void destroy() {
if (nodeListener != null) {
// 节点监听都注销 销毁监听的方式支持 FileNodeListener文件节点、ZookeeperNodeListener节点
nodeListener.destroy();
}
if (poolUpdater != null) {
// 池更新器销毁
poolUpdater.destroy();
}
if (selector != null) {
// 选择器销毁
selector.destroy();
}
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
return;
}
for (DataSource dataSource : dataSourceMap.values()) {
if (dataSource instanceof DruidDataSource) {
// 关闭所有的数据源
((DruidDataSource) dataSource).close();
}
}
}
......
// 获取连接
@Override
public Connection getConnection() throws SQLException {
// 初始化
init();
// 从选择器中获取数据源
DataSource dataSource = selector.get();
if (dataSource == null) {
LOG.warn("Can NOT obtain DataSource, return null.");
return null;
}
// 获取数据源连接
return dataSource.getConnection();
}
......
private void createNodeMap() {
if (nodeListener == null) {
// 兼容老的版本
// 创建一个FileNodeListener监听数据源的文件.
FileNodeListener listener = new FileNodeListener();
listener.setFile(dataSourceFile);
listener.setPrefix(propertyPrefix);
nodeListener = listener;
}
nodeListener.setObserver(poolUpdater);
nodeListener.init();
nodeListener.update(); // Do update in the current Thread at the startup
}
......
}
PoolUpdater
// 数据源连接池更新器的初始化操作
public void init() {
if (inited) {
return;
}
synchronized (this) {
// double check
if (inited) {
return;
}
if (intervalSeconds < 10) {
//间隔秒时间小于10s 异常提示
LOG.warn("CAUTION: Purge interval has been set to " + intervalSeconds
+ ". This value should NOT be too small.");
}
if (intervalSeconds <= 0) {
// 设置默认值 60s
intervalSeconds = DEFAULT_INTERVAL;
}
// 新建一个线程池
executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
LOG.debug("Purging the DataSource Pool every " + intervalSeconds + "s.");
try {
//异步线程执行删除数据源
removeDataSources();
} catch (Exception e) {
LOG.error("Exception occurred while removing DataSources.", e);
}
}
}, intervalSeconds, intervalSeconds, TimeUnit.SECONDS);
}
}
// 删除不使用的数据源
public void removeDataSources() {
if (nodesToDel == null || nodesToDel.isEmpty()) {
return;
}
try {
//加锁
lock.lock();
//获取数据源组
Map<String, DataSource> map = highAvailableDataSource.getDataSourceMap();
//需要删除的节点
Set<String> copySet = new HashSet<String>(nodesToDel);
for (String nodeName : copySet) {
LOG.info("Start removing Node " + nodeName + ".");
if (!map.containsKey(nodeName)) {
LOG.info("Node " + nodeName + " is NOT existed in the map.");
// map中不包含需要删除的节点。则nodesToDel中删除节点,并从highAvailableDataSource.BlackList删除该节点
cancelBlacklistNode(nodeName);
continue;
}
//获取数据源
DataSource ds = map.get(nodeName);
if (ds instanceof DruidDataSource) {
//如果是 druid的数据源
DruidDataSource dds = (DruidDataSource) ds;
// 获取当前数据源的激活数量
int activeCount = dds.getActiveCount(); // CAUTION, activeCount MAYBE changed!
if (activeCount > 0) {
//如果有激活数量不删除
LOG.warn("Node " + nodeName + " is still running [activeCount=" + activeCount
+ "], try next time.");
continue;
} else {
LOG.info("Close Node " + nodeName + " and remove it.");
try {
//当前数据源中没有使用的数量。关闭数据源
dds.close();
} catch (Exception e) {
LOG.error("Exception occurred while closing Node " + nodeName
+ ", just remove it.", e);
}
}
}
//不是DruidDataSource的话 从map节点中删除,从highAvailableDataSource.BlackList删除该节点
map.remove(nodeName); // Remove the node directly if it is NOT a DruidDataSource.
cancelBlacklistNode(nodeName);
}
} catch (Exception e) {
LOG.error("Exception occurred while removing DataSources.", e);
} finally {
//解锁
lock.unlock();
}
}
DataSourceCreator
// 动态创建DruidDataSource 的工具类。
// 上面有提到过,HighAvailableDataSource的底层初始化数据源集合就是使用该类。通过构造进来配置文件,以及配置参数的前缀,做解析,紧接着创建数据源集合。具体创建过程就是拿解析到的name、url、username、psw,直接构建DruidDataSource,除了这几个基本参数外,其他参数使用高可用数据源实例自己的默认值(外面也可以设置)
public class DataSourceCreator {
//创建数据源
public static DruidDataSource create(String name, String url, String username, String password,
HighAvailableDataSource haDataSource) throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setName(name + "-" + System.identityHashCode(dataSource));
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
//设置属性
......
//初始化
dataSource.init();
return dataSource;
}
}
RandomDataSourceSelector
// 随机的选择数据源 RandomDataSourceSelector
// 类说明:使用随机数随机选取数据源。从数据源map(同上解释)中,根据总大小使用随机函数(Random.nextInt())选取一个数据源返回。
// 适用场景:多个从数据源,之前无差别
public class RandomDataSourceSelector implements DataSourceSelector {
.......
// init
@Override
public void init() {
if (highAvailableDataSource == null) {
LOG.warn("highAvailableDataSource is NULL!");
return;
}
if (!highAvailableDataSource.isTestOnBorrow() && !highAvailableDataSource.isTestOnReturn()) {
// 加载配置。初始化线程s
loadProperties();
initThreads();
} else {
LOG.info("testOnBorrow or testOnReturn has been set to true, ignore validateThread");
}
}
// 销毁
@Override
public void destroy() {
if (runningValidateThread != null) {
// 中断正在运行的线程
runningValidateThread.interrupt();
validateThread.setSelector(null);
}
if (runningRecoverThread != null) {
// 中断正在运行的线程
runningRecoverThread.interrupt();
recoverThread.setSelector(null);
}
}
// 获取数据源
@Override
public DataSource get() {
Map<String, DataSource> dataSourceMap = getDataSourceMap();
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
return null;
}
// 删除黑名数据源
Collection<DataSource> targetDataSourceSet = removeBlackList(dataSourceMap);
// 移除忙绿的数据源
removeBusyDataSource(targetDataSourceSet);
// 随机获取数据源
DataSource dataSource = getRandomDataSource(targetDataSourceSet);
return dataSource;
}
......
// 获取所有的数据源
public Map<String, DataSource> getFullDataSourceMap() {
if (highAvailableDataSource != null) {
return highAvailableDataSource.getDataSourceMap();
}
return new HashMap<String, DataSource>();
}
// 获取所有可用数据源
public Map<String, DataSource> getDataSourceMap() {
if (highAvailableDataSource != null) {
return highAvailableDataSource.getAvailableDataSourceMap();
}
return new HashMap<String, DataSource>();
}
// 获取黑名单
public List<DataSource> getBlacklist() {
return blacklist;
}
//是否包含在黑名单中
public boolean containInBlacklist(DataSource dataSource) {
return dataSource != null && blacklist.contains(dataSource);
}
// 加入黑名单
public void addBlacklist(DataSource dataSource) {
if (dataSource != null && !blacklist.contains(dataSource)) {
// 数据源不为空,且黑名单中没有该数据源
blacklist.add(dataSource);
if (dataSource instanceof DruidDataSource) {
// 测试属性
((DruidDataSource) dataSource).setTestOnReturn(true);
}
}
}
// 移除黑名单
public void removeBlacklist(DataSource dataSource) {
if (containInBlacklist(dataSource)) {
//在黑名单中移除
blacklist.remove(dataSource);
if (dataSource instanceof DruidDataSource) {
((DruidDataSource) dataSource).setTestOnReturn(highAvailableDataSource.isTestOnReturn());
}
}
}
// 加载配置
private void loadProperties() {
//检查间隔
checkingIntervalSeconds = loadInteger(PROP_CHECKING_INTERVAL, checkingIntervalSeconds);
//恢复间隔
recoveryIntervalSeconds = loadInteger(PROP_RECOVERY_INTERVAL, recoveryIntervalSeconds);
// 校验休眠时间
validationSleepSeconds = loadInteger(PROP_VALIDATION_SLEEP, validationSleepSeconds);
// 黑名单阈值
blacklistThreshold = loadInteger(PROP_BLACKLIST_THRESHOLD, blacklistThreshold);
}
......
// 初始化线程
private void initThreads() {
if (validateThread == null) {
// 校验线程不为空
validateThread = new RandomDataSourceValidateThread(this);
validateThread.setCheckingIntervalSeconds(checkingIntervalSeconds);
validateThread.setValidationSleepSeconds(validationSleepSeconds);
validateThread.setBlacklistThreshold(blacklistThreshold);
} else {
validateThread.setSelector(this);
}
if (runningValidateThread != null) {
// 中断正在进行的校验线程
runningValidateThread.interrupt();
}
// 随机校验线程
runningValidateThread = new Thread(validateThread, "RandomDataSourceSelector-validate-thread");
runningValidateThread.start();
//恢复线程
if (recoverThread == null) {
recoverThread = new RandomDataSourceRecoverThread(this);
recoverThread.setRecoverIntervalSeconds(recoveryIntervalSeconds);
recoverThread.setValidationSleepSeconds(validationSleepSeconds);
} else {
recoverThread.setSelector(this);
}
if (runningRecoverThread != null) {
//中断正在运行的恢复线程
runningRecoverThread.interrupt();
}
//恢复线程
runningRecoverThread = new Thread(recoverThread, "RandomDataSourceSelector-recover-thread");
runningRecoverThread.start();
}
// 删除黑名单
private Collection<DataSource> removeBlackList(Map<String, DataSource> dataSourceMap) {
Collection<DataSource> dataSourceSet;
if (blacklist == null || blacklist.isEmpty() || blacklist.size() >= dataSourceMap.size()) {
dataSourceSet = dataSourceMap.values();
} else {
dataSourceSet = new HashSet<DataSource>(dataSourceMap.values());
for (DataSource b : blacklist) {
dataSourceSet.remove(b);
}
LOG.info(blacklist.size() + " Blacklist DataSource removed, return "
+ dataSourceSet.size() + " DataSource(s).");
}
return dataSourceSet;
}
// 获取高可用的数据源。(代码命名误人子弟)
private void removeBusyDataSource(Collection<DataSource> dataSourceSet) {
// 高可用线程
Collection<DataSource> busyDataSourceSet = new HashSet<DataSource>();
for (DataSource ds : dataSourceSet) {
if (ds instanceof DruidDataSource && ((DruidDataSource) ds).getPoolingCount() <= 0) {
// 数据源的池计数 < =0的时候说明该数据源已经异常了,源码中好多判断PoolingCount为0的时候会发送emptySignal给CreateThread信号量去创建线程
busyDataSourceSet.add(ds);
}
}
if (!busyDataSourceSet.isEmpty() && busyDataSourceSet.size() < dataSourceSet.size()) {
LOG.info("Busy DataSouces: " + busyDataSourceSet.size() + "/" + dataSourceSet.size());
for (DataSource ds : busyDataSourceSet) {
//数据源集合中剔除忙绿的数据源
dataSourceSet.remove(ds);
}
}
}
// 随机获取数据源
private DataSource getRandomDataSource(Collection<DataSource> dataSourceSet) {
DataSource[] dataSources = dataSourceSet.toArray(new DataSource[] {});
if (dataSources != null && dataSources.length > 0) {
// 随机数获取
return dataSources[random.nextInt(dataSourceSet.size())];
}
return null;
}
......
}
StickyRandomDataSourceSelector
// 粘性随机选择数据源 StickyRandomDataSourceSelector。基于 RandomDataSourceSelector 的扩展选择器,它可以在一段时间内将数据源粘贴到线程。
// 类说明:粘性随机选取数据源。这是一个比较特殊的选取器。它继承随机选取数据源,也就是说如果是第一次进来,就是走的随机选取。在选取后,它会把这个数据源跟绑定到当前线程(ThreadLocal),会持续一段时间(这个时间可以配置)
// 适用场景:避免同一个线程内多个数据库操作在不停的切换数据源
public class StickyRandomDataSourceSelector extends RandomDataSourceSelector {
......
private ThreadLocal<StickyDataSourceHolder> holders = new ThreadLocal<StickyDataSourceHolder>();
//过期时间
private int expireSeconds = 5;
......
@Override
public DataSource get() {
StickyDataSourceHolder holder = holders.get();
// 有有效的数据源持有者
if (holder != null && isAvailable(holder)) {
// 获取数据源
LOG.debug("Return the sticky DataSource " + holder.getDataSource().toString() + " directly.");
return holder.getDataSource();
}
LOG.debug("Return a random DataSource.");
// 随机获取一个
DataSource dataSource = super.get();
holder = new StickyDataSourceHolder(dataSource);
holders.remove();
// 当前的holder放在当前线程内部变量中
holders.set(holder);
return dataSource;
}
private boolean isAvailable(StickyDataSourceHolder holder) {
// 通过校验并且是有效的持有者
boolean flag = isValid(holder) && !isExpired(holder);
if (flag && holder.getDataSource() instanceof DruidDataSource) {
// 当前数据源持有者的PoolingCount数量是否>0
flag = ((DruidDataSource) holder.getDataSource()).getPoolingCount() > 0;
}
return flag;
}
private boolean isValid(StickyDataSourceHolder holder) {
// 当前持有者是有效的 并且当前数据源不在黑名单中
boolean flag = holder.isValid() && !getBlacklist().contains(holder.getDataSource());
if (!(holder.getDataSource() instanceof DruidDataSource) || !flag) {
return flag;
}
// 获取数据源
DruidDataSource dataSource = (DruidDataSource) holder.getDataSource();
// 当前数据源的激活数量小于最大数量
return flag && dataSource.getActiveCount() < dataSource.getMaxActive();
}
// 是否过期
private boolean isExpired(StickyDataSourceHolder holder) {
return System.currentTimeMillis() - holder.getRetrievingTime() > expireSeconds * 1000;
}
......
}
总结
今天针对druid的数据源在多个数据源场景下如何进行选择的源代码学习,目前druid数据源选择器提供三种方案,按照名称、随机、粘性随机的方案。其中遗留的疑惑点关于随机选择器中的删除忙碌的数据源的代码中的判断“数据源的池计数 < =0的时候,加入忙碌的数据源中”这块业务不太能理解,放个TODO,后面补充。
已解决:随机选择器中的removeBusyDataSource是获取高可用数据源,数据源的池计数 < =0的时候说明该数据源已经异常了,源码中好多判断PoolingCount为0的时候会发送emptySignal给CreateThread信号量去创建线程