This commit is contained in:
shaojin.wensj
2019-05-06 10:54:22 +08:00
22 changed files with 648 additions and 113 deletions
@@ -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
@@ -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;
}
}
@@ -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:
@@ -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;
}
@@ -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;
@@ -489,7 +489,7 @@ public final class JdbcUtils implements JdbcConstants {
} else if(rawUrl.startsWith("jdbc:presto:")) {
return JdbcConstants.PRESTO_DRIVER;
}else {
throw new SQLException("unkow jdbc driver : " + rawUrl);
throw new SQLException("unknown jdbc driver : " + rawUrl);
}
}
@@ -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"));
}
}
@@ -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;
}
}
@@ -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());
}
}
}
@@ -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) {
@@ -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