mirror of
https://github.com/alibaba/druid.git
synced 2023-03-04 19:17:16 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ public class RandomDataSourceSelectorWithValidationTest {
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
highAvailableDataSource.destroy();
|
||||
highAvailableDataSource = null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user