mirror of
https://github.com/alibaba/druid.git
synced 2023-03-04 19:17:16 +00:00
Merge pull request #3169 from digitalsonic/master
优化 HighAvailableDataSource 逻辑
This commit is contained in:
@@ -45,8 +45,12 @@ public class DataSourceCreator {
|
||||
private List<String> nameList = new ArrayList<String>();
|
||||
|
||||
public DataSourceCreator(String file) {
|
||||
this(file, "");
|
||||
}
|
||||
|
||||
public DataSourceCreator(String file, String propertyPrefix) {
|
||||
loadProperties(file);
|
||||
loadNameList();
|
||||
loadNameList(propertyPrefix);
|
||||
}
|
||||
|
||||
public Map<String, DataSource> createMap(HighAvailableDataSource haDataSource) throws SQLException {
|
||||
@@ -62,8 +66,12 @@ public class DataSourceCreator {
|
||||
String username = properties.getProperty(n + ".username");
|
||||
String password = properties.getProperty(n + ".password");
|
||||
LOG.info("Creating " + n + " with url[" + url + "] and username[" + username + "].");
|
||||
DruidDataSource dataSource = create(n, url, username, password, haDataSource);
|
||||
map.put(n, dataSource);
|
||||
try {
|
||||
DruidDataSource dataSource = create(n, url, username, password, haDataSource);
|
||||
map.put(n, dataSource);
|
||||
} catch(Exception e) {
|
||||
LOG.error("Can NOT create DruidDataSource for " + n, e);
|
||||
}
|
||||
}
|
||||
LOG.info(map.size() + " DruidDataSource(s) created. ");
|
||||
return map;
|
||||
@@ -120,10 +128,14 @@ public class DataSourceCreator {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private void loadNameList() {
|
||||
private void loadNameList(String propertyPrefix) {
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (String n : properties.stringPropertyNames()) {
|
||||
if (n.contains(".url")) {
|
||||
if (propertyPrefix != null && !propertyPrefix.isEmpty()
|
||||
&& !n.startsWith(propertyPrefix)) {
|
||||
continue;
|
||||
}
|
||||
if (n.endsWith(".url")) {
|
||||
names.add(n.split("\\.url")[0]);
|
||||
}
|
||||
}
|
||||
@@ -168,4 +180,8 @@ public class DataSourceCreator {
|
||||
LOG.warn("File " + file + " can't be loaded!");
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getNameList() {
|
||||
return nameList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ import com.alibaba.druid.pool.DruidAbstractDataSource;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.alibaba.druid.pool.WrapperAdapter;
|
||||
import com.alibaba.druid.pool.ha.selector.DataSourceSelector;
|
||||
import com.alibaba.druid.pool.ha.selector.DataSourceSelectorEnum;
|
||||
import com.alibaba.druid.pool.ha.selector.DataSourceSelectorFactory;
|
||||
import com.alibaba.druid.pool.ha.selector.RandomDataSourceSelector;
|
||||
import com.alibaba.druid.support.logging.Log;
|
||||
import com.alibaba.druid.support.logging.LogFactory;
|
||||
|
||||
@@ -87,10 +87,11 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
|
||||
// Properties copied from DruidAbstractDataSource END
|
||||
|
||||
private Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<String, DataSource>();
|
||||
private DataSourceSelector selector = new RandomDataSourceSelector(this);
|
||||
private DataSourceSelector selector;
|
||||
private String dataSourceFile = DEFAULT_DATA_SOURCE_FILE;
|
||||
private String propertyPrefix = "";
|
||||
|
||||
private boolean inited = false;
|
||||
private volatile boolean inited = false;
|
||||
|
||||
public void init() throws SQLException {
|
||||
if (inited) {
|
||||
@@ -101,10 +102,10 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
|
||||
return;
|
||||
}
|
||||
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
|
||||
dataSourceMap = new DataSourceCreator(dataSourceFile).createMap(this);
|
||||
dataSourceMap = new DataSourceCreator(dataSourceFile, propertyPrefix).createMap(this);
|
||||
}
|
||||
if (selector == null) {
|
||||
selector = new RandomDataSourceSelector(this);
|
||||
setSelector(DataSourceSelectorEnum.RANDOM.getName());
|
||||
}
|
||||
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
|
||||
LOG.warn("There is NO DataSource available!!! Please check your configuration.");
|
||||
@@ -147,6 +148,14 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
|
||||
this.dataSourceFile = dataSourceFile;
|
||||
}
|
||||
|
||||
public String getPropertyPrefix() {
|
||||
return propertyPrefix;
|
||||
}
|
||||
|
||||
public void setPropertyPrefix(String propertyPrefix) {
|
||||
this.propertyPrefix = propertyPrefix;
|
||||
}
|
||||
|
||||
public void setDataSourceMap(Map<String, DataSource> dataSourceMap) {
|
||||
if (dataSourceMap != null) {
|
||||
this.dataSourceMap = dataSourceMap;
|
||||
@@ -158,11 +167,9 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
|
||||
}
|
||||
|
||||
public void setSelector(String name) {
|
||||
if (name != null && this.selector != null && this.selector.isSame(name)) {
|
||||
return;
|
||||
}
|
||||
DataSourceSelector selector = DataSourceSelectorFactory.getSelector(name, this);
|
||||
if (selector != null) {
|
||||
selector.init();
|
||||
setDataSourceSelector(selector);
|
||||
}
|
||||
}
|
||||
@@ -181,7 +188,7 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
|
||||
|
||||
@Override
|
||||
public Connection getConnection(String username, String password) throws SQLException {
|
||||
throw new UnsupportedOperationException("Not supported by DruidDataSource");
|
||||
throw new UnsupportedOperationException("Not supported by HighAvailableDataSource.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,7 +228,15 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
|
||||
}
|
||||
|
||||
public void setConnectProperties(Properties connectProperties) {
|
||||
this.connectProperties = connectProperties;
|
||||
if (connectProperties == null) {
|
||||
connectProperties = new Properties();
|
||||
}
|
||||
|
||||
if (this.connectProperties != null) {
|
||||
this.connectProperties.putAll(connectProperties);
|
||||
} else {
|
||||
this.connectProperties = connectProperties;
|
||||
}
|
||||
}
|
||||
|
||||
public int getInitialSize() {
|
||||
@@ -406,6 +421,32 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
|
||||
|
||||
public void setConnectionProperties(String connectionProperties) {
|
||||
this.connectionProperties = connectionProperties;
|
||||
|
||||
// COPIED FROM DruidAbstractDataSource.setConnectionProperties()
|
||||
|
||||
if (connectionProperties == null || connectionProperties.trim().length() == 0) {
|
||||
setConnectProperties(null);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] entries = connectionProperties.split(";");
|
||||
Properties properties = new Properties();
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
String entry = entries[i];
|
||||
if (entry.length() > 0) {
|
||||
int index = entry.indexOf('=');
|
||||
if (index > 0) {
|
||||
String name = entry.substring(0, index);
|
||||
String value = entry.substring(index + 1);
|
||||
properties.setProperty(name, value);
|
||||
} else {
|
||||
// no value is empty string which is how java.util.Properties works
|
||||
properties.setProperty(entry, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setConnectProperties(properties);
|
||||
}
|
||||
|
||||
public String getFilters() {
|
||||
|
||||
@@ -26,6 +26,6 @@ import javax.sql.DataSource;
|
||||
public interface DataSourceSelector {
|
||||
DataSource get();
|
||||
void setTarget(String name);
|
||||
boolean isSame(String name);
|
||||
String getName();
|
||||
void init();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.alibaba.druid.pool.ha.selector;
|
||||
|
||||
import com.alibaba.druid.pool.ha.HighAvailableDataSource;
|
||||
import com.alibaba.druid.support.logging.Log;
|
||||
import com.alibaba.druid.support.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* An enum holding the names and classes of DataSourceSelector.
|
||||
*
|
||||
* @author DigitalSonic
|
||||
*/
|
||||
public enum DataSourceSelectorEnum {
|
||||
BY_NAME("byName", NamedDataSourceSelector.class),
|
||||
RANDOM("random", RandomDataSourceSelector.class),
|
||||
STICKY_RANDOM("stickyRandom", StickyRandomDataSourceSelector.class);
|
||||
|
||||
private final static Log LOG = LogFactory.getLog(DataSourceSelectorEnum.class);
|
||||
private String name;
|
||||
private Class<? extends DataSourceSelector> clazz;
|
||||
|
||||
DataSourceSelectorEnum(String name, Class<? extends DataSourceSelector> clazz) {
|
||||
this.name = name;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the DataSourceSelector represented by this enum.
|
||||
*
|
||||
* @return null if dataSource is not given or exception occurred while creating new instance
|
||||
*/
|
||||
public DataSourceSelector newInstance(HighAvailableDataSource dataSource) {
|
||||
if (dataSource == null) {
|
||||
LOG.warn("You should provide an instance of HighAvailableDataSource!");
|
||||
return null;
|
||||
}
|
||||
|
||||
DataSourceSelector selector = null;
|
||||
try {
|
||||
selector = clazz.getDeclaredConstructor(HighAvailableDataSource.class).newInstance(dataSource);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Can not create new instance of " + clazz.getName(), e);
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Class<? extends DataSourceSelector> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,16 @@ import com.alibaba.druid.pool.ha.HighAvailableDataSource;
|
||||
* @author DigitalSonic
|
||||
*/
|
||||
public class DataSourceSelectorFactory {
|
||||
/**
|
||||
* Get a new instance of the given selector name.
|
||||
*
|
||||
* @return null if the given name do not represent a DataSourceSelector
|
||||
*/
|
||||
public static DataSourceSelector getSelector(String name, HighAvailableDataSource highAvailableDataSource) {
|
||||
if ("random".equalsIgnoreCase(name)) {
|
||||
return new RandomDataSourceSelector(highAvailableDataSource);
|
||||
} else if ("byName".equalsIgnoreCase(name)) {
|
||||
return new NamedDataSourceSelector(highAvailableDataSource);
|
||||
for (DataSourceSelectorEnum e : DataSourceSelectorEnum.values()) {
|
||||
if (e.getName().equalsIgnoreCase(name)) {
|
||||
return e.newInstance(highAvailableDataSource);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -37,13 +37,12 @@ public class NamedDataSourceSelector implements DataSourceSelector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(String name) {
|
||||
return getName().equalsIgnoreCase(name);
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "byName";
|
||||
return DataSourceSelectorEnum.BY_NAME.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+26
-4
@@ -32,7 +32,8 @@ import com.alibaba.druid.util.JdbcUtils;
|
||||
public class RandomDataSourceRecoverThread implements Runnable {
|
||||
private final static Log LOG = LogFactory.getLog(RandomDataSourceRecoverThread.class);
|
||||
private RandomDataSourceSelector selector;
|
||||
private int sleepSeconds = 30;
|
||||
private int sleepSeconds = 120;
|
||||
private int validationSleepSeconds = 0;
|
||||
|
||||
public RandomDataSourceRecoverThread(RandomDataSourceSelector selector) {
|
||||
this.selector = selector;
|
||||
@@ -62,20 +63,29 @@ public class RandomDataSourceRecoverThread implements Runnable {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = dataSource.getConnection();
|
||||
sleepBeforeValidation();
|
||||
dataSource.validateConnection(connection);
|
||||
LOG.info(dataSource.getName() + " is available now.");
|
||||
selector.removeBlacklist(dataSource);
|
||||
} catch(Exception e) {
|
||||
LOG.warn("DataSource[" + dataSource.getName() + "] is still unavailable. Exception: "
|
||||
+ e.getMessage());
|
||||
if (connection != null) {
|
||||
dataSource.discardConnection(connection);
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.close(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void sleepBeforeValidation() {
|
||||
if (validationSleepSeconds > 0) {
|
||||
try {
|
||||
LOG.debug("Sleep " + validationSleepSeconds + " second(s) before validation.");
|
||||
Thread.sleep(validationSleepSeconds * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sleep() {
|
||||
try {
|
||||
Thread.sleep(sleepSeconds * 1000);
|
||||
@@ -84,7 +94,19 @@ public class RandomDataSourceRecoverThread implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
public int getSleepSeconds() {
|
||||
return sleepSeconds;
|
||||
}
|
||||
|
||||
public void setSleepSeconds(int sleepSeconds) {
|
||||
this.sleepSeconds = sleepSeconds;
|
||||
}
|
||||
|
||||
public int getValidationSleepSeconds() {
|
||||
return validationSleepSeconds;
|
||||
}
|
||||
|
||||
public void setValidationSleepSeconds(int validationSleepSeconds) {
|
||||
this.validationSleepSeconds = validationSleepSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@@ -36,6 +37,12 @@ import com.alibaba.druid.support.logging.LogFactory;
|
||||
* @author DigitalSonic
|
||||
*/
|
||||
public class RandomDataSourceSelector implements DataSourceSelector {
|
||||
private static final String PROP_PREFIX = "druid.ha.random.";
|
||||
public static final String PROP_CHECKING_INTERVAL = PROP_PREFIX + "checkingIntervalSeconds";
|
||||
public static final String PROP_RECOVERY_INTERVAL = PROP_PREFIX + "recoveryIntervalSeconds";
|
||||
public static final String PROP_VALIDATION_SLEEP = PROP_PREFIX + "validationSleepSeconds";
|
||||
public static final String PROP_BLACKLIST_THRESHOLD = PROP_PREFIX + "blacklistThreshold";
|
||||
|
||||
private final static Log LOG = LogFactory.getLog(RandomDataSourceSelector.class);
|
||||
|
||||
private Random random = new Random();
|
||||
@@ -44,26 +51,32 @@ public class RandomDataSourceSelector implements DataSourceSelector {
|
||||
private RandomDataSourceValidateThread validateThread;
|
||||
private RandomDataSourceRecoverThread recoverThread;
|
||||
|
||||
private int checkingIntervalSeconds = 15;
|
||||
private int recoveryIntervalSeconds = 120;
|
||||
private int validationSleepSeconds = 0;
|
||||
private int blacklistThreshold = 3;
|
||||
|
||||
public RandomDataSourceSelector(HighAvailableDataSource highAvailableDataSource) {
|
||||
this.highAvailableDataSource = highAvailableDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
if (highAvailableDataSource == null) {
|
||||
LOG.warn("highAvailableDataSource is NULL!");
|
||||
return;
|
||||
}
|
||||
if (!highAvailableDataSource.isTestOnBorrow() && !highAvailableDataSource.isTestOnReturn()) {
|
||||
validateThread = new RandomDataSourceValidateThread(this);
|
||||
recoverThread = new RandomDataSourceRecoverThread(this);
|
||||
new Thread(validateThread, "RandomDataSourceSelector-validate-thread").start();
|
||||
new Thread(recoverThread, "RandomDataSourceSelector-recover-thread").start();
|
||||
loadProperties();
|
||||
initThreads();
|
||||
} else {
|
||||
LOG.info("testOnBorrow or testOnReturn has been set to true, ignore validateThread");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(String name) {
|
||||
return getName().equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "random";
|
||||
return DataSourceSelectorEnum.RANDOM.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,6 +119,10 @@ public class RandomDataSourceSelector implements DataSourceSelector {
|
||||
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);
|
||||
@@ -116,7 +133,7 @@ public class RandomDataSourceSelector implements DataSourceSelector {
|
||||
}
|
||||
|
||||
public void removeBlacklist(DataSource dataSource) {
|
||||
if (dataSource != null && blacklist.contains(dataSource)) {
|
||||
if (containInBlacklist(dataSource)) {
|
||||
blacklist.remove(dataSource);
|
||||
if (dataSource instanceof DruidDataSource) {
|
||||
((DruidDataSource) dataSource).setTestOnReturn(highAvailableDataSource.isTestOnReturn());
|
||||
@@ -124,11 +141,96 @@ public class RandomDataSourceSelector implements DataSourceSelector {
|
||||
}
|
||||
}
|
||||
|
||||
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 int loadInteger(String name, int defaultValue) {
|
||||
if (name == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
Properties properties = highAvailableDataSource.getConnectProperties();
|
||||
int value = defaultValue;
|
||||
try {
|
||||
if (properties.containsKey(name)) {
|
||||
value = Integer.parseInt(properties.getProperty(name));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Exception occurred while parsing " + name, e);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void initThreads() {
|
||||
if (validateThread == null) {
|
||||
validateThread = new RandomDataSourceValidateThread(this);
|
||||
validateThread.setCheckingIntervalSeconds(checkingIntervalSeconds);
|
||||
validateThread.setValidationSleepSeconds(validationSleepSeconds);
|
||||
validateThread.setBlacklistThreshold(blacklistThreshold);
|
||||
}
|
||||
new Thread(validateThread, "RandomDataSourceSelector-validate-thread").start();
|
||||
|
||||
if (recoverThread == null) {
|
||||
recoverThread = new RandomDataSourceRecoverThread(this);
|
||||
recoverThread.setSleepSeconds(recoveryIntervalSeconds);
|
||||
recoverThread.setValidationSleepSeconds(validationSleepSeconds);
|
||||
}
|
||||
new Thread(recoverThread, "RandomDataSourceSelector-recover-thread").start();
|
||||
}
|
||||
|
||||
public HighAvailableDataSource getHighAvailableDataSource() {
|
||||
return highAvailableDataSource;
|
||||
}
|
||||
|
||||
public RandomDataSourceValidateThread getValidateThread() {
|
||||
return validateThread;
|
||||
}
|
||||
|
||||
public void setValidateThread(RandomDataSourceValidateThread validateThread) {
|
||||
this.validateThread = validateThread;
|
||||
}
|
||||
|
||||
public RandomDataSourceRecoverThread getRecoverThread() {
|
||||
return recoverThread;
|
||||
}
|
||||
|
||||
public void setRecoverThread(RandomDataSourceRecoverThread recoverThread) {
|
||||
this.recoverThread = recoverThread;
|
||||
}
|
||||
|
||||
public int getCheckingIntervalSeconds() {
|
||||
return checkingIntervalSeconds;
|
||||
}
|
||||
|
||||
public void setCheckingIntervalSeconds(int checkingIntervalSeconds) {
|
||||
this.checkingIntervalSeconds = checkingIntervalSeconds;
|
||||
}
|
||||
|
||||
public int getRecoveryIntervalSeconds() {
|
||||
return recoveryIntervalSeconds;
|
||||
}
|
||||
|
||||
public void setRecoveryIntervalSeconds(int recoveryIntervalSeconds) {
|
||||
this.recoveryIntervalSeconds = recoveryIntervalSeconds;
|
||||
}
|
||||
|
||||
public int getValidationSleepSeconds() {
|
||||
return validationSleepSeconds;
|
||||
}
|
||||
|
||||
public void setValidationSleepSeconds(int validationSleepSeconds) {
|
||||
this.validationSleepSeconds = validationSleepSeconds;
|
||||
}
|
||||
|
||||
public int getBlacklistThreshold() {
|
||||
return blacklistThreshold;
|
||||
}
|
||||
|
||||
public void setBlacklistThreshold(int blacklistThreshold) {
|
||||
this.blacklistThreshold = blacklistThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
+96
-23
@@ -18,9 +18,14 @@ package com.alibaba.druid.pool.ha.selector;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Driver;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -31,17 +36,18 @@ import com.alibaba.druid.util.JdbcUtils;
|
||||
|
||||
/**
|
||||
* A Thread trying to test all DataSource provided by HADataSource.
|
||||
* If a DataSource failed this test for 3 times, it will be put into a blacklist.
|
||||
* If a DataSource failed this test for 3 times (default value), it will be put into a blacklist.
|
||||
*
|
||||
* @author DigitalSonic
|
||||
*/
|
||||
public class RandomDataSourceValidateThread implements Runnable {
|
||||
private final static Log LOG = LogFactory.getLog(RandomDataSourceValidateThread.class);
|
||||
private int sleepSeconds = 30;
|
||||
private int checkingIntervalSeconds = 15;
|
||||
private int validationSleepSeconds = 0;
|
||||
private int blacklistThreshold = 3;
|
||||
|
||||
private RandomDataSourceSelector selector;
|
||||
private Map<String, Integer> errorCounts = new HashMap<String, Integer>();
|
||||
private ExecutorService checkExecutor = Executors.newFixedThreadPool(5);
|
||||
private Map<String, Integer> errorCounts = new ConcurrentHashMap<String, Integer>();
|
||||
|
||||
public RandomDataSourceValidateThread(RandomDataSourceSelector selector) {
|
||||
this.selector = selector;
|
||||
@@ -54,12 +60,29 @@ public class RandomDataSourceValidateThread implements Runnable {
|
||||
checkAllDataSources();
|
||||
maintainBlacklist();
|
||||
}
|
||||
try {
|
||||
Thread.sleep(sleepSeconds * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
sleepForNextValidation();
|
||||
}
|
||||
}
|
||||
|
||||
private void sleepForNextValidation() {
|
||||
int errorCountBelowThreshold = 0;
|
||||
|
||||
for (int count : errorCounts.values()) {
|
||||
if (count > 0 && count < blacklistThreshold && count > errorCountBelowThreshold) {
|
||||
errorCountBelowThreshold = count;
|
||||
}
|
||||
}
|
||||
|
||||
int newSleepSeconds = checkingIntervalSeconds / (errorCountBelowThreshold + 1);
|
||||
if (newSleepSeconds < 1) {
|
||||
newSleepSeconds = 1;
|
||||
}
|
||||
try {
|
||||
LOG.debug("Sleep " + newSleepSeconds + " second(s) until next checking.");
|
||||
Thread.sleep(newSleepSeconds * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private void maintainBlacklist() {
|
||||
@@ -67,7 +90,8 @@ public class RandomDataSourceValidateThread implements Runnable {
|
||||
for (Map.Entry<String, Integer> e : errorCounts.entrySet()) {
|
||||
if (e.getValue() <= 0) {
|
||||
selector.removeBlacklist(dataSourceMap.get(e.getKey()));
|
||||
} else if (e.getValue() >= blacklistThreshold) {
|
||||
} else if (e.getValue() >= blacklistThreshold
|
||||
&& !selector.containInBlacklist(dataSourceMap.get(e.getKey()))) {
|
||||
LOG.warn("Adding " + e.getKey() + " to blacklist.");
|
||||
selector.addBlacklist(dataSourceMap.get(e.getKey()));
|
||||
}
|
||||
@@ -76,21 +100,41 @@ public class RandomDataSourceValidateThread implements Runnable {
|
||||
|
||||
private void checkAllDataSources() {
|
||||
Map<String, DataSource> dataSourceMap = selector.getDataSourceMap();
|
||||
for (Map.Entry<String, DataSource> e : dataSourceMap.entrySet()) {
|
||||
List<Callable<Boolean>> tasks = new ArrayList<Callable<Boolean>>();
|
||||
|
||||
for (final Map.Entry<String, DataSource> e : dataSourceMap.entrySet()) {
|
||||
if (!(e.getValue() instanceof DruidDataSource)) {
|
||||
continue;
|
||||
}
|
||||
boolean flag = check(e.getKey(), (DruidDataSource) e.getValue());
|
||||
|
||||
if (flag) {
|
||||
errorCounts.put(e.getKey(), 0);
|
||||
} else {
|
||||
if (!errorCounts.containsKey(e.getKey())) {
|
||||
errorCounts.put(e.getKey(), 0);
|
||||
}
|
||||
int count = errorCounts.get(e.getKey());
|
||||
errorCounts.put(e.getKey(), count + 1);
|
||||
if (selector.containInBlacklist(e.getValue())) {
|
||||
LOG.debug(e.getKey() + " is already in blacklist, skip.");
|
||||
continue;
|
||||
}
|
||||
|
||||
tasks.add(new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() {
|
||||
boolean flag = check(e.getKey(), (DruidDataSource) e.getValue());
|
||||
|
||||
if (flag) {
|
||||
errorCounts.put(e.getKey(), 0);
|
||||
} else {
|
||||
if (!errorCounts.containsKey(e.getKey())) {
|
||||
errorCounts.put(e.getKey(), 0);
|
||||
}
|
||||
int count = errorCounts.get(e.getKey());
|
||||
errorCounts.put(e.getKey(), count + 1);
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
checkExecutor.invokeAll(tasks);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Exception occurred while checking DataSource.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +154,9 @@ public class RandomDataSourceValidateThread implements Runnable {
|
||||
info.setProperty("password", password);
|
||||
}
|
||||
try {
|
||||
LOG.debug("Validating " + name + " every " + sleepSeconds + " seconds.");
|
||||
LOG.debug("Validating " + name + " every " + checkingIntervalSeconds + " seconds.");
|
||||
conn = driver.connect(url, info);
|
||||
sleepBeforeValidation();
|
||||
dataSource.validateConnection(conn);
|
||||
} catch (SQLException e) {
|
||||
LOG.warn("Validation FAILED for " + name + " with url [" + url + "] and username ["
|
||||
@@ -124,8 +169,36 @@ public class RandomDataSourceValidateThread implements Runnable {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setSleepSeconds(int sleepSeconds) {
|
||||
this.sleepSeconds = sleepSeconds;
|
||||
private void sleepBeforeValidation() {
|
||||
if (validationSleepSeconds <= 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
LOG.debug("Sleep " + validationSleepSeconds + " second(s) before validation.");
|
||||
Thread.sleep(validationSleepSeconds * 1000L);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
public int getCheckingIntervalSeconds() {
|
||||
return checkingIntervalSeconds;
|
||||
}
|
||||
|
||||
public void setCheckingIntervalSeconds(int checkingIntervalSeconds) {
|
||||
this.checkingIntervalSeconds = checkingIntervalSeconds;
|
||||
}
|
||||
|
||||
public int getValidationSleepSeconds() {
|
||||
return validationSleepSeconds;
|
||||
}
|
||||
|
||||
public void setValidationSleepSeconds(int validationSleepSeconds) {
|
||||
this.validationSleepSeconds = validationSleepSeconds;
|
||||
}
|
||||
|
||||
public int getBlacklistThreshold() {
|
||||
return blacklistThreshold;
|
||||
}
|
||||
|
||||
public void setBlacklistThreshold(int blacklistThreshold) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.alibaba.druid.pool.ha.selector;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* A class holding DataSource reference and retrieving time.
|
||||
*
|
||||
* @author DigitalSonic
|
||||
*/
|
||||
public class StickyDataSourceHolder {
|
||||
private long retrievingTime = System.currentTimeMillis();
|
||||
private DataSource dataSource;
|
||||
|
||||
public StickyDataSourceHolder() {
|
||||
}
|
||||
|
||||
public StickyDataSourceHolder(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return retrievingTime > 0 && dataSource != null;
|
||||
}
|
||||
|
||||
public long getRetrievingTime() {
|
||||
return retrievingTime;
|
||||
}
|
||||
|
||||
public void setRetrievingTime(long retrievingTime) {
|
||||
this.retrievingTime = retrievingTime;
|
||||
}
|
||||
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.alibaba.druid.pool.ha.selector;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.alibaba.druid.pool.ha.HighAvailableDataSource;
|
||||
import com.alibaba.druid.support.logging.Log;
|
||||
import com.alibaba.druid.support.logging.LogFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* An extend selector based on RandomDataSourceSelector which can stick a DataSource to a Thread in a while.
|
||||
*
|
||||
* @author DigitalSonic
|
||||
* @see RandomDataSourceSelector
|
||||
* @see StickyDataSourceHolder
|
||||
*/
|
||||
public class StickyRandomDataSourceSelector extends RandomDataSourceSelector {
|
||||
private final static Log LOG = LogFactory.getLog(StickyRandomDataSourceSelector.class);
|
||||
|
||||
private ThreadLocal<StickyDataSourceHolder> holders = new ThreadLocal<StickyDataSourceHolder>();
|
||||
|
||||
private int expireSeconds = 5;
|
||||
|
||||
public StickyRandomDataSourceSelector(HighAvailableDataSource highAvailableDataSource) {
|
||||
super(highAvailableDataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return DataSourceSelectorEnum.STICKY_RANDOM.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource get() {
|
||||
StickyDataSourceHolder holder = holders.get();
|
||||
if (holder != null && isValid(holder) && !isExpired(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();
|
||||
holders.set(holder);
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public int getExpireSeconds() {
|
||||
return expireSeconds;
|
||||
}
|
||||
|
||||
public void setExpireSeconds(int expireSeconds) {
|
||||
this.expireSeconds = expireSeconds;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public class MySqlExceptionSorter implements ExceptionSorter {
|
||||
}
|
||||
|
||||
switch (errorCode) {
|
||||
// Communications Errors
|
||||
// Communications Errors
|
||||
case 1040: // ER_CON_COUNT_ERROR
|
||||
case 1042: // ER_BAD_HOST_ERROR
|
||||
case 1043: // ER_HANDSHAKE_ERROR
|
||||
@@ -46,21 +46,23 @@ public class MySqlExceptionSorter implements ExceptionSorter {
|
||||
case 1081: // ER_IPSOCK_ERROR
|
||||
case 1129: // ER_HOST_IS_BLOCKED
|
||||
case 1130: // ER_HOST_NOT_PRIVILEGED
|
||||
// Authentication Errors
|
||||
// Authentication Errors
|
||||
case 1045: // ER_ACCESS_DENIED_ERROR
|
||||
// Resource errors
|
||||
// Resource errors
|
||||
case 1004: // ER_CANT_CREATE_FILE
|
||||
case 1005: // ER_CANT_CREATE_TABLE
|
||||
case 1015: // ER_CANT_LOCK
|
||||
case 1021: // ER_DISK_FULL
|
||||
case 1041: // ER_OUT_OF_RESOURCES
|
||||
// Out-of-memory errors
|
||||
// Out-of-memory errors
|
||||
case 1037: // ER_OUTOFMEMORY
|
||||
case 1038: // ER_OUT_OF_SORTMEMORY
|
||||
// Access denied
|
||||
// Access denied
|
||||
case 1142: // ER_TABLEACCESS_DENIED_ERROR
|
||||
case 1227: // ER_SPECIFIC_ACCESS_DENIED_ERROR
|
||||
|
||||
case 1023: // ER_ERROR_ON_CLOSE
|
||||
|
||||
case 1290: // ER_OPTION_PREVENTS_STATEMENT
|
||||
return true;
|
||||
default:
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ public class MySqlValidConnectionChecker extends ValidConnectionCheckerAdapter i
|
||||
}
|
||||
|
||||
if (clazz.isAssignableFrom(conn.getClass())) {
|
||||
if (validationQueryTimeout < 0) {
|
||||
if (validationQueryTimeout <= 0) {
|
||||
validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public class OracleValidConnectionChecker extends ValidConnectionCheckerAdapter
|
||||
return true;
|
||||
}
|
||||
|
||||
int queryTimeout = validationQueryTimeout < 0 ? timeout : validationQueryTimeout;
|
||||
int queryTimeout = validationQueryTimeout <= 0 ? timeout : validationQueryTimeout;
|
||||
|
||||
Statement stmt = null;
|
||||
ResultSet rs = null;
|
||||
|
||||
@@ -55,7 +55,7 @@ public class PGValidConnectionChecker extends ValidConnectionCheckerAdapter impl
|
||||
conn = ((ConnectionProxy) conn).getRawObject();
|
||||
}
|
||||
|
||||
int queryTimeout = validationQueryTimeout < 0 ? defaultQueryTimeout : validationQueryTimeout;
|
||||
int queryTimeout = validationQueryTimeout <= 0 ? defaultQueryTimeout : validationQueryTimeout;
|
||||
|
||||
Statement stmt = null;
|
||||
ResultSet rs = null;
|
||||
|
||||
@@ -3,11 +3,29 @@ package com.alibaba.druid.pool.ha;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DataSourceCreatorTest {
|
||||
@Test
|
||||
public void testNameList() {
|
||||
String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties";
|
||||
DataSourceCreator creator = new DataSourceCreator(file, "prefix1");
|
||||
List<String> list = creator.getNameList();
|
||||
assertFalse(list.isEmpty());
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("prefix1.foo", list.get(0));
|
||||
|
||||
creator = new DataSourceCreator(file);
|
||||
list = creator.getNameList();
|
||||
assertEquals(3, list.size());
|
||||
for (String k : list) {
|
||||
assertNotEquals("bar", k);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMap() throws Exception {
|
||||
HighAvailableDataSource highAvailableDataSource = new HighAvailableDataSource();
|
||||
@@ -19,4 +37,21 @@ public class DataSourceCreatorTest {
|
||||
assertNotNull(map.get("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMapWithPrefix() throws Exception {
|
||||
HighAvailableDataSource highAvailableDataSource = new HighAvailableDataSource();
|
||||
String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties";
|
||||
DataSourceCreator creator = new DataSourceCreator(file, "prefix1");
|
||||
Map<String, DataSource> map = creator.createMap(highAvailableDataSource);
|
||||
assertEquals(1, map.size());
|
||||
assertNotNull(map.get("prefix1.foo"));
|
||||
assertNull(map.get("prefix2.foo"));
|
||||
|
||||
creator = new DataSourceCreator(file, "prefix2");
|
||||
map = creator.createMap(highAvailableDataSource);
|
||||
assertEquals(1, map.size());
|
||||
assertNull(map.get("prefix1.foo"));
|
||||
assertNotNull(map.get("prefix2.foo"));
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.alibaba.druid.pool.ha.selector;
|
||||
|
||||
import com.alibaba.druid.pool.ha.HighAvailableDataSource;
|
||||
import com.alibaba.druid.pool.ha.MockDataSource;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BaseRandomDataSourceSelectorTest {
|
||||
protected Map<String, DataSource> dataSourceMap;
|
||||
protected HighAvailableDataSource dataSource;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
dataSourceMap = new HashMap<String, DataSource>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
dataSourceMap.put(Integer.toString(i), new MockDataSource(Integer.toString(i)));
|
||||
}
|
||||
dataSource = new HighAvailableDataSource();
|
||||
dataSource.setDataSourceMap(dataSourceMap);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
dataSourceMap = null;
|
||||
dataSource = null;
|
||||
}
|
||||
}
|
||||
+24
-31
@@ -1,39 +1,14 @@
|
||||
package com.alibaba.druid.pool.ha.selector;
|
||||
|
||||
import com.alibaba.druid.pool.ha.HighAvailableDataSource;
|
||||
import com.alibaba.druid.pool.ha.MockDataSource;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RandomDataSourceSelectorTest {
|
||||
private Map<String, DataSource> dataSourceMap;
|
||||
private HighAvailableDataSource dataSource;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
dataSourceMap = new HashMap<String, DataSource>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
dataSourceMap.put(Integer.toString(i), new MockDataSource(Integer.toString(i)));
|
||||
}
|
||||
dataSource = new HighAvailableDataSource();
|
||||
dataSource.setDataSourceMap(dataSourceMap);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
dataSourceMap = null;
|
||||
dataSource = null;
|
||||
}
|
||||
|
||||
public class RandomDataSourceSelectorTest extends BaseRandomDataSourceSelectorTest {
|
||||
@Test
|
||||
public void testRandomGet() throws Exception {
|
||||
public void testRandomGet() {
|
||||
RandomDataSourceSelector selector = new RandomDataSourceSelector(dataSource);
|
||||
int[] count = new int[10];
|
||||
|
||||
@@ -48,8 +23,26 @@ public class RandomDataSourceSelectorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneDataSourceFail() {
|
||||
public void testCreateSelectorWithProperties() {
|
||||
String props = "druid.ha.random.checkingIntervalSeconds=60;";
|
||||
props += "druid.ha.random.recoveryIntervalSeconds=120;";
|
||||
props += "druid.ha.random.validationSleepSeconds=10;";
|
||||
props += "druid.ha.random.blacklistThreshold=5;";
|
||||
dataSource.setConnectionProperties(props);
|
||||
|
||||
RandomDataSourceSelector selector = new RandomDataSourceSelector(dataSource);
|
||||
selector.init();
|
||||
assertEquals(60, selector.getCheckingIntervalSeconds());
|
||||
assertEquals(60, selector.getValidateThread().getCheckingIntervalSeconds());
|
||||
|
||||
assertEquals(120, selector.getRecoveryIntervalSeconds());
|
||||
assertEquals(120, selector.getRecoverThread().getSleepSeconds());
|
||||
|
||||
assertEquals(10, selector.getValidationSleepSeconds());
|
||||
assertEquals(10, selector.getValidateThread().getValidationSleepSeconds());
|
||||
assertEquals(10, selector.getRecoverThread().getValidationSleepSeconds());
|
||||
|
||||
assertEquals(5, selector.getBlacklistThreshold());
|
||||
assertEquals(5, selector.getValidateThread().getBlacklistThreshold());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -30,17 +30,17 @@ public class RandomDataSourceSelectorWithValidationTest {
|
||||
|
||||
@Test
|
||||
public void testOneDataSourceFailAndRecover() throws Exception {
|
||||
((RandomDataSourceSelector) highAvailableDataSource.getDataSourceSelector()).getValidateThread().setSleepSeconds(3);
|
||||
((RandomDataSourceSelector) highAvailableDataSource.getDataSourceSelector()).getRecoverThread().setSleepSeconds(3);
|
||||
|
||||
Thread.sleep(30 * 1000);
|
||||
RandomDataSourceSelector selector = ((RandomDataSourceSelector) highAvailableDataSource.getDataSourceSelector());
|
||||
selector.getValidateThread().setCheckingIntervalSeconds(3);
|
||||
selector.getRecoverThread().setSleepSeconds(3);
|
||||
selector.init();
|
||||
|
||||
DruidDataSource dataSource = (DruidDataSource) highAvailableDataSource.getDataSourceMap().get("foo");
|
||||
dataSource.setValidationQuery("select xxx from yyy");
|
||||
Thread.sleep(10 * 1000);
|
||||
assertTrue(dataSource.isTestOnReturn());
|
||||
for (int i = 0; i < 100; i++) {
|
||||
assertNotEquals(dataSource, highAvailableDataSource.getDataSourceSelector().get());
|
||||
assertNotEquals(dataSource, selector.get());
|
||||
}
|
||||
|
||||
dataSource.setValidationQuery(null);
|
||||
@@ -48,7 +48,7 @@ public class RandomDataSourceSelectorWithValidationTest {
|
||||
assertFalse(dataSource.isTestOnReturn());
|
||||
int count = 0;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (dataSource == highAvailableDataSource.getDataSourceSelector().get()) {
|
||||
if (dataSource == selector.get()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@@ -57,23 +57,23 @@ public class RandomDataSourceSelectorWithValidationTest {
|
||||
|
||||
@Test
|
||||
public void testAllDataSourceFail() throws Exception {
|
||||
((RandomDataSourceSelector) highAvailableDataSource.getDataSourceSelector()).getValidateThread().setSleepSeconds(3);
|
||||
((RandomDataSourceSelector) highAvailableDataSource.getDataSourceSelector()).getRecoverThread().setSleepSeconds(3);
|
||||
|
||||
Thread.sleep(30 * 1000);
|
||||
RandomDataSourceSelector selector = (RandomDataSourceSelector) highAvailableDataSource.getDataSourceSelector();
|
||||
selector.setCheckingIntervalSeconds(3);
|
||||
selector.setRecoveryIntervalSeconds(3);
|
||||
selector.init();
|
||||
|
||||
DruidDataSource foo = (DruidDataSource) highAvailableDataSource.getDataSourceMap().get("foo");
|
||||
DruidDataSource bar = (DruidDataSource) highAvailableDataSource.getDataSourceMap().get("bar");
|
||||
foo.setValidationQuery("select xxx from yyy");
|
||||
bar.setValidationQuery("select xxx from yyy");
|
||||
|
||||
Thread.sleep(10 * 1000);
|
||||
Thread.sleep(6 * 1000);
|
||||
assertTrue(foo.isTestOnReturn());
|
||||
assertTrue(bar.isTestOnReturn());
|
||||
|
||||
int[] count = new int[2];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
DataSource dataSource = highAvailableDataSource.getDataSourceSelector().get();
|
||||
DataSource dataSource = selector.get();
|
||||
if (foo == dataSource) {
|
||||
count[0]++;
|
||||
} else if (bar == dataSource) {
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.alibaba.druid.pool.ha.selector;
|
||||
|
||||
import com.alibaba.druid.pool.ha.MockDataSource;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class StickyRandomDataSourceSelectorTest extends BaseRandomDataSourceSelectorTest {
|
||||
@Test
|
||||
public void testGetSameDataSourceInFiveSeconds() throws InterruptedException {
|
||||
StickyRandomDataSourceSelector selector = new StickyRandomDataSourceSelector(dataSource);
|
||||
int lastHashCode = selector.get().hashCode();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
assertEquals(lastHashCode, selector.get().hashCode());
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDifferentDataSource() throws InterruptedException {
|
||||
StickyRandomDataSourceSelector selector = new StickyRandomDataSourceSelector(dataSource);
|
||||
selector.setExpireSeconds(1);
|
||||
int lastHashCode = selector.get().hashCode();
|
||||
boolean hasDifferentDataSource = false;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
hasDifferentDataSource = selector.get().hashCode() != lastHashCode;
|
||||
Thread.sleep(500);
|
||||
}
|
||||
assertTrue(hasDifferentDataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreForbiddenDataSource() throws InterruptedException {
|
||||
MockDataSource normal = new MockDataSource("Normal");
|
||||
MockDataSource forbidden = new MockDataSource("Forbidden");
|
||||
dataSourceMap.put("N", normal);
|
||||
dataSourceMap.put("F", forbidden);
|
||||
dataSource.setDataSourceMap(dataSourceMap);
|
||||
|
||||
StickyRandomDataSourceSelector selector = new StickyRandomDataSourceSelector(dataSource);
|
||||
selector.setExpireSeconds(1);
|
||||
selector.getBlacklist().add(forbidden);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
assertNotEquals(forbidden, selector.get());
|
||||
Thread.sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
prefix1.foo.url=jdbc:derby:memory:foo1;create=true
|
||||
prefix2.foo.url=jdbc:derby:memory:foo2;create=true
|
||||
foo.url=jdbc:derby:memory:foo3;create=true
|
||||
bar=jdbc:derby:memory:bar;create=true
|
||||
Reference in New Issue
Block a user