Refactor Pool creation logic

1. Refactor the create logic to use NodeListener instead of DataSourceCreator.createMap
2. Add destroy methods to NodeListener and PoolUpdater
3. Add a lock to prevent Node update concurrently
This commit is contained in:
DigitalSonic
2020-01-22 18:46:20 +08:00
parent e03511cdaa
commit cff870e80e
9 changed files with 193 additions and 188 deletions
@@ -16,16 +16,8 @@
package com.alibaba.druid.pool.ha;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
/**
* An utility class to create DruidDataSource dynamically.
@@ -33,51 +25,6 @@ import com.alibaba.druid.support.logging.LogFactory;
* @author DigitalSonic
*/
public class DataSourceCreator {
private final static Log LOG = LogFactory.getLog(DataSourceCreator.class);
private Properties properties;
private List<String> nameList;
public DataSourceCreator(String file) {
this(file, "");
}
public DataSourceCreator(String file, String propertyPrefix) {
this.properties = PropertiesUtils.loadProperties(file);
this.nameList = PropertiesUtils.loadNameList(this.properties, propertyPrefix);
}
public Map<String, DataSource> createMap(HighAvailableDataSource haDataSource) {
Map<String, DataSource> map = new ConcurrentHashMap<String, DataSource>();
if (nameList == null || nameList.isEmpty()) {
LOG.error("No DataSource will be created!");
return map;
}
for (String n : nameList) {
String url = properties.getProperty(n + ".url");
String username = properties.getProperty(n + ".username");
String password = properties.getProperty(n + ".password");
LOG.info("Creating " + n + " with url[" + url + "] and username[" + username + "].");
DruidDataSource dataSource = null;
try {
dataSource = create(n, url, username, password, haDataSource);
map.put(n, dataSource);
} catch(Exception e) {
LOG.error("Can NOT create DruidDataSource for " + n, e);
if (dataSource != null) {
try {
dataSource.close();
} catch (Exception ex) {
LOG.error("Exception occurred while closing the FAILURE DataSource.", ex);
}
}
}
}
LOG.info(map.size() + " DruidDataSource(s) created. ");
return map;
}
public static DruidDataSource create(String name, String url, String username, String password,
HighAvailableDataSource haDataSource) throws SQLException {
@@ -129,8 +76,4 @@ public class DataSourceCreator {
return dataSource;
}
public List<String> getNameList() {
return nameList;
}
}
@@ -35,6 +35,9 @@ 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.node.FileNodeListener;
import com.alibaba.druid.pool.ha.node.NodeListener;
import com.alibaba.druid.pool.ha.node.PoolUpdater;
import com.alibaba.druid.pool.ha.selector.DataSourceSelector;
import com.alibaba.druid.pool.ha.selector.DataSourceSelectorEnum;
import com.alibaba.druid.pool.ha.selector.DataSourceSelectorFactory;
@@ -96,6 +99,9 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
private volatile boolean inited = false;
private PoolUpdater poolUpdater = new PoolUpdater(this);
private NodeListener nodeListener;
public void init() {
if (inited) {
return;
@@ -105,7 +111,7 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
return;
}
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
dataSourceMap = new DataSourceCreator(dataSourceFile, propertyPrefix).createMap(this);
createNodeMap();
}
if (selector == null) {
setSelector(DataSourceSelectorEnum.RANDOM.getName());
@@ -122,6 +128,12 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
}
public void destroy() {
if (nodeListener != null) {
nodeListener.destroy();
}
if (poolUpdater != null) {
poolUpdater.destroy();
}
if (dataSourceMap == null || dataSourceMap.isEmpty()) {
return;
}
@@ -211,29 +223,11 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
return selector == null ? null : selector.getName();
}
public DataSourceSelector getDataSourceSelector() {
return this.selector;
}
public void setDataSourceSelector(DataSourceSelector dataSourceSelector) {
this.selector = dataSourceSelector;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
throw new UnsupportedOperationException("Not supported by HighAvailableDataSource.");
}
@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);
@@ -248,16 +242,34 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
throw new SQLFeatureNotSupportedException();
}
public String getDriverClassName() {
return driverClassName;
}
public void setConnectionProperties(String connectionProperties) {
this.connectionProperties = connectionProperties;
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
// COPIED FROM DruidAbstractDataSource.setConnectionProperties()
public Properties getConnectProperties() {
return connectProperties;
if (connectionProperties == null || connectionProperties.trim().length() == 0) {
setConnectProperties(null);
return;
}
String[] entries = connectionProperties.split(";");
Properties properties = new Properties();
for (int i = 0; i < entries.length; i++) {
String entry = entries[i];
if (entry.length() > 0) {
int index = entry.indexOf('=');
if (index > 0) {
String name = entry.substring(0, index);
String value = entry.substring(index + 1);
properties.setProperty(name, value);
} else {
// no value is empty string which is how java.util.Properties works
properties.setProperty(entry, "");
}
}
}
setConnectProperties(properties);
}
public void setConnectProperties(Properties connectProperties) {
@@ -272,6 +284,68 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
}
}
private void createNodeMap() {
if (nodeListener == null) {
// Compatiable with the old version.
// Create a FileNodeListener to watch the dataSourceFile.
FileNodeListener listener = new FileNodeListener();
listener.setFile(dataSourceFile);
listener.setPrefix(propertyPrefix);
listener.setObserver(poolUpdater);
listener.init();
nodeListener = listener;
}
nodeListener.update(); // Do update in the current Thread at the startup
}
// Getters & Setters
public PoolUpdater getPoolUpdater() {
return poolUpdater;
}
public void setPoolUpdater(PoolUpdater poolUpdater) {
this.poolUpdater = poolUpdater;
}
public NodeListener getNodeListener() {
return nodeListener;
}
public void setNodeListener(NodeListener nodeListener) {
this.nodeListener = nodeListener;
}
public DataSourceSelector getDataSourceSelector() {
return this.selector;
}
public void setDataSourceSelector(DataSourceSelector dataSourceSelector) {
this.selector = dataSourceSelector;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
this.logWriter = out;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Properties getConnectProperties() {
return connectProperties;
}
public int getInitialSize() {
return initialSize;
}
@@ -452,36 +526,6 @@ public class HighAvailableDataSource extends WrapperAdapter implements DataSourc
return connectionProperties;
}
public void setConnectionProperties(String connectionProperties) {
this.connectionProperties = connectionProperties;
// COPIED FROM DruidAbstractDataSource.setConnectionProperties()
if (connectionProperties == null || connectionProperties.trim().length() == 0) {
setConnectProperties(null);
return;
}
String[] entries = connectionProperties.split(";");
Properties properties = new Properties();
for (int i = 0; i < entries.length; i++) {
String entry = entries[i];
if (entry.length() > 0) {
int index = entry.indexOf('=');
if (index > 0) {
String name = entry.substring(0, index);
String value = entry.substring(index + 1);
properties.setProperty(name, value);
} else {
// no value is empty string which is how java.util.Properties works
properties.setProperty(entry, "");
}
}
}
setConnectProperties(properties);
}
public String getFilters() {
return filters;
}
@@ -60,7 +60,7 @@ public class FileNodeListener extends NodeListener {
LOG.error("Can NOT update the node list.", e);
}
}
}, 0, intervalSeconds, TimeUnit.SECONDS);
}, intervalSeconds, intervalSeconds, TimeUnit.SECONDS);
}
/**
@@ -77,14 +77,22 @@ public class FileNodeListener extends NodeListener {
String url = originalProperties.getProperty(n + ".url");
String username = originalProperties.getProperty(n + ".username");
String password = originalProperties.getProperty(n + ".password");
if (url == null || url.isEmpty()
|| username == null || username.isEmpty()
|| password == null || password.isEmpty()) {
if (url == null || url.isEmpty()) {
LOG.warn(n + ".url is EMPTY! IGNORE!");
continue;
} else {
properties.setProperty(n + ".url", url);
}
if (username == null || username.isEmpty()) {
LOG.warn(n + ".username is EMPTY. Maybe you should check the config.");
} else {
properties.setProperty(n + ".username", username);
}
if (password == null || password.isEmpty()) {
LOG.warn(n + ".password is EMPTY. Maybe you should check the config.");
} else {
properties.setProperty(n + ".password", password);
}
properties.setProperty(n + ".url", url);
properties.setProperty(n + ".username", username);
properties.setProperty(n + ".password", password);
}
List<NodeEvent> events = NodeEvent.getEventListFromProperties(getProperties(), properties);
@@ -94,6 +102,18 @@ public class FileNodeListener extends NodeListener {
return events;
}
@Override
public void destroy() {
if (executor == null || executor.isShutdown()) {
return;
}
try {
executor.shutdown();
} catch (Exception e) {
LOG.error("Can NOT shutdown the ScheduledExecutorService.", e);
}
}
public int getIntervalSeconds() {
return intervalSeconds;
}
@@ -15,11 +15,16 @@
*/
package com.alibaba.druid.pool.ha.node;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import java.util.Date;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Properties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* This abstract class defines a listener to monitor the change of DataSource nodes.
@@ -28,15 +33,23 @@ import java.util.Properties;
* @see Observable
*/
public abstract class NodeListener extends Observable {
private final static Log LOG = LogFactory.getLog(NodeListener.class);
private Properties properties = new Properties();
private Date lastUpdateTime = null;
private Observer observer = null;
private Lock lock = new ReentrantLock();
/**
* The method implements the detail update logic.
*/
public abstract List<NodeEvent> refresh();
/**
* Do some cleanup.
*/
public abstract void destroy();
/**
* Add the given PoolUpdater as the Observer.
*
@@ -55,15 +68,23 @@ public abstract class NodeListener extends Observable {
* @see #refresh()
*/
public void update() {
List<NodeEvent> events = refresh();
if (events != null && !events.isEmpty()) {
this.lastUpdateTime = new Date();
NodeEvent[] arr = new NodeEvent[events.size()];
for (int i = 0; i < events.size(); i++) {
arr[i] = events.get(i);
if (!lock.tryLock()) {
LOG.info("Can not acquire the lock, skip this time.");
return;
}
try {
List<NodeEvent> events = refresh();
if (events != null && !events.isEmpty()) {
this.lastUpdateTime = new Date();
NodeEvent[] arr = new NodeEvent[events.size()];
for (int i = 0; i < events.size(); i++) {
arr[i] = events.get(i);
}
this.setChanged();
this.notifyObservers(arr);
}
this.setChanged();
this.notifyObservers(arr);
} finally {
lock.unlock();
}
}
@@ -48,6 +48,9 @@ public class PoolUpdater implements Observer {
private Lock lock = new ReentrantLock();
private ScheduledExecutorService executor;
/**
* Create a ScheduledExecutorService to remove unused DataSources.
*/
public PoolUpdater() {
executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(new Runnable() {
@@ -62,11 +65,28 @@ public class PoolUpdater implements Observer {
}, 60, 60, TimeUnit.SECONDS);
}
/**
* @see #PoolUpdater()
*/
public PoolUpdater(HighAvailableDataSource highAvailableDataSource) {
this();
setHighAvailableDataSource(highAvailableDataSource);
}
public void destroy() {
if (executor == null || executor.isShutdown()) {
return;
}
try {
executor.shutdown();
} catch (Exception e) {
LOG.error("Can NOT shutdown the ScheduledExecutorService.", e);
}
}
/**
* Process the given NodeEvent[]. Maybe add / delete some nodes.
*/
@Override
public void update(Observable o, Object arg) {
if (!(o instanceof NodeListener)) {
@@ -99,6 +119,9 @@ public class PoolUpdater implements Observer {
}
}
/**
* Remove unused DataSources.
*/
public void removeDataSources() {
if (nodesToDel == null || nodesToDel.isEmpty()) {
return;
@@ -157,12 +180,20 @@ public class PoolUpdater implements Observer {
cancelBlacklistNode(nodeName);
return;
}
DruidDataSource dataSource = null;
try {
DruidDataSource dataSource = DataSourceCreator.create(nodeName, url, username,
dataSource = DataSourceCreator.create(nodeName, url, username,
password, this.highAvailableDataSource);
map.put(nodeName, dataSource);
} catch (Exception e) {
LOG.error("Can NOT create DataSource " + nodeName, e);
LOG.error("Can NOT create DataSource " + nodeName + ". IGNORE IT.", e);
if (dataSource != null) {
try {
dataSource.close();
} catch (Exception ex) {
LOG.error("Exception occurred while closing the FAILURE DataSource.", ex);
}
}
}
}
@@ -1,57 +0,0 @@
package com.alibaba.druid.pool.ha;
import org.junit.Test;
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
public class DataSourceCreatorTest {
@Test
public void testNameList() {
String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties";
DataSourceCreator creator = new DataSourceCreator(file, "prefix1");
List<String> list = creator.getNameList();
assertFalse(list.isEmpty());
assertEquals(1, list.size());
assertEquals("prefix1.foo", list.get(0));
creator = new DataSourceCreator(file);
list = creator.getNameList();
assertEquals(3, list.size());
for (String k : list) {
assertNotEquals("bar", k);
}
}
@Test
public void createMap() throws Exception {
HighAvailableDataSource highAvailableDataSource = new HighAvailableDataSource();
String file = "/com/alibaba/druid/pool/ha/ha-datasource.properties";
DataSourceCreator creator = new DataSourceCreator(file);
Map<String, DataSource> map = creator.createMap(highAvailableDataSource);
assertEquals(2, map.size());
assertNotNull(map.get("foo"));
assertNotNull(map.get("bar"));
}
@Test
public void createMapWithPrefix() throws Exception {
HighAvailableDataSource highAvailableDataSource = new HighAvailableDataSource();
String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties";
DataSourceCreator creator = new DataSourceCreator(file, "prefix1");
Map<String, DataSource> map = creator.createMap(highAvailableDataSource);
assertEquals(1, map.size());
assertNotNull(map.get("prefix1.foo"));
assertNull(map.get("prefix2.foo"));
creator = new DataSourceCreator(file, "prefix2");
map = creator.createMap(highAvailableDataSource);
assertEquals(1, map.size());
assertNull(map.get("prefix1.foo"));
assertNotNull(map.get("prefix2.foo"));
}
}
@@ -26,6 +26,7 @@ public class BaseRandomDataSourceSelectorTest {
@After
public void tearDown() {
dataSourceMap = null;
dataSource.destroy();
dataSource = null;
}
}
@@ -29,6 +29,7 @@ public class NamedDataSourceSelectorTest {
@After
public void tearDown() {
dataSourceMap = null;
dataSource.destroy();
dataSource = null;
}
@@ -25,6 +25,7 @@ public class RandomDataSourceSelectorWithValidationTest {
@After
public void tearDown() {
highAvailableDataSource.destroy();
highAvailableDataSource = null;
}