From 5c50f0f37a2581aec3823138011ece2a1856b9b9 Mon Sep 17 00:00:00 2001 From: DigitalSonic Date: Wed, 11 Apr 2018 09:02:06 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E5=A2=9E=E5=8A=A0JDBC=20URL=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D=E7=AC=A6=E6=9B=BF=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根据配置文件,对URL中的占位符进行替换,同时提供定时健康检查的功能,可剔除不可用节点。 --- .../com/alibaba/druid/filter/FilterChain.java | 2 + .../alibaba/druid/filter/FilterChainImpl.java | 4 + .../url/ConnectionConnectFilterChainImpl.java | 65 ++++++++ .../druid/filter/url/HostAndPortHolder.java | 141 ++++++++++++++++++ .../url/HostAndPortValidatorThread.java | 105 +++++++++++++ .../url/JdbcUrlHostAndPortReplaceFilter.java | 67 +++++++++ .../filter/url/HostAndPortHolderTest.java | 63 ++++++++ .../JdbcUrlHostAndPortReplaceFilterTest.java | 50 +++++++ .../alibaba/druid/filter/url/dataSource.xml | 29 ++++ .../druid/filter/url/datasource.properties | 4 + 10 files changed, 530 insertions(+) create mode 100644 src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java create mode 100644 src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java create mode 100644 src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java create mode 100644 src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java create mode 100644 src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java create mode 100644 src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java create mode 100644 src/test/resources/com/alibaba/druid/filter/url/dataSource.xml create mode 100644 src/test/resources/com/alibaba/druid/filter/url/datasource.properties diff --git a/src/main/java/com/alibaba/druid/filter/FilterChain.java b/src/main/java/com/alibaba/druid/filter/FilterChain.java index 681b211ff8..9b63e082d7 100644 --- a/src/main/java/com/alibaba/druid/filter/FilterChain.java +++ b/src/main/java/com/alibaba/druid/filter/FilterChain.java @@ -62,6 +62,8 @@ public interface FilterChain { int getFilterSize(); + int getPos(); + FilterChain cloneChain(); T unwrap(Wrapper wrapper, java.lang.Class iface) throws java.sql.SQLException; diff --git a/src/main/java/com/alibaba/druid/filter/FilterChainImpl.java b/src/main/java/com/alibaba/druid/filter/FilterChainImpl.java index e1279e3d24..c9542f7c5f 100644 --- a/src/main/java/com/alibaba/druid/filter/FilterChainImpl.java +++ b/src/main/java/com/alibaba/druid/filter/FilterChainImpl.java @@ -92,6 +92,10 @@ public class FilterChainImpl implements FilterChain { return filterSize; } + public int getPos() { + return pos; + } + public void reset() { pos = 0; } diff --git a/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java b/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java new file mode 100644 index 0000000000..9892dadc97 --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java @@ -0,0 +1,65 @@ +package com.alibaba.druid.filter.url; + +import com.alibaba.druid.filter.FilterChain; +import com.alibaba.druid.filter.FilterChainImpl; +import com.alibaba.druid.proxy.jdbc.ConnectionProxy; +import com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl; +import com.alibaba.druid.proxy.jdbc.DataSourceProxy; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; +import java.util.Properties; + +public class ConnectionConnectFilterChainImpl extends FilterChainImpl { + private final static Log LOG = LogFactory.getLog(ConnectionConnectFilterChainImpl.class); + + public ConnectionConnectFilterChainImpl(FilterChain filterChain) { + this(filterChain.getDataSource(), filterChain.getPos()); + } + + public ConnectionConnectFilterChainImpl(DataSourceProxy dataSource, int pos) { + super(dataSource, pos); + } + + @Override + public ConnectionProxy connection_connect(Properties info) throws SQLException { + if (this.pos < getFilterSize()) { + return getFilters().get(pos++).connection_connect(this, info); + } + + DataSourceProxy dataSource = getDataSource(); + Driver driver = dataSource.getRawDriver(); + String url = dataSource.getRawJdbcUrl(); + + url = processUrl(url); + + Connection nativeConnection = driver.connect(url, info); + + if (nativeConnection == null) { + return null; + } + + return new ConnectionProxyImpl(dataSource, nativeConnection, info, dataSource.createConnectionId()); + } + + protected String processUrl(String url) { + if (url == null || (url != null && + (url.indexOf("${") == -1 || url.indexOf("}") == -1))) { + return url; + } + + String processedUrl = url; + String placeHolder = url.substring(url.indexOf("${") + 2, url.indexOf("}")); + String value = HostAndPortHolder.getInstance().get(placeHolder); + if (value != null) { + LOG.info("PlaceHolder ${" + placeHolder + "} is found in url, it will be replace by " + value); + processedUrl = url.replace("${" + placeHolder + "}", value); + } else { + LOG.info("PlaceHolder ${" + placeHolder + "} is found in url, but no value is bind."); + } + return processedUrl; + } +} diff --git a/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java b/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java new file mode 100644 index 0000000000..5ac334075d --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java @@ -0,0 +1,141 @@ +package com.alibaba.druid.filter.url; + +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Random; + +public class HostAndPortHolder { + private static final Log LOG = LogFactory.getLog(ConnectionConnectFilterChainImpl.class); + + public static final String UNAVAILABLE = "UNAVAILABLE_HOST"; + + private static HostAndPortHolder instance = new HostAndPortHolder(); + + private Map> holder = new HashMap>(); + private List blacklist = new ArrayList(); + + private Random random = new Random(); + + public static HostAndPortHolder getInstance() { + return instance; + } + + public static void loadFromProperties(String file) { + Properties properties = new Properties(); + if (file == null) { + return; + } + InputStream is; + try { + LOG.debug("Trying to load " + file + " from FileSystem."); + is = new FileInputStream(file); + } catch(FileNotFoundException e) { + LOG.debug("Trying to load " + file + " from Classpath."); + is = HostAndPortHolder.class.getResourceAsStream(file); + } + if (is != null) { + try { + properties.load(is); + } 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!"); + } + + for (Object k : properties.keySet()) { + String v = properties.getProperty((String) k); + if (v != null && v.trim().length() > 0) { + LOG.info("Loading placeholder " + k + " = [" + v + "]"); + List values = Arrays.asList(v.split(";")); + getInstance().putHostAndPorts((String) k, values); + } + } + + } + + private HostAndPortHolder() { + } + + public void putHostAndPorts(String key, List values) { + if (key == null || values == null || values.isEmpty()) { + return; + } + holder.put(key, values); + } + + public Map> getHolder() { + return holder; + } + + public void clearBlacklist() { + blacklist.clear(); + } + + public void addBlacklist(String hostAndPort) { + blacklist.add(hostAndPort); + } + + public void removeBlacklist(String hostAndPort) { + if (hostAndPort == null) { + return; + } + blacklist.remove(hostAndPort); + } + + public int getBlacklistSize() { + return blacklist.size(); + } + + public String get(String name) { + if (name == null || !holder.containsKey(name)) { + LOG.warn("Can not find placeholder " + name + "."); + return UNAVAILABLE; + } + + List hostAndPorts = holder.get(name); + int idx = random.nextInt(hostAndPorts.size()); + String returnValue = hostAndPorts.get(idx); + String hostAndPort = returnValue; + for (int i = 0; i < hostAndPorts.size(); i++) { + if (isAvailable(hostAndPort)) { + returnValue = hostAndPort; + break; + } + if (++idx >= hostAndPorts.size()) { + idx = 0; + } + hostAndPort = hostAndPorts.get(idx); + } + return returnValue; + } + + public List getAll(String name) { + if (name == null || !holder.containsKey(name)) { + return new ArrayList(); + } + return holder.get(name); + } + + private boolean isAvailable(String hostAndPort) { + return !blacklist.contains(hostAndPort); + } +} diff --git a/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java b/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java new file mode 100644 index 0000000000..05f426b685 --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java @@ -0,0 +1,105 @@ +package com.alibaba.druid.filter.url; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; +import java.util.List; +import java.util.Properties; + +public class HostAndPortValidatorThread implements Runnable { + private final static Log LOG = LogFactory.getLog(HostAndPortValidatorThread.class); + private DruidDataSource dataSource; + private int sleepSeconds = 30; + private boolean modifyTestFlags = false; + private boolean oldTestOnBorrow = false; + private boolean oldTestOnReturn = false; + + @Override + public void run() { + oldTestOnBorrow = dataSource.isTestOnBorrow(); + oldTestOnReturn = dataSource.isTestOnReturn(); + while (true) { + try { + check(); + } catch(Exception e) { + LOG.error("Validation Failed!", e); + } finally { + try { + Thread.sleep(sleepSeconds * 1000); + } catch (InterruptedException e) { + // ignore + } + } + } + } + + protected void check() { + Driver driver = dataSource.getRawDriver(); + Properties info = dataSource.getConnectProperties(); + String url = dataSource.getRawJdbcUrl(); + if (url == null || (url != null && + (url.indexOf("${") == -1 || url.indexOf("}") == -1))) { + return; + } + + String placeHolder = url.substring(url.indexOf("${") + 2, url.indexOf("}")); + List values = HostAndPortHolder.getInstance().getAll(placeHolder); + for (String v : values) { + Connection conn = null; + try { + String realUrl = url.replace("${" + placeHolder + "}", v); + conn = driver.connect(realUrl, info); + dataSource.validateConnection(conn); + HostAndPortHolder.getInstance().removeBlacklist(v); + } catch (SQLException e) { + LOG.warn("Validation FAILED for " + v, e); + HostAndPortHolder.getInstance().addBlacklist(v); + modifyFlagsIfNeeded(); + } finally { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + LOG.error("Can not close connection for HostAndPort Validation.", e); + } + } + } + } + if (HostAndPortHolder.getInstance().getBlacklistSize() == 0) { + resetFlagsIfNeeded(); + } + } + + private void resetFlagsIfNeeded() { + if (modifyTestFlags) { + dataSource.setTestOnBorrow(oldTestOnBorrow); + dataSource.setTestOnReturn(oldTestOnReturn); + } + } + + private void modifyFlagsIfNeeded() { + if (!modifyTestFlags) { + return; + } + if (oldTestOnReturn || oldTestOnBorrow) { + dataSource.setTestOnBorrow(true); + dataSource.setTestOnReturn(true); + } + } + + public void setDataSource(DruidDataSource dataSource) { + this.dataSource = dataSource; + } + + public void setSleepSeconds(int sleepSeconds) { + this.sleepSeconds = sleepSeconds; + } + + public void setModifyTestFlags(boolean modifyTestFlags) { + this.modifyTestFlags = modifyTestFlags; + } +} diff --git a/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java b/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java new file mode 100644 index 0000000000..b51d22e088 --- /dev/null +++ b/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java @@ -0,0 +1,67 @@ +package com.alibaba.druid.filter.url; + +import com.alibaba.druid.filter.FilterAdapter; +import com.alibaba.druid.filter.FilterChain; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.proxy.jdbc.ConnectionProxy; +import com.alibaba.druid.proxy.jdbc.DataSourceProxy; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; + +import java.sql.SQLException; +import java.util.Properties; + +public class JdbcUrlHostAndPortReplaceFilter extends FilterAdapter { + private final static Log LOG = LogFactory.getLog(HostAndPortValidatorThread.class); + + public static final String DEFAULT_FILE = "/druid-datasource.properties"; + + private String file = DEFAULT_FILE; + + private int healthCheckIntervalSeconds = 0; + private HostAndPortValidatorThread validatorThread; + private boolean isValidatorThreadStarted = false; + + public void init() { + if (file == null || file.trim().length() == 0) { + file = DEFAULT_FILE; + } + HostAndPortHolder.loadFromProperties(file); + } + + @Override + public ConnectionProxy connection_connect(FilterChain chain, Properties info) throws SQLException { + startValidationThreadIfNeeded(chain); + return super.connection_connect(new ConnectionConnectFilterChainImpl(chain), info); + } + + private void startValidationThreadIfNeeded(FilterChain chain) { + if (isValidatorThreadStarted || healthCheckIntervalSeconds <= 0) { + return; + } + DataSourceProxy dataSource = chain.getDataSource(); + if (dataSource instanceof DruidDataSource) { + if (validatorThread == null) { + validatorThread = new HostAndPortValidatorThread(); + validatorThread.setSleepSeconds(healthCheckIntervalSeconds); + } + validatorThread.setDataSource((DruidDataSource) dataSource); + new Thread(validatorThread).start(); + isValidatorThreadStarted = true; + } else { + LOG.warn("The DataSource is not a DruidDataSource, ignore HostAndPortValidator."); + } + } + + public void setFile(String file) { + this.file = file; + } + + public void setHealthCheckIntervalSeconds(int healthCheckIntervalSeconds) { + this.healthCheckIntervalSeconds = healthCheckIntervalSeconds; + } + + public void setValidatorThread(HostAndPortValidatorThread validatorThread) { + this.validatorThread = validatorThread; + } +} diff --git a/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java b/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java new file mode 100644 index 0000000000..e177c9a581 --- /dev/null +++ b/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java @@ -0,0 +1,63 @@ +package com.alibaba.druid.filter.url; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +public class HostAndPortHolderTest { + @Before + public void setUp() { + HostAndPortHolder.loadFromProperties("/com/alibaba/druid/filter/url/datasource.properties"); + } + + @After + public void tearDown() { + HostAndPortHolder.getInstance().clearBlacklist(); + } + + @Test + public void testLoadFromProperties() { + Map> holder = HostAndPortHolder.getInstance().getHolder(); + assertEquals(3, holder.size()); + assertEquals("127.0.0.1:3066", holder.get("datasource.foo").get(0)); + assertEquals("192.168.0.1:3066", holder.get("datasource.foo").get(1)); + assertFalse(holder.containsKey("datasource.empty")); + } + + @Test + public void testRandomGet() { + boolean[] flags = new boolean[] { false, false }; + HostAndPortHolder holder = HostAndPortHolder.getInstance(); + for (int i = 0; i < 10; i++) { + String v = holder.get("datasource.foo"); + if (v.equals("127.0.0.1:3066")) { + flags[0] = true; + } else if (v.equals("192.168.0.1:3066")) { + flags[1] = true; + } + } + assertTrue(flags[0] && flags[1]); + } + + @Test + public void testGetOne() { + HostAndPortHolder holder = HostAndPortHolder.getInstance(); + String value = holder.get("datasource.foo"); + assertTrue(value.equals("127.0.0.1:3066") || value.equals("192.168.0.1:3066")); + assertEquals(HostAndPortHolder.UNAVAILABLE, holder.get("UNAVAILABLE")); + } + + @Test + public void testBlackList() { + HostAndPortHolder holder = HostAndPortHolder.getInstance(); + holder.addBlacklist("192.168.0.1:3066"); + for (int i = 0; i < 100; i++) { + assertNotEquals("192.168.0.1:3066", holder.get("datasource.foo")); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java b/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java new file mode 100644 index 0000000000..6e1d4fd409 --- /dev/null +++ b/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java @@ -0,0 +1,50 @@ +package com.alibaba.druid.filter.url; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.DruidPooledConnection; +import com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl; +import org.apache.derby.impl.jdbc.EmbedConnection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:com/alibaba/druid/filter/url/dataSource.xml") +public class JdbcUrlHostAndPortReplaceFilterTest { + @Autowired + private DruidDataSource dataSource; + + @Test + public void testConnectWithDifferentPlaceholderValue() throws Exception { + int[] count = new int[] { 0,0 }; + List connectionList = new ArrayList(); + + for (int i = 0; i < 10; i++) { + DruidPooledConnection conn = dataSource.getConnection(); + EmbedConnection embed = ((EmbedConnection)((ConnectionProxyImpl)conn.getConnection()).getRawObject()); + String connName = embed.toString(); + + if (connName.contains("foo")) { + count[0]++; + } else if (connName.contains("bar")) { + count[1]++; + } else { + fail("NOT foo or bar"); + } + connectionList.add(conn); + } + assertTrue(count[0] > 0 && count[1] >0); + for (Connection c : connectionList) { + c.close(); + } + } + +} \ No newline at end of file diff --git a/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml b/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml new file mode 100644 index 0000000000..076ab68e7d --- /dev/null +++ b/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/com/alibaba/druid/filter/url/datasource.properties b/src/test/resources/com/alibaba/druid/filter/url/datasource.properties new file mode 100644 index 0000000000..4e05753ccd --- /dev/null +++ b/src/test/resources/com/alibaba/druid/filter/url/datasource.properties @@ -0,0 +1,4 @@ +datasource.foo=127.0.0.1:3066;192.168.0.1:3066 +datasource.bar=198.168.1.1:3166;192.168.1.2:3166 +datasource.empty +datasource.test=foo;bar \ No newline at end of file From cdefafd232cd3a0ff476d0204cf32c8ba2b9d4b5 Mon Sep 17 00:00:00 2001 From: DigitalSonic Date: Thu, 12 Apr 2018 23:14:59 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8B=BC=E5=86=99?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../druid/filter/config/ConfigFilter.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/alibaba/druid/filter/config/ConfigFilter.java b/src/main/java/com/alibaba/druid/filter/config/ConfigFilter.java index 2b7d75d6dd..fbd6280f95 100644 --- a/src/main/java/com/alibaba/druid/filter/config/ConfigFilter.java +++ b/src/main/java/com/alibaba/druid/filter/config/ConfigFilter.java @@ -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)) { From 64be5cc70378e351da53782623ae6409a8f7c998 Mon Sep 17 00:00:00 2001 From: DigitalSonic Date: Thu, 12 Apr 2018 23:16:18 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=EF=BC=8C=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../url/ConnectionConnectFilterChainImpl.java | 5 ++ .../druid/filter/url/HostAndPortHolder.java | 15 ++++- .../url/HostAndPortValidatorThread.java | 57 ++++++++++++------- .../url/JdbcUrlHostAndPortReplaceFilter.java | 25 +++++++- .../filter/url/HostAndPortHolderTest.java | 19 ++++++- .../url/HostAndPortValidatorThreadTest.java | 40 +++++++++++++ .../JdbcUrlHostAndPortReplaceFilterTest.java | 9 ++- .../alibaba/druid/filter/url/dataSource.xml | 5 +- 8 files changed, 142 insertions(+), 33 deletions(-) create mode 100644 src/test/java/com/alibaba/druid/filter/url/HostAndPortValidatorThreadTest.java diff --git a/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java b/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java index 9892dadc97..f296a29ac5 100644 --- a/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java +++ b/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java @@ -13,6 +13,11 @@ import java.sql.Driver; import java.sql.SQLException; import java.util.Properties; +/** + * FilterChain to create new connection with placeholder in URL + * + * @author DigitalSonic + */ public class ConnectionConnectFilterChainImpl extends FilterChainImpl { private final static Log LOG = LogFactory.getLog(ConnectionConnectFilterChainImpl.class); diff --git a/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java b/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java index 5ac334075d..a76662f811 100644 --- a/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java +++ b/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java @@ -14,6 +14,11 @@ import java.util.Map; import java.util.Properties; import java.util.Random; +/** + * A singleton object to hold Host & Port information. + * + * @author DigitalSonic + */ public class HostAndPortHolder { private static final Log LOG = LogFactory.getLog(ConnectionConnectFilterChainImpl.class); @@ -30,18 +35,22 @@ public class HostAndPortHolder { return instance; } - public static void loadFromProperties(String file) { + public static void loadProperties(String file) { Properties properties = new Properties(); if (file == null) { return; } - InputStream is; + 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."); - is = HostAndPortHolder.class.getResourceAsStream(file); + try { + is = HostAndPortHolder.class.getResourceAsStream(file); + } catch (Exception ex) { + LOG.warn("Can not load resource " + file, ex); + } } if (is != null) { try { diff --git a/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java b/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java index 05f426b685..bc460d6315 100644 --- a/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java +++ b/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java @@ -10,6 +10,12 @@ import java.sql.SQLException; import java.util.List; import java.util.Properties; +/** + * A thread to run the validation on each host:port. + * If some host:port is failed in the validation, we can set test flags in DruidDataSource. + * + * @author DigitalSonic + */ public class HostAndPortValidatorThread implements Runnable { private final static Log LOG = LogFactory.getLog(HostAndPortValidatorThread.class); private DruidDataSource dataSource; @@ -38,8 +44,6 @@ public class HostAndPortValidatorThread implements Runnable { } protected void check() { - Driver driver = dataSource.getRawDriver(); - Properties info = dataSource.getConnectProperties(); String url = dataSource.getRawJdbcUrl(); if (url == null || (url != null && (url.indexOf("${") == -1 || url.indexOf("}") == -1))) { @@ -49,31 +53,40 @@ public class HostAndPortValidatorThread implements Runnable { String placeHolder = url.substring(url.indexOf("${") + 2, url.indexOf("}")); List values = HostAndPortHolder.getInstance().getAll(placeHolder); for (String v : values) { - Connection conn = null; - try { - String realUrl = url.replace("${" + placeHolder + "}", v); - conn = driver.connect(realUrl, info); - dataSource.validateConnection(conn); - HostAndPortHolder.getInstance().removeBlacklist(v); - } catch (SQLException e) { - LOG.warn("Validation FAILED for " + v, e); - HostAndPortHolder.getInstance().addBlacklist(v); - modifyFlagsIfNeeded(); - } finally { - if (conn != null) { - try { - conn.close(); - } catch (SQLException e) { - LOG.error("Can not close connection for HostAndPort Validation.", e); - } - } - } + validateConnection(dataSource, placeHolder, v); } if (HostAndPortHolder.getInstance().getBlacklistSize() == 0) { resetFlagsIfNeeded(); } } + private void validateConnection(DruidDataSource dataSource, String placeHolder, String target) { + Driver driver = dataSource.getRawDriver(); + Properties info = dataSource.getConnectProperties(); + String url = dataSource.getRawJdbcUrl(); + Connection conn = null; + + try { + LOG.debug("Validating " + target + " every " + sleepSeconds + " seconds."); + String realUrl = url.replace("${" + placeHolder + "}", target); + conn = driver.connect(realUrl, info); + dataSource.validateConnection(conn); + HostAndPortHolder.getInstance().removeBlacklist(target); + } catch (SQLException e) { + LOG.warn("Validation FAILED for " + target, e); + HostAndPortHolder.getInstance().addBlacklist(target); + modifyFlagsIfNeeded(); + } finally { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + LOG.error("Can not close connection for HostAndPort Validation.", e); + } + } + } + } + private void resetFlagsIfNeeded() { if (modifyTestFlags) { dataSource.setTestOnBorrow(oldTestOnBorrow); @@ -85,7 +98,7 @@ public class HostAndPortValidatorThread implements Runnable { if (!modifyTestFlags) { return; } - if (oldTestOnReturn || oldTestOnBorrow) { + if (!oldTestOnReturn || !oldTestOnBorrow) { dataSource.setTestOnBorrow(true); dataSource.setTestOnReturn(true); } diff --git a/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java b/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java index b51d22e088..583105146d 100644 --- a/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java +++ b/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java @@ -11,22 +11,35 @@ import com.alibaba.druid.support.logging.LogFactory; import java.sql.SQLException; import java.util.Properties; +/** + * The filter to replace the placeholder in JDBC URL. + * + * @author DigitalSonic + */ public class JdbcUrlHostAndPortReplaceFilter extends FilterAdapter { private final static Log LOG = LogFactory.getLog(HostAndPortValidatorThread.class); - public static final String DEFAULT_FILE = "/druid-datasource.properties"; + public static final String DEFAULT_FILE = "datasource.properties"; private String file = DEFAULT_FILE; private int healthCheckIntervalSeconds = 0; private HostAndPortValidatorThread validatorThread; + private Thread runningValidatorThread; private boolean isValidatorThreadStarted = false; + private boolean modifyTestFlags = false; public void init() { if (file == null || file.trim().length() == 0) { file = DEFAULT_FILE; } - HostAndPortHolder.loadFromProperties(file); + HostAndPortHolder.loadProperties(file); + } + + public void close() { + if (runningValidatorThread != null) { + runningValidatorThread.interrupt(); + } } @Override @@ -44,9 +57,11 @@ public class JdbcUrlHostAndPortReplaceFilter extends FilterAdapter { if (validatorThread == null) { validatorThread = new HostAndPortValidatorThread(); validatorThread.setSleepSeconds(healthCheckIntervalSeconds); + validatorThread.setModifyTestFlags(modifyTestFlags); } validatorThread.setDataSource((DruidDataSource) dataSource); - new Thread(validatorThread).start(); + runningValidatorThread = new Thread(validatorThread); + runningValidatorThread.start(); isValidatorThreadStarted = true; } else { LOG.warn("The DataSource is not a DruidDataSource, ignore HostAndPortValidator."); @@ -64,4 +79,8 @@ public class JdbcUrlHostAndPortReplaceFilter extends FilterAdapter { public void setValidatorThread(HostAndPortValidatorThread validatorThread) { this.validatorThread = validatorThread; } + + public void setModifyTestFlags(boolean modifyTestFlags) { + this.modifyTestFlags = modifyTestFlags; + } } diff --git a/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java b/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java index e177c9a581..b4e2b2f256 100644 --- a/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java +++ b/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java @@ -7,12 +7,20 @@ import org.junit.Test; import java.util.List; import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +/** + * Test class for HostAndPortHolder + * + * @author DigitalSonic + */ public class HostAndPortHolderTest { @Before public void setUp() { - HostAndPortHolder.loadFromProperties("/com/alibaba/druid/filter/url/datasource.properties"); + HostAndPortHolder.loadProperties("/com/alibaba/druid/filter/url/datasource.properties"); } @After @@ -20,6 +28,13 @@ public class HostAndPortHolderTest { HostAndPortHolder.getInstance().clearBlacklist(); } + @Test + public void testLoadFromFileNotExisted() { + int size = HostAndPortHolder.getInstance().getHolder().size(); + HostAndPortHolder.loadProperties("FILE_NOT_FOUND"); + assertEquals(size, HostAndPortHolder.getInstance().getHolder().size()); + } + @Test public void testLoadFromProperties() { Map> holder = HostAndPortHolder.getInstance().getHolder(); diff --git a/src/test/java/com/alibaba/druid/filter/url/HostAndPortValidatorThreadTest.java b/src/test/java/com/alibaba/druid/filter/url/HostAndPortValidatorThreadTest.java new file mode 100644 index 0000000000..02b0c4c440 --- /dev/null +++ b/src/test/java/com/alibaba/druid/filter/url/HostAndPortValidatorThreadTest.java @@ -0,0 +1,40 @@ +package com.alibaba.druid.filter.url; + +import com.alibaba.druid.pool.DruidDataSource; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Test class for HostAndPortValidatorThread + * + * @author DigitalSonic + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:com/alibaba/druid/filter/url/dataSource.xml") +public class HostAndPortValidatorThreadTest { + @Autowired + private DruidDataSource dataSource; + + @Test + public void testValidateFail() throws Exception { + assertEquals(0, HostAndPortHolder.getInstance().getBlacklistSize()); + dataSource.setValidationQuery("select xxx from foo"); + Thread.sleep(20 * 1000); + assertTrue(HostAndPortHolder.getInstance().getBlacklistSize() > 0); + assertTrue(dataSource.isTestOnBorrow()); + assertTrue(dataSource.isTestOnReturn()); + + dataSource.setValidationQuery(null); + Thread.sleep(20 * 1000); + assertEquals(0, HostAndPortHolder.getInstance().getBlacklistSize()); + assertFalse(dataSource.isTestOnBorrow()); + assertFalse(dataSource.isTestOnReturn()); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java b/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java index 6e1d4fd409..f62444b1f4 100644 --- a/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java +++ b/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java @@ -14,8 +14,14 @@ import java.sql.Connection; import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +/** + * Test class for JdbcUrlHostAndPortReplaceFilter + * + * @author DigitalSonic + */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:com/alibaba/druid/filter/url/dataSource.xml") public class JdbcUrlHostAndPortReplaceFilterTest { @@ -46,5 +52,4 @@ public class JdbcUrlHostAndPortReplaceFilterTest { c.close(); } } - } \ No newline at end of file diff --git a/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml b/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml index 076ab68e7d..ac2c72adfb 100644 --- a/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml +++ b/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml @@ -9,8 +9,11 @@ default-lazy-init="false"> - + + + Date: Sat, 5 May 2018 23:23:46 +0800 Subject: [PATCH 4/5] Add a HA DataSource implementation --- .../druid/pool/ha/DataSourceCreator.java | 155 +++++++ .../pool/ha/HighAvailableDataSource.java | 385 ++++++++++++++++++ .../pool/ha/selector/DataSourceSelector.java | 16 + .../selector/DataSourceSelectorFactory.java | 19 + .../ha/selector/NamedDataSourceSelector.java | 74 ++++ .../RandomDataSourceRecoverThread.java | 74 ++++ .../ha/selector/RandomDataSourceSelector.java | 105 +++++ .../RandomDataSourceValidateThread.java | 114 ++++++ .../druid/pool/ha/DataSourceCreatorTest.java | 22 + .../pool/ha/HighAvailableDataSourceTest.java | 47 +++ .../alibaba/druid/pool/ha/MockDataSource.java | 57 +++ .../selector/NamedDataSourceSelectorTest.java | 64 +++ .../RandomDataSourceSelectorTest.java | 55 +++ ...mDataSourceSelectorWithValidationTest.java | 86 ++++ .../druid/pool/ha/ha-datasource.properties | 2 + 15 files changed, 1275 insertions(+) create mode 100644 src/main/java/com/alibaba/druid/pool/ha/DataSourceCreator.java create mode 100644 src/main/java/com/alibaba/druid/pool/ha/HighAvailableDataSource.java create mode 100644 src/main/java/com/alibaba/druid/pool/ha/selector/DataSourceSelector.java create mode 100644 src/main/java/com/alibaba/druid/pool/ha/selector/DataSourceSelectorFactory.java create mode 100644 src/main/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelector.java create mode 100644 src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceRecoverThread.java create mode 100644 src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelector.java create mode 100644 src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceValidateThread.java create mode 100644 src/test/java/com/alibaba/druid/pool/ha/DataSourceCreatorTest.java create mode 100644 src/test/java/com/alibaba/druid/pool/ha/HighAvailableDataSourceTest.java create mode 100644 src/test/java/com/alibaba/druid/pool/ha/MockDataSource.java create mode 100644 src/test/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelectorTest.java create mode 100644 src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorTest.java create mode 100644 src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorWithValidationTest.java create mode 100644 src/test/resources/com/alibaba/druid/pool/ha/ha-datasource.properties diff --git a/src/main/java/com/alibaba/druid/pool/ha/DataSourceCreator.java b/src/main/java/com/alibaba/druid/pool/ha/DataSourceCreator.java new file mode 100644 index 0000000000..b73be23af3 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/DataSourceCreator.java @@ -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 nameList = new ArrayList(); + + public DataSourceCreator(String file) { + loadProperties(file); + loadNameList(); + } + + public Map createMap(HighAvailableDataSource haDataSource) throws SQLException { + Map map = new ConcurrentHashMap(); + + 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 names = new HashSet(); + 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!"); + } + } +} diff --git a/src/main/java/com/alibaba/druid/pool/ha/HighAvailableDataSource.java b/src/main/java/com/alibaba/druid/pool/ha/HighAvailableDataSource.java new file mode 100644 index 0000000000..e8ef1ccd51 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/HighAvailableDataSource.java @@ -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 proxyFilters = new CopyOnWriteArrayList(); + private PrintWriter logWriter = new PrintWriter(System.out); + // Properties copied from DruidAbstractDataSource END + + private Map dataSourceMap = new ConcurrentHashMap(); + 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 dataSourceMap) { + if (dataSourceMap != null) { + this.dataSourceMap = dataSourceMap; + } + } + + public Map 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 getProxyFilters() { + return proxyFilters; + } + + public void setProxyFilters(List proxyFilters) { + this.proxyFilters = proxyFilters; + } +} diff --git a/src/main/java/com/alibaba/druid/pool/ha/selector/DataSourceSelector.java b/src/main/java/com/alibaba/druid/pool/ha/selector/DataSourceSelector.java new file mode 100644 index 0000000000..af6099dc33 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/selector/DataSourceSelector.java @@ -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); +} diff --git a/src/main/java/com/alibaba/druid/pool/ha/selector/DataSourceSelectorFactory.java b/src/main/java/com/alibaba/druid/pool/ha/selector/DataSourceSelectorFactory.java new file mode 100644 index 0000000000..469e619731 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/selector/DataSourceSelectorFactory.java @@ -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; + } +} diff --git a/src/main/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelector.java b/src/main/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelector.java new file mode 100644 index 0000000000..42d1f04c2a --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelector.java @@ -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 targetDataSourceName = new ThreadLocal(); + 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 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; + } +} diff --git a/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceRecoverThread.java b/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceRecoverThread.java new file mode 100644 index 0000000000..b0a6b52a36 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceRecoverThread.java @@ -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; + } +} diff --git a/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelector.java b/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelector.java new file mode 100644 index 0000000000..71def65eea --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelector.java @@ -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 blacklist = new CopyOnWriteArrayList(); + 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 dataSourceMap = getDataSourceMap(); + if (dataSourceMap == null || dataSourceMap.isEmpty()) { + return null; + } + + Collection targetDataSourceSet; + if (blacklist == null || blacklist.isEmpty() || blacklist.size() >= dataSourceMap.size()) { + targetDataSourceSet = dataSourceMap.values(); + } else { + targetDataSourceSet = new HashSet(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 getDataSourceMap() { + if (highAvailableDataSource != null) { + return highAvailableDataSource.getDataSourceMap(); + } + return new HashMap(); + } + + public List 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; + } +} diff --git a/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceValidateThread.java b/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceValidateThread.java new file mode 100644 index 0000000000..6ccc85e44b --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceValidateThread.java @@ -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 errorCounts = new HashMap(); + + 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 dataSourceMap = selector.getDataSourceMap(); + for (Map.Entry 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 dataSourceMap = selector.getDataSourceMap(); + for (Map.Entry 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; + } +} diff --git a/src/test/java/com/alibaba/druid/pool/ha/DataSourceCreatorTest.java b/src/test/java/com/alibaba/druid/pool/ha/DataSourceCreatorTest.java new file mode 100644 index 0000000000..f3052702b6 --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/DataSourceCreatorTest.java @@ -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 map = creator.createMap(highAvailableDataSource); + assertEquals(2, map.size()); + assertNotNull(map.get("foo")); + assertNotNull(map.get("bar")); + } + +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/pool/ha/HighAvailableDataSourceTest.java b/src/test/java/com/alibaba/druid/pool/ha/HighAvailableDataSourceTest.java new file mode 100644 index 0000000000..28291352c8 --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/HighAvailableDataSourceTest.java @@ -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 map = new HashMap(); + 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 map = new HashMap(); + 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); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/pool/ha/MockDataSource.java b/src/test/java/com/alibaba/druid/pool/ha/MockDataSource.java new file mode 100644 index 0000000000..70341b91a0 --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/MockDataSource.java @@ -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; + } +} diff --git a/src/test/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelectorTest.java b/src/test/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelectorTest.java new file mode 100644 index 0000000000..41e552de1a --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/selector/NamedDataSourceSelectorTest.java @@ -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 dataSourceMap; + private HighAvailableDataSource dataSource; + + @Before + public void setUp() { + dataSourceMap = new HashMap(); + 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()); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorTest.java b/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorTest.java new file mode 100644 index 0000000000..0421ef3c40 --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorTest.java @@ -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 dataSourceMap; + private HighAvailableDataSource dataSource; + + @Before + public void setUp() { + dataSourceMap = new HashMap(); + 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() { + + } + +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorWithValidationTest.java b/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorWithValidationTest.java new file mode 100644 index 0000000000..6a10653abb --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/selector/RandomDataSourceSelectorWithValidationTest.java @@ -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); + } +} diff --git a/src/test/resources/com/alibaba/druid/pool/ha/ha-datasource.properties b/src/test/resources/com/alibaba/druid/pool/ha/ha-datasource.properties new file mode 100644 index 0000000000..50f01147db --- /dev/null +++ b/src/test/resources/com/alibaba/druid/pool/ha/ha-datasource.properties @@ -0,0 +1,2 @@ +foo.url=jdbc:derby:memory:foo;create=true +bar.url=jdbc:derby:memory:bar;create=true \ No newline at end of file From 2b3c50c8b3ad72e098a15082a1951e5447b6e742 Mon Sep 17 00:00:00 2001 From: DigitalSonic Date: Sat, 5 May 2018 23:25:49 +0800 Subject: [PATCH 5/5] Delete URL Filter related codes committed previously --- .../url/ConnectionConnectFilterChainImpl.java | 70 -------- .../druid/filter/url/HostAndPortHolder.java | 150 ------------------ .../url/HostAndPortValidatorThread.java | 118 -------------- .../url/JdbcUrlHostAndPortReplaceFilter.java | 86 ---------- .../filter/url/HostAndPortHolderTest.java | 78 --------- .../url/HostAndPortValidatorThreadTest.java | 40 ----- .../JdbcUrlHostAndPortReplaceFilterTest.java | 55 ------- .../alibaba/druid/filter/url/dataSource.xml | 32 ---- .../druid/filter/url/datasource.properties | 4 - 9 files changed, 633 deletions(-) delete mode 100644 src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java delete mode 100644 src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java delete mode 100644 src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java delete mode 100644 src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java delete mode 100644 src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java delete mode 100644 src/test/java/com/alibaba/druid/filter/url/HostAndPortValidatorThreadTest.java delete mode 100644 src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java delete mode 100644 src/test/resources/com/alibaba/druid/filter/url/dataSource.xml delete mode 100644 src/test/resources/com/alibaba/druid/filter/url/datasource.properties diff --git a/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java b/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java deleted file mode 100644 index f296a29ac5..0000000000 --- a/src/main/java/com/alibaba/druid/filter/url/ConnectionConnectFilterChainImpl.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.alibaba.druid.filter.url; - -import com.alibaba.druid.filter.FilterChain; -import com.alibaba.druid.filter.FilterChainImpl; -import com.alibaba.druid.proxy.jdbc.ConnectionProxy; -import com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl; -import com.alibaba.druid.proxy.jdbc.DataSourceProxy; -import com.alibaba.druid.support.logging.Log; -import com.alibaba.druid.support.logging.LogFactory; - -import java.sql.Connection; -import java.sql.Driver; -import java.sql.SQLException; -import java.util.Properties; - -/** - * FilterChain to create new connection with placeholder in URL - * - * @author DigitalSonic - */ -public class ConnectionConnectFilterChainImpl extends FilterChainImpl { - private final static Log LOG = LogFactory.getLog(ConnectionConnectFilterChainImpl.class); - - public ConnectionConnectFilterChainImpl(FilterChain filterChain) { - this(filterChain.getDataSource(), filterChain.getPos()); - } - - public ConnectionConnectFilterChainImpl(DataSourceProxy dataSource, int pos) { - super(dataSource, pos); - } - - @Override - public ConnectionProxy connection_connect(Properties info) throws SQLException { - if (this.pos < getFilterSize()) { - return getFilters().get(pos++).connection_connect(this, info); - } - - DataSourceProxy dataSource = getDataSource(); - Driver driver = dataSource.getRawDriver(); - String url = dataSource.getRawJdbcUrl(); - - url = processUrl(url); - - Connection nativeConnection = driver.connect(url, info); - - if (nativeConnection == null) { - return null; - } - - return new ConnectionProxyImpl(dataSource, nativeConnection, info, dataSource.createConnectionId()); - } - - protected String processUrl(String url) { - if (url == null || (url != null && - (url.indexOf("${") == -1 || url.indexOf("}") == -1))) { - return url; - } - - String processedUrl = url; - String placeHolder = url.substring(url.indexOf("${") + 2, url.indexOf("}")); - String value = HostAndPortHolder.getInstance().get(placeHolder); - if (value != null) { - LOG.info("PlaceHolder ${" + placeHolder + "} is found in url, it will be replace by " + value); - processedUrl = url.replace("${" + placeHolder + "}", value); - } else { - LOG.info("PlaceHolder ${" + placeHolder + "} is found in url, but no value is bind."); - } - return processedUrl; - } -} diff --git a/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java b/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java deleted file mode 100644 index a76662f811..0000000000 --- a/src/main/java/com/alibaba/druid/filter/url/HostAndPortHolder.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.alibaba.druid.filter.url; - -import com.alibaba.druid.support.logging.Log; -import com.alibaba.druid.support.logging.LogFactory; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Random; - -/** - * A singleton object to hold Host & Port information. - * - * @author DigitalSonic - */ -public class HostAndPortHolder { - private static final Log LOG = LogFactory.getLog(ConnectionConnectFilterChainImpl.class); - - public static final String UNAVAILABLE = "UNAVAILABLE_HOST"; - - private static HostAndPortHolder instance = new HostAndPortHolder(); - - private Map> holder = new HashMap>(); - private List blacklist = new ArrayList(); - - private Random random = new Random(); - - public static HostAndPortHolder getInstance() { - return instance; - } - - public static 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 = HostAndPortHolder.class.getResourceAsStream(file); - } catch (Exception ex) { - LOG.warn("Can not load resource " + file, ex); - } - } - if (is != null) { - try { - properties.load(is); - } 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!"); - } - - for (Object k : properties.keySet()) { - String v = properties.getProperty((String) k); - if (v != null && v.trim().length() > 0) { - LOG.info("Loading placeholder " + k + " = [" + v + "]"); - List values = Arrays.asList(v.split(";")); - getInstance().putHostAndPorts((String) k, values); - } - } - - } - - private HostAndPortHolder() { - } - - public void putHostAndPorts(String key, List values) { - if (key == null || values == null || values.isEmpty()) { - return; - } - holder.put(key, values); - } - - public Map> getHolder() { - return holder; - } - - public void clearBlacklist() { - blacklist.clear(); - } - - public void addBlacklist(String hostAndPort) { - blacklist.add(hostAndPort); - } - - public void removeBlacklist(String hostAndPort) { - if (hostAndPort == null) { - return; - } - blacklist.remove(hostAndPort); - } - - public int getBlacklistSize() { - return blacklist.size(); - } - - public String get(String name) { - if (name == null || !holder.containsKey(name)) { - LOG.warn("Can not find placeholder " + name + "."); - return UNAVAILABLE; - } - - List hostAndPorts = holder.get(name); - int idx = random.nextInt(hostAndPorts.size()); - String returnValue = hostAndPorts.get(idx); - String hostAndPort = returnValue; - for (int i = 0; i < hostAndPorts.size(); i++) { - if (isAvailable(hostAndPort)) { - returnValue = hostAndPort; - break; - } - if (++idx >= hostAndPorts.size()) { - idx = 0; - } - hostAndPort = hostAndPorts.get(idx); - } - return returnValue; - } - - public List getAll(String name) { - if (name == null || !holder.containsKey(name)) { - return new ArrayList(); - } - return holder.get(name); - } - - private boolean isAvailable(String hostAndPort) { - return !blacklist.contains(hostAndPort); - } -} diff --git a/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java b/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java deleted file mode 100644 index bc460d6315..0000000000 --- a/src/main/java/com/alibaba/druid/filter/url/HostAndPortValidatorThread.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.alibaba.druid.filter.url; - -import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.druid.support.logging.Log; -import com.alibaba.druid.support.logging.LogFactory; - -import java.sql.Connection; -import java.sql.Driver; -import java.sql.SQLException; -import java.util.List; -import java.util.Properties; - -/** - * A thread to run the validation on each host:port. - * If some host:port is failed in the validation, we can set test flags in DruidDataSource. - * - * @author DigitalSonic - */ -public class HostAndPortValidatorThread implements Runnable { - private final static Log LOG = LogFactory.getLog(HostAndPortValidatorThread.class); - private DruidDataSource dataSource; - private int sleepSeconds = 30; - private boolean modifyTestFlags = false; - private boolean oldTestOnBorrow = false; - private boolean oldTestOnReturn = false; - - @Override - public void run() { - oldTestOnBorrow = dataSource.isTestOnBorrow(); - oldTestOnReturn = dataSource.isTestOnReturn(); - while (true) { - try { - check(); - } catch(Exception e) { - LOG.error("Validation Failed!", e); - } finally { - try { - Thread.sleep(sleepSeconds * 1000); - } catch (InterruptedException e) { - // ignore - } - } - } - } - - protected void check() { - String url = dataSource.getRawJdbcUrl(); - if (url == null || (url != null && - (url.indexOf("${") == -1 || url.indexOf("}") == -1))) { - return; - } - - String placeHolder = url.substring(url.indexOf("${") + 2, url.indexOf("}")); - List values = HostAndPortHolder.getInstance().getAll(placeHolder); - for (String v : values) { - validateConnection(dataSource, placeHolder, v); - } - if (HostAndPortHolder.getInstance().getBlacklistSize() == 0) { - resetFlagsIfNeeded(); - } - } - - private void validateConnection(DruidDataSource dataSource, String placeHolder, String target) { - Driver driver = dataSource.getRawDriver(); - Properties info = dataSource.getConnectProperties(); - String url = dataSource.getRawJdbcUrl(); - Connection conn = null; - - try { - LOG.debug("Validating " + target + " every " + sleepSeconds + " seconds."); - String realUrl = url.replace("${" + placeHolder + "}", target); - conn = driver.connect(realUrl, info); - dataSource.validateConnection(conn); - HostAndPortHolder.getInstance().removeBlacklist(target); - } catch (SQLException e) { - LOG.warn("Validation FAILED for " + target, e); - HostAndPortHolder.getInstance().addBlacklist(target); - modifyFlagsIfNeeded(); - } finally { - if (conn != null) { - try { - conn.close(); - } catch (SQLException e) { - LOG.error("Can not close connection for HostAndPort Validation.", e); - } - } - } - } - - private void resetFlagsIfNeeded() { - if (modifyTestFlags) { - dataSource.setTestOnBorrow(oldTestOnBorrow); - dataSource.setTestOnReturn(oldTestOnReturn); - } - } - - private void modifyFlagsIfNeeded() { - if (!modifyTestFlags) { - return; - } - if (!oldTestOnReturn || !oldTestOnBorrow) { - dataSource.setTestOnBorrow(true); - dataSource.setTestOnReturn(true); - } - } - - public void setDataSource(DruidDataSource dataSource) { - this.dataSource = dataSource; - } - - public void setSleepSeconds(int sleepSeconds) { - this.sleepSeconds = sleepSeconds; - } - - public void setModifyTestFlags(boolean modifyTestFlags) { - this.modifyTestFlags = modifyTestFlags; - } -} diff --git a/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java b/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java deleted file mode 100644 index 583105146d..0000000000 --- a/src/main/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilter.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.alibaba.druid.filter.url; - -import com.alibaba.druid.filter.FilterAdapter; -import com.alibaba.druid.filter.FilterChain; -import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.druid.proxy.jdbc.ConnectionProxy; -import com.alibaba.druid.proxy.jdbc.DataSourceProxy; -import com.alibaba.druid.support.logging.Log; -import com.alibaba.druid.support.logging.LogFactory; - -import java.sql.SQLException; -import java.util.Properties; - -/** - * The filter to replace the placeholder in JDBC URL. - * - * @author DigitalSonic - */ -public class JdbcUrlHostAndPortReplaceFilter extends FilterAdapter { - private final static Log LOG = LogFactory.getLog(HostAndPortValidatorThread.class); - - public static final String DEFAULT_FILE = "datasource.properties"; - - private String file = DEFAULT_FILE; - - private int healthCheckIntervalSeconds = 0; - private HostAndPortValidatorThread validatorThread; - private Thread runningValidatorThread; - private boolean isValidatorThreadStarted = false; - private boolean modifyTestFlags = false; - - public void init() { - if (file == null || file.trim().length() == 0) { - file = DEFAULT_FILE; - } - HostAndPortHolder.loadProperties(file); - } - - public void close() { - if (runningValidatorThread != null) { - runningValidatorThread.interrupt(); - } - } - - @Override - public ConnectionProxy connection_connect(FilterChain chain, Properties info) throws SQLException { - startValidationThreadIfNeeded(chain); - return super.connection_connect(new ConnectionConnectFilterChainImpl(chain), info); - } - - private void startValidationThreadIfNeeded(FilterChain chain) { - if (isValidatorThreadStarted || healthCheckIntervalSeconds <= 0) { - return; - } - DataSourceProxy dataSource = chain.getDataSource(); - if (dataSource instanceof DruidDataSource) { - if (validatorThread == null) { - validatorThread = new HostAndPortValidatorThread(); - validatorThread.setSleepSeconds(healthCheckIntervalSeconds); - validatorThread.setModifyTestFlags(modifyTestFlags); - } - validatorThread.setDataSource((DruidDataSource) dataSource); - runningValidatorThread = new Thread(validatorThread); - runningValidatorThread.start(); - isValidatorThreadStarted = true; - } else { - LOG.warn("The DataSource is not a DruidDataSource, ignore HostAndPortValidator."); - } - } - - public void setFile(String file) { - this.file = file; - } - - public void setHealthCheckIntervalSeconds(int healthCheckIntervalSeconds) { - this.healthCheckIntervalSeconds = healthCheckIntervalSeconds; - } - - public void setValidatorThread(HostAndPortValidatorThread validatorThread) { - this.validatorThread = validatorThread; - } - - public void setModifyTestFlags(boolean modifyTestFlags) { - this.modifyTestFlags = modifyTestFlags; - } -} diff --git a/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java b/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java deleted file mode 100644 index b4e2b2f256..0000000000 --- a/src/test/java/com/alibaba/druid/filter/url/HostAndPortHolderTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.alibaba.druid.filter.url; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - -/** - * Test class for HostAndPortHolder - * - * @author DigitalSonic - */ -public class HostAndPortHolderTest { - @Before - public void setUp() { - HostAndPortHolder.loadProperties("/com/alibaba/druid/filter/url/datasource.properties"); - } - - @After - public void tearDown() { - HostAndPortHolder.getInstance().clearBlacklist(); - } - - @Test - public void testLoadFromFileNotExisted() { - int size = HostAndPortHolder.getInstance().getHolder().size(); - HostAndPortHolder.loadProperties("FILE_NOT_FOUND"); - assertEquals(size, HostAndPortHolder.getInstance().getHolder().size()); - } - - @Test - public void testLoadFromProperties() { - Map> holder = HostAndPortHolder.getInstance().getHolder(); - assertEquals(3, holder.size()); - assertEquals("127.0.0.1:3066", holder.get("datasource.foo").get(0)); - assertEquals("192.168.0.1:3066", holder.get("datasource.foo").get(1)); - assertFalse(holder.containsKey("datasource.empty")); - } - - @Test - public void testRandomGet() { - boolean[] flags = new boolean[] { false, false }; - HostAndPortHolder holder = HostAndPortHolder.getInstance(); - for (int i = 0; i < 10; i++) { - String v = holder.get("datasource.foo"); - if (v.equals("127.0.0.1:3066")) { - flags[0] = true; - } else if (v.equals("192.168.0.1:3066")) { - flags[1] = true; - } - } - assertTrue(flags[0] && flags[1]); - } - - @Test - public void testGetOne() { - HostAndPortHolder holder = HostAndPortHolder.getInstance(); - String value = holder.get("datasource.foo"); - assertTrue(value.equals("127.0.0.1:3066") || value.equals("192.168.0.1:3066")); - assertEquals(HostAndPortHolder.UNAVAILABLE, holder.get("UNAVAILABLE")); - } - - @Test - public void testBlackList() { - HostAndPortHolder holder = HostAndPortHolder.getInstance(); - holder.addBlacklist("192.168.0.1:3066"); - for (int i = 0; i < 100; i++) { - assertNotEquals("192.168.0.1:3066", holder.get("datasource.foo")); - } - } -} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/filter/url/HostAndPortValidatorThreadTest.java b/src/test/java/com/alibaba/druid/filter/url/HostAndPortValidatorThreadTest.java deleted file mode 100644 index 02b0c4c440..0000000000 --- a/src/test/java/com/alibaba/druid/filter/url/HostAndPortValidatorThreadTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.alibaba.druid.filter.url; - -import com.alibaba.druid.pool.DruidDataSource; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * Test class for HostAndPortValidatorThread - * - * @author DigitalSonic - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration("classpath:com/alibaba/druid/filter/url/dataSource.xml") -public class HostAndPortValidatorThreadTest { - @Autowired - private DruidDataSource dataSource; - - @Test - public void testValidateFail() throws Exception { - assertEquals(0, HostAndPortHolder.getInstance().getBlacklistSize()); - dataSource.setValidationQuery("select xxx from foo"); - Thread.sleep(20 * 1000); - assertTrue(HostAndPortHolder.getInstance().getBlacklistSize() > 0); - assertTrue(dataSource.isTestOnBorrow()); - assertTrue(dataSource.isTestOnReturn()); - - dataSource.setValidationQuery(null); - Thread.sleep(20 * 1000); - assertEquals(0, HostAndPortHolder.getInstance().getBlacklistSize()); - assertFalse(dataSource.isTestOnBorrow()); - assertFalse(dataSource.isTestOnReturn()); - } -} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java b/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java deleted file mode 100644 index f62444b1f4..0000000000 --- a/src/test/java/com/alibaba/druid/filter/url/JdbcUrlHostAndPortReplaceFilterTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.alibaba.druid.filter.url; - -import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.druid.pool.DruidPooledConnection; -import com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl; -import org.apache.derby.impl.jdbc.EmbedConnection; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import java.sql.Connection; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * Test class for JdbcUrlHostAndPortReplaceFilter - * - * @author DigitalSonic - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration("classpath:com/alibaba/druid/filter/url/dataSource.xml") -public class JdbcUrlHostAndPortReplaceFilterTest { - @Autowired - private DruidDataSource dataSource; - - @Test - public void testConnectWithDifferentPlaceholderValue() throws Exception { - int[] count = new int[] { 0,0 }; - List connectionList = new ArrayList(); - - for (int i = 0; i < 10; i++) { - DruidPooledConnection conn = dataSource.getConnection(); - EmbedConnection embed = ((EmbedConnection)((ConnectionProxyImpl)conn.getConnection()).getRawObject()); - String connName = embed.toString(); - - if (connName.contains("foo")) { - count[0]++; - } else if (connName.contains("bar")) { - count[1]++; - } else { - fail("NOT foo or bar"); - } - connectionList.add(conn); - } - assertTrue(count[0] > 0 && count[1] >0); - for (Connection c : connectionList) { - c.close(); - } - } -} \ No newline at end of file diff --git a/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml b/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml deleted file mode 100644 index ac2c72adfb..0000000000 --- a/src/test/resources/com/alibaba/druid/filter/url/dataSource.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/resources/com/alibaba/druid/filter/url/datasource.properties b/src/test/resources/com/alibaba/druid/filter/url/datasource.properties deleted file mode 100644 index 4e05753ccd..0000000000 --- a/src/test/resources/com/alibaba/druid/filter/url/datasource.properties +++ /dev/null @@ -1,4 +0,0 @@ -datasource.foo=127.0.0.1:3066;192.168.0.1:3066 -datasource.bar=198.168.1.1:3166;192.168.1.2:3166 -datasource.empty -datasource.test=foo;bar \ No newline at end of file