Merge pull request #2485 from digitalsonic/master

为Druid增加URL占位符,使得Druid的一个DataSource可以同时连接多个目标
This commit is contained in:
温绍锦
2018-05-17 20:43:56 +08:00
committed by GitHub
18 changed files with 1291 additions and 10 deletions
@@ -62,6 +62,8 @@ public interface FilterChain {
int getFilterSize();
int getPos();
FilterChain cloneChain();
<T> T unwrap(Wrapper wrapper, java.lang.Class<T> iface) throws java.sql.SQLException;
@@ -92,6 +92,10 @@ public class FilterChainImpl implements FilterChain {
return filterSize;
}
public int getPos() {
return pos;
}
public void reset() {
pos = 0;
}
@@ -111,12 +111,12 @@ public class ConfigFilter extends FilterAdapter {
}
DruidDataSource dataSource = (DruidDataSource) dataSourceProxy;
Properties connectinProperties = dataSource.getConnectProperties();
Properties connectionProperties = dataSource.getConnectProperties();
Properties configFileProperties = loadPropertyFromConfigFile(connectinProperties);
Properties configFileProperties = loadPropertyFromConfigFile(connectionProperties);
// 判断是否需要解密,如果需要就进行解密行动
boolean decrypt = isDecrypt(connectinProperties, configFileProperties);
boolean decrypt = isDecrypt(connectionProperties, configFileProperties);
if (configFileProperties == null) {
if (decrypt) {
@@ -136,8 +136,8 @@ public class ConfigFilter extends FilterAdapter {
}
}
public boolean isDecrypt(Properties connectinProperties, Properties configFileProperties) {
String decrypterId = connectinProperties.getProperty(CONFIG_DECRYPT);
public boolean isDecrypt(Properties connectionProperties, Properties configFileProperties) {
String decrypterId = connectionProperties.getProperty(CONFIG_DECRYPT);
if (decrypterId == null || decrypterId.length() == 0) {
if (configFileProperties != null) {
decrypterId = configFileProperties.getProperty(CONFIG_DECRYPT);
@@ -151,8 +151,8 @@ public class ConfigFilter extends FilterAdapter {
return Boolean.valueOf(decrypterId);
}
Properties loadPropertyFromConfigFile(Properties connectinProperties) {
String configFile = connectinProperties.getProperty(CONFIG_FILE);
Properties loadPropertyFromConfigFile(Properties connectionProperties) {
String configFile = connectionProperties.getProperty(CONFIG_FILE);
if (configFile == null) {
configFile = System.getProperty(SYS_PROP_CONFIG_FILE);
@@ -206,14 +206,14 @@ public class ConfigFilter extends FilterAdapter {
}
}
public PublicKey getPublicKey(Properties connectinProperties, Properties configFileProperties) {
public PublicKey getPublicKey(Properties connectionProperties, Properties configFileProperties) {
String key = null;
if (configFileProperties != null) {
key = configFileProperties.getProperty(CONFIG_KEY);
}
if (StringUtils.isEmpty(key) && connectinProperties != null) {
key = connectinProperties.getProperty(CONFIG_KEY);
if (StringUtils.isEmpty(key) && connectionProperties != null) {
key = connectionProperties.getProperty(CONFIG_KEY);
}
if (StringUtils.isEmpty(key)) {
@@ -0,0 +1,155 @@
package com.alibaba.druid.pool.ha;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* An utility class to create DruidDataSource dynamically.
*
* @author DigitalSonic
*/
public class DataSourceCreator {
private final static Log LOG = LogFactory.getLog(DataSourceCreator.class);
private Properties properties = new Properties();
private List<String> nameList = new ArrayList<String>();
public DataSourceCreator(String file) {
loadProperties(file);
loadNameList();
}
public Map<String, DataSource> createMap(HighAvailableDataSource haDataSource) throws SQLException {
Map<String, DataSource> map = new ConcurrentHashMap<String, DataSource>();
if (nameList == null || nameList.isEmpty()) {
LOG.error("No DataSource will be created!");
return map;
}
for (String n : nameList) {
String url = properties.getProperty(n + ".url");
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);
}
LOG.info(map.size() + " DruidDataSource(s) created. ");
return map;
}
protected DruidDataSource create(String name, String url, String username, String password,
HighAvailableDataSource haDataSource) throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setName(name);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(haDataSource.getDriverClassName());
dataSource.setConnectProperties(haDataSource.getConnectProperties());
dataSource.setConnectionProperties(haDataSource.getConnectionProperties());
dataSource.setInitialSize(haDataSource.getInitialSize());
dataSource.setMaxActive(haDataSource.getMaxActive());
dataSource.setMinIdle(haDataSource.getMinIdle());
dataSource.setMaxWait(haDataSource.getMaxWait());
dataSource.setValidationQuery(haDataSource.getValidationQuery());
dataSource.setValidationQueryTimeout(haDataSource.getValidationQueryTimeout());
dataSource.setTestOnBorrow(haDataSource.isTestOnBorrow());
dataSource.setTestOnReturn(haDataSource.isTestOnReturn());
dataSource.setTestWhileIdle(haDataSource.isTestWhileIdle());
dataSource.setPoolPreparedStatements(haDataSource.isPoolPreparedStatements());
dataSource.setSharePreparedStatements(haDataSource.isSharePreparedStatements());
dataSource.setMaxPoolPreparedStatementPerConnectionSize(
haDataSource.getMaxPoolPreparedStatementPerConnectionSize());
dataSource.setQueryTimeout(haDataSource.getQueryTimeout());
dataSource.setTransactionQueryTimeout(haDataSource.getTransactionQueryTimeout());
dataSource.setTimeBetweenEvictionRunsMillis(haDataSource.getTimeBetweenEvictionRunsMillis());
dataSource.setMinEvictableIdleTimeMillis(haDataSource.getMinEvictableIdleTimeMillis());
dataSource.setMaxEvictableIdleTimeMillis(haDataSource.getMaxEvictableIdleTimeMillis());
dataSource.setPhyTimeoutMillis(haDataSource.getPhyTimeoutMillis());
dataSource.setTimeBetweenConnectErrorMillis(haDataSource.getTimeBetweenConnectErrorMillis());
dataSource.setRemoveAbandoned(haDataSource.isRemoveAbandoned());
dataSource.setRemoveAbandonedTimeoutMillis(haDataSource.getRemoveAbandonedTimeoutMillis());
dataSource.setLogAbandoned(haDataSource.isLogAbandoned());
dataSource.setProxyFilters(haDataSource.getProxyFilters());
dataSource.setFilters(haDataSource.getFilters());
dataSource.setLogWriter(haDataSource.getLogWriter());
dataSource.init();
return dataSource;
}
private void loadNameList() {
Set<String> names = new HashSet<String>();
for (String n : properties.stringPropertyNames()) {
if (n.contains(".")) {
names.add(n.split("\\.")[0]);
}
}
if (!names.isEmpty()) {
nameList.addAll(names);
}
}
private void loadProperties(String file) {
Properties properties = new Properties();
if (file == null) {
return;
}
InputStream is = null;
try {
LOG.debug("Trying to load " + file + " from FileSystem.");
is = new FileInputStream(file);
} catch(FileNotFoundException e) {
LOG.debug("Trying to load " + file + " from Classpath.");
try {
is = DataSourceCreator.class.getResourceAsStream(file);
} catch (Exception ex) {
LOG.warn("Can not load resource " + file, ex);
}
}
if (is != null) {
try {
properties.load(is);
this.properties = properties;
} catch(Exception e) {
LOG.error("Exception occurred while loading " + file, e);
} finally {
if (is != null) {
try {
is.close();
} catch (Exception e) {
// ignore
}
}
}
} else {
LOG.warn("File " + file + " can't be loaded!");
}
}
}
@@ -0,0 +1,385 @@
package com.alibaba.druid.pool.ha;
import com.alibaba.druid.filter.Filter;
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.DataSourceSelectorFactory;
import com.alibaba.druid.pool.ha.selector.RandomDataSourceSelector;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
/**
* DataSource class which contains multiple DataSource objects.
*
* @author DigitalSonic
*/
public class HighAvailableDataSource extends WrapperAdapter implements DataSource {
private final static Log LOG = LogFactory.getLog(HighAvailableDataSource.class);
private final static String DEFAULT_DATA_SOURCE_FILE = "ha-datasource.properties";
// Properties copied from DruidAbstractDataSource BEGIN
private String driverClassName;
private Properties connectProperties = new Properties();
private String connectionProperties = null;
private int initialSize = DruidAbstractDataSource.DEFAULT_INITIAL_SIZE;
private int maxActive = DruidAbstractDataSource.DEFAULT_MAX_ACTIVE_SIZE;
private int minIdle = DruidAbstractDataSource.DEFAULT_MIN_IDLE;
private long maxWait = DruidAbstractDataSource.DEFAULT_MAX_WAIT;
private String validationQuery = DruidAbstractDataSource.DEFAULT_VALIDATION_QUERY;
private int validationQueryTimeout = -1;
private boolean testOnBorrow = DruidAbstractDataSource.DEFAULT_TEST_ON_BORROW;
private boolean testOnReturn = DruidAbstractDataSource.DEFAULT_TEST_ON_RETURN;
private boolean testWhileIdle = DruidAbstractDataSource.DEFAULT_WHILE_IDLE;
private boolean poolPreparedStatements = false;
private boolean sharePreparedStatements = false;
private int maxPoolPreparedStatementPerConnectionSize = 10;
private int queryTimeout;
private int transactionQueryTimeout;
private long timeBetweenEvictionRunsMillis = DruidAbstractDataSource.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
private long minEvictableIdleTimeMillis = DruidAbstractDataSource.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private long maxEvictableIdleTimeMillis = DruidAbstractDataSource.DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS;
private long phyTimeoutMillis = DruidAbstractDataSource.DEFAULT_PHY_TIMEOUT_MILLIS;
private long timeBetweenConnectErrorMillis = DruidAbstractDataSource.DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS;
private boolean removeAbandoned;
private long removeAbandonedTimeoutMillis = 300 * 1000;
private boolean logAbandoned;
private String filters;
private List<Filter> proxyFilters = new CopyOnWriteArrayList<Filter>();
private PrintWriter logWriter = new PrintWriter(System.out);
// Properties copied from DruidAbstractDataSource END
private Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<String, DataSource>();
private DataSourceSelector selector = new RandomDataSourceSelector(this);
private String dataSourceFile = DEFAULT_DATA_SOURCE_FILE;
public void init() throws SQLException {
synchronized (this) {
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
dataSourceMap = new DataSourceCreator(dataSourceFile).createMap(this);
}
if (selector == null) {
selector = new RandomDataSourceSelector(this);
}
}
}
public void destroy() {
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
return;
}
for (DataSource dataSource : dataSourceMap.values()) {
if (dataSource instanceof DruidDataSource) {
((DruidDataSource) dataSource).close();
}
}
}
public void setTargetDataSource(String targetName) {
selector.setTarget(targetName);
}
@Override
public Connection getConnection() throws SQLException {
DataSource dataSource = selector.get();
return dataSource.getConnection();
}
public String getDataSourceFile() {
return dataSourceFile;
}
public void setDataSourceFile(String dataSourceFile) {
this.dataSourceFile = dataSourceFile;
}
public void setDataSourceMap(Map<String, DataSource> dataSourceMap) {
if (dataSourceMap != null) {
this.dataSourceMap = dataSourceMap;
}
}
public Map<String, DataSource> getDataSourceMap() {
return dataSourceMap;
}
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) {
this.selector = selector;
}
}
public DataSourceSelector getSelector() {
return selector;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
throw new UnsupportedOperationException("Not supported by DruidDataSource");
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
this.logWriter = out;
}
@Override
public void setLoginTimeout(int seconds) {
DriverManager.setLoginTimeout(seconds);
}
@Override
public int getLoginTimeout() {
return DriverManager.getLoginTimeout();
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Properties getConnectProperties() {
return connectProperties;
}
public void setConnectProperties(Properties connectProperties) {
this.connectProperties = connectProperties;
}
public int getInitialSize() {
return initialSize;
}
public void setInitialSize(int initialSize) {
this.initialSize = initialSize;
}
public int getMaxActive() {
return maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public int getMinIdle() {
return minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public long getMaxWait() {
return maxWait;
}
public void setMaxWait(long maxWait) {
this.maxWait = maxWait;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public int getValidationQueryTimeout() {
return validationQueryTimeout;
}
public void setValidationQueryTimeout(int validationQueryTimeout) {
this.validationQueryTimeout = validationQueryTimeout;
}
public boolean isTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public boolean isTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
public boolean isTestWhileIdle() {
return testWhileIdle;
}
public void setTestWhileIdle(boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
public boolean isPoolPreparedStatements() {
return poolPreparedStatements;
}
public void setPoolPreparedStatements(boolean poolPreparedStatements) {
this.poolPreparedStatements = poolPreparedStatements;
}
public boolean isSharePreparedStatements() {
return sharePreparedStatements;
}
public void setSharePreparedStatements(boolean sharePreparedStatements) {
this.sharePreparedStatements = sharePreparedStatements;
}
public int getMaxPoolPreparedStatementPerConnectionSize() {
return maxPoolPreparedStatementPerConnectionSize;
}
public void setMaxPoolPreparedStatementPerConnectionSize(int maxPoolPreparedStatementPerConnectionSize) {
this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
}
public int getQueryTimeout() {
return queryTimeout;
}
public void setQueryTimeout(int queryTimeout) {
this.queryTimeout = queryTimeout;
}
public int getTransactionQueryTimeout() {
return transactionQueryTimeout;
}
public void setTransactionQueryTimeout(int transactionQueryTimeout) {
this.transactionQueryTimeout = transactionQueryTimeout;
}
public long getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public long getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public long getMaxEvictableIdleTimeMillis() {
return maxEvictableIdleTimeMillis;
}
public void setMaxEvictableIdleTimeMillis(long maxEvictableIdleTimeMillis) {
this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis;
}
public long getPhyTimeoutMillis() {
return phyTimeoutMillis;
}
public void setPhyTimeoutMillis(long phyTimeoutMillis) {
this.phyTimeoutMillis = phyTimeoutMillis;
}
public long getTimeBetweenConnectErrorMillis() {
return timeBetweenConnectErrorMillis;
}
public void setTimeBetweenConnectErrorMillis(long timeBetweenConnectErrorMillis) {
this.timeBetweenConnectErrorMillis = timeBetweenConnectErrorMillis;
}
public boolean isRemoveAbandoned() {
return removeAbandoned;
}
public void setRemoveAbandoned(boolean removeAbandoned) {
this.removeAbandoned = removeAbandoned;
}
public long getRemoveAbandonedTimeoutMillis() {
return removeAbandonedTimeoutMillis;
}
public void setRemoveAbandonedTimeoutMillis(long removeAbandonedTimeoutMillis) {
this.removeAbandonedTimeoutMillis = removeAbandonedTimeoutMillis;
}
public boolean isLogAbandoned() {
return logAbandoned;
}
public void setLogAbandoned(boolean logAbandoned) {
this.logAbandoned = logAbandoned;
}
public String getConnectionProperties() {
return connectionProperties;
}
public void setConnectionProperties(String connectionProperties) {
this.connectionProperties = connectionProperties;
}
public String getFilters() {
return filters;
}
public void setFilters(String filters) {
this.filters = filters;
}
public List<Filter> getProxyFilters() {
return proxyFilters;
}
public void setProxyFilters(List<Filter> proxyFilters) {
this.proxyFilters = proxyFilters;
}
}
@@ -0,0 +1,16 @@
package com.alibaba.druid.pool.ha.selector;
import javax.sql.DataSource;
import java.util.Map;
/**
* Interface for those selector to implement.
* e.g. Random and Named
*
* @author DigitalSonic
*/
public interface DataSourceSelector {
DataSource get();
void setTarget(String name);
boolean isSame(String name);
}
@@ -0,0 +1,19 @@
package com.alibaba.druid.pool.ha.selector;
import com.alibaba.druid.pool.ha.HighAvailableDataSource;
/**
* A Factory pattern for DataSourceSelector.
*
* @author DigitalSonic
*/
public class DataSourceSelectorFactory {
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);
}
return null;
}
}
@@ -0,0 +1,74 @@
package com.alibaba.druid.pool.ha.selector;
import com.alibaba.druid.pool.ha.HighAvailableDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* Use the given name in ThreadLocal variable to choose DataSource.
*
* @author DigitalSonic
*/
public class NamedDataSourceSelector implements DataSourceSelector {
public static final String DEFAULT_NAME = "default";
private HighAvailableDataSource highAvailableDataSource;
private ThreadLocal<String> targetDataSourceName = new ThreadLocal<String>();
private String defaultName = DEFAULT_NAME;
public NamedDataSourceSelector(HighAvailableDataSource highAvailableDataSource) {
this.highAvailableDataSource = highAvailableDataSource;
}
@Override
public boolean isSame(String name) {
return "byName".equalsIgnoreCase(name);
}
@Override
public DataSource get() {
if (highAvailableDataSource == null) {
return null;
}
Map<String, DataSource> dataSourceMap = highAvailableDataSource.getDataSourceMap();
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
return null;
}
if (dataSourceMap.size() == 1) {
for (DataSource v : dataSourceMap.values()) {
return v;
}
}
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);
}
public String getTarget() {
return targetDataSourceName.get();
}
public void resetDataSourceName() {
targetDataSourceName.remove();
}
public String getDefaultName() {
return defaultName;
}
public void setDefaultName(String defaultName) {
this.defaultName = defaultName;
}
}
@@ -0,0 +1,74 @@
package com.alibaba.druid.pool.ha.selector;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.alibaba.druid.util.JdbcUtils;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* A Thread trying to test if DataSource in blacklist has been recovered.
*
* @author DigitalSonic
*/
public class RandomDataSourceRecoverThread implements Runnable {
private final static Log LOG = LogFactory.getLog(RandomDataSourceRecoverThread.class);
private RandomDataSourceSelector selector;
private int sleepSeconds = 30;
public RandomDataSourceRecoverThread(RandomDataSourceSelector selector) {
this.selector = selector;
}
@Override
public void run() {
while (true) {
if (selector != null && selector.getBlacklist() != null
&& !selector.getBlacklist().isEmpty()) {
LOG.info(selector.getBlacklist().size() + " DataSource in blacklist.");
for (DataSource dataSource : selector.getBlacklist()) {
if (!(dataSource instanceof DruidDataSource)) {
continue;
}
tryOneDataSource((DruidDataSource) dataSource);
}
}
sleep();
}
}
private void tryOneDataSource(DruidDataSource dataSource) {
if (dataSource == null) {
return;
}
Connection connection = null;
try {
connection = dataSource.getConnection();
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 sleep() {
try {
Thread.sleep(sleepSeconds * 1000);
} catch (InterruptedException e) {
// ignore
}
}
public void setSleepSeconds(int sleepSeconds) {
this.sleepSeconds = sleepSeconds;
}
}
@@ -0,0 +1,105 @@
package com.alibaba.druid.pool.ha.selector;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.ha.HighAvailableDataSource;
import javax.sql.DataSource;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A selector which uses java.util.Random to choose DataSource.
*
* @author DigitalSonic
*/
public class RandomDataSourceSelector implements DataSourceSelector {
private Random random = new Random();
private List<DataSource> blacklist = new CopyOnWriteArrayList<DataSource>();
private HighAvailableDataSource highAvailableDataSource;
private RandomDataSourceValidateThread validateThread;
private RandomDataSourceRecoverThread recoverThread;
public RandomDataSourceSelector(HighAvailableDataSource highAvailableDataSource) {
this.highAvailableDataSource = highAvailableDataSource;
validateThread = new RandomDataSourceValidateThread(this);
recoverThread = new RandomDataSourceRecoverThread(this);
new Thread(validateThread, "RandomDataSourceSelector-validate-thread").start();
new Thread(recoverThread, "RandomDataSourceSelector-recover-thread").start();
}
@Override
public boolean isSame(String name) {
return "random".equalsIgnoreCase(name);
}
@Override
public DataSource get() {
Map<String, DataSource> dataSourceMap = getDataSourceMap();
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
return null;
}
Collection<DataSource> targetDataSourceSet;
if (blacklist == null || blacklist.isEmpty() || blacklist.size() >= dataSourceMap.size()) {
targetDataSourceSet = dataSourceMap.values();
} else {
targetDataSourceSet = new HashSet<DataSource>(dataSourceMap.values());
for (DataSource b : blacklist) {
targetDataSourceSet.remove(b);
}
}
DataSource[] dataSources = targetDataSourceSet.toArray(new DataSource[] {});
if (dataSources != null && dataSources.length > 0) {
return dataSources[random.nextInt(targetDataSourceSet.size())];
}
return null;
}
@Override
public void setTarget(String name) {
// do nothing
}
public Map<String, DataSource> getDataSourceMap() {
if (highAvailableDataSource != null) {
return highAvailableDataSource.getDataSourceMap();
}
return new HashMap<String, DataSource>();
}
public List<DataSource> getBlacklist() {
return blacklist;
}
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 (dataSource != null && blacklist.contains(dataSource)) {
blacklist.remove(dataSource);
if (dataSource instanceof DruidDataSource) {
((DruidDataSource) dataSource).setTestOnReturn(highAvailableDataSource.isTestOnReturn());
}
}
}
public RandomDataSourceValidateThread getValidateThread() {
return validateThread;
}
public RandomDataSourceRecoverThread getRecoverThread() {
return recoverThread;
}
}
@@ -0,0 +1,114 @@
package com.alibaba.druid.pool.ha.selector;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 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.
*
* @author DigitalSonic
*/
public class RandomDataSourceValidateThread implements Runnable {
private final static Log LOG = LogFactory.getLog(RandomDataSourceValidateThread.class);
private int sleepSeconds = 30;
private int blacklistThreshold = 3;
private RandomDataSourceSelector selector;
private Map<String, Integer> errorCounts = new HashMap<String, Integer>();
public RandomDataSourceValidateThread(RandomDataSourceSelector selector) {
this.selector = selector;
}
@Override
public void run() {
while (true) {
if (selector != null) {
checkAllDataSources();
maintainBlacklist();
}
try {
Thread.sleep(sleepSeconds * 1000);
} catch (InterruptedException e) {
// ignore
}
}
}
private void maintainBlacklist() {
Map<String, DataSource> dataSourceMap = selector.getDataSourceMap();
for (Map.Entry<String, Integer> e : errorCounts.entrySet()) {
if (e.getValue() <= 0) {
selector.removeBlacklist(dataSourceMap.get(e.getKey()));
} else if (e.getValue() >= blacklistThreshold) {
LOG.warn("Adding " + e.getKey() + " to blacklist.");
selector.addBlacklist(dataSourceMap.get(e.getKey()));
}
}
}
private void checkAllDataSources() {
Map<String, DataSource> dataSourceMap = selector.getDataSourceMap();
for (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);
}
}
}
private boolean check(String name, DruidDataSource dataSource) {
boolean result = true;
Driver driver = dataSource.getRawDriver();
Properties info = dataSource.getConnectProperties();
String url = dataSource.getRawJdbcUrl();
Connection conn = null;
try {
LOG.debug("Validating " + name + " every " + sleepSeconds + " seconds.");
conn = driver.connect(url, info);
dataSource.validateConnection(conn);
} catch (SQLException e) {
LOG.warn("Validation FAILED for " + name + ". Exception: " + e.getMessage());
result = false;
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
LOG.error("Can not close connection for HostAndPort Validation.", e);
}
}
}
return result;
}
public void setSleepSeconds(int sleepSeconds) {
this.sleepSeconds = sleepSeconds;
}
public void setBlacklistThreshold(int blacklistThreshold) {
this.blacklistThreshold = blacklistThreshold;
}
}
@@ -0,0 +1,22 @@
package com.alibaba.druid.pool.ha;
import org.junit.Test;
import javax.sql.DataSource;
import java.util.Map;
import static org.junit.Assert.*;
public class DataSourceCreatorTest {
@Test
public void createMap() throws Exception {
HighAvailableDataSource highAvailableDataSource = new HighAvailableDataSource();
String file = "/com/alibaba/druid/pool/ha/ha-datasource.properties";
DataSourceCreator creator = new DataSourceCreator(file);
Map<String, DataSource> map = creator.createMap(highAvailableDataSource);
assertEquals(2, map.size());
assertNotNull(map.get("foo"));
assertNotNull(map.get("bar"));
}
}
@@ -0,0 +1,47 @@
package com.alibaba.druid.pool.ha;
import com.alibaba.druid.mock.MockConnection;
import org.junit.Test;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.*;
public class HighAvailableDataSourceTest {
@Test
public void testByNameGet() throws Exception {
HighAvailableDataSource haDataSource = new HighAvailableDataSource();
Map<String, DataSource> map = new HashMap<String, DataSource>();
map.put("foo", new MockDataSource("foo"));
map.put("bar", new MockDataSource("bar"));
haDataSource.setDataSourceMap(map);
haDataSource.setSelector("byname");
haDataSource.init();
haDataSource.setTargetDataSource("foo");
assertNotNull(haDataSource.getConnection());
}
@Test
public void testRandomGet() throws Exception {
HighAvailableDataSource haDataSource = new HighAvailableDataSource();
Map<String, DataSource> map = new HashMap<String, DataSource>();
map.put("foo", new MockDataSource("foo"));
map.put("bar", new MockDataSource("bar"));
int[] count = new int[2];
haDataSource.setDataSourceMap(map);
for (int i = 0; i < 100; i++) {
String name = ((MockConnection)haDataSource.getConnection()).getUrl();
if (name.equalsIgnoreCase("foo")) {
count[0]++;
} else if (name.equalsIgnoreCase("bar")) {
count[1]++;
}
}
assertTrue(count[0] > 0);
assertTrue(count[1] > 0);
}
}
@@ -0,0 +1,57 @@
package com.alibaba.druid.pool.ha;
import com.alibaba.druid.mock.MockConnection;
import com.alibaba.druid.pool.WrapperAdapter;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class MockDataSource extends WrapperAdapter implements DataSource {
private String name;
public MockDataSource(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public Connection getConnection() throws SQLException {
return new MockConnection(null, name, null);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
@@ -0,0 +1,64 @@
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.*;
public class NamedDataSourceSelectorTest {
private Map<String, DataSource> dataSourceMap;
private HighAvailableDataSource dataSource;
@Before
public void setUp() {
dataSourceMap = new HashMap<String, DataSource>();
dataSourceMap.put("foo", new MockDataSource("foo"));
dataSourceMap.put("bar", new MockDataSource("bar"));
dataSource = new HighAvailableDataSource();
dataSource.setDataSourceMap(dataSourceMap);
}
@After
public void tearDown() {
dataSourceMap = null;
dataSource = null;
}
@Test
public void testEmptyMap() {
dataSourceMap.clear();
NamedDataSourceSelector selector = new NamedDataSourceSelector(null);
assertNull(selector.get());
selector = new NamedDataSourceSelector(dataSource);
assertNull(selector.get());
}
@Test
public void testOnlyOne() {
dataSourceMap.remove("foo");
NamedDataSourceSelector selector = new NamedDataSourceSelector(dataSource);
for (int i = 0; i < 50; i++) {
assertEquals("bar", ((MockDataSource)selector.get()).getName());
}
}
@Test
public void testGetByName() throws Exception {
NamedDataSourceSelector selector = new NamedDataSourceSelector(dataSource);
assertNull(selector.get());
selector.setTarget("foo");
assertEquals("foo", ((MockDataSource)selector.get()).getName());
selector.resetDataSourceName();
selector.setDefaultName("bar");
assertEquals("bar", ((MockDataSource)selector.get()).getName());
}
}
@@ -0,0 +1,55 @@
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.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;
}
@Test
public void testRandomGet() throws Exception {
RandomDataSourceSelector selector = new RandomDataSourceSelector(dataSource);
int[] count = new int[10];
for (int i = 0; i < 100; i++) {
MockDataSource dataSource = (MockDataSource) selector.get();
count[Integer.parseInt(dataSource.getName())]++;
}
for (int i : count) {
assertTrue(i > 0);
}
}
@Test
public void testOneDataSourceFail() {
}
}
@@ -0,0 +1,86 @@
package com.alibaba.druid.pool.ha.selector;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.ha.HighAvailableDataSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.sql.DataSource;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
public class RandomDataSourceSelectorWithValidationTest {
private HighAvailableDataSource highAvailableDataSource;
@Before
public void setUp() throws Exception {
highAvailableDataSource = new HighAvailableDataSource();
String file = "/com/alibaba/druid/pool/ha/ha-datasource.properties";
highAvailableDataSource.setDataSourceFile(file);
highAvailableDataSource.init();
}
@After
public void tearDown() {
highAvailableDataSource = null;
}
@Test
public void testOneDataSourceFailAndRecover() throws Exception {
((RandomDataSourceSelector) highAvailableDataSource.getSelector()).getValidateThread().setSleepSeconds(3);
((RandomDataSourceSelector) highAvailableDataSource.getSelector()).getRecoverThread().setSleepSeconds(3);
Thread.sleep(30 * 1000);
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.getSelector().get());
}
dataSource.setValidationQuery(null);
Thread.sleep(4 * 1000);
assertFalse(dataSource.isTestOnReturn());
int count = 0;
for (int i = 0; i < 100; i++) {
if (dataSource == highAvailableDataSource.getSelector().get()) {
count++;
}
}
assertTrue(count > 0);
}
@Test
public void testAllDataSourceFail() throws Exception {
((RandomDataSourceSelector) highAvailableDataSource.getSelector()).getValidateThread().setSleepSeconds(3);
((RandomDataSourceSelector) highAvailableDataSource.getSelector()).getRecoverThread().setSleepSeconds(3);
Thread.sleep(30 * 1000);
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);
assertTrue(foo.isTestOnReturn());
assertTrue(bar.isTestOnReturn());
int[] count = new int[2];
for (int i = 0; i < 100; i++) {
DataSource dataSource = highAvailableDataSource.getSelector().get();
if (foo == dataSource) {
count[0]++;
} else if (bar == dataSource) {
count[1]++;
}
}
assertTrue(count[0] > 0);
assertTrue(count[1] > 0);
}
}
@@ -0,0 +1,2 @@
foo.url=jdbc:derby:memory:foo;create=true
bar.url=jdbc:derby:memory:bar;create=true