package com.alibaba.druid.pool.ha;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.management.ObjectName;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.logging.Log;
import com.alibaba.druid.logging.LogFactory;
import com.alibaba.druid.pool.DataSourceAdapter;
import com.alibaba.druid.pool.ha.balance.Balancer;
import com.alibaba.druid.pool.ha.balance.WeightBalancer;
import com.alibaba.druid.pool.ha.config.ConfigLoader;
import com.alibaba.druid.pool.ha.valid.DataSourceFailureDetecter;
import com.alibaba.druid.pool.ha.valid.DefaultDataSourceFailureDetecter;
import com.alibaba.druid.proxy.jdbc.DataSourceProxy;
import com.alibaba.druid.stat.JdbcDataSourceStat;
import com.alibaba.druid.util.JdbcUtils;
public class MultiDataSource extends DataSourceAdapter implements MultiDataSourceMBean, DataSourceProxy {
private final static Log LOG = LogFactory.getLog(MultiDataSource.class);
private Properties properties = new Properties();
private final AtomicLong connectionIdSeed = new AtomicLong();
private final AtomicLong statementIdSeed = new AtomicLong();
private final AtomicLong resultSetIdSeed = new AtomicLong();
private final AtomicLong transactionIdSeed = new AtomicLong();
private final AtomicLong configLoadCount = new AtomicLong();
private final AtomicLong failureDetectCount = new AtomicLong();
private final AtomicLong busySkipCount = new AtomicLong();
private final AtomicLong retryGetConnectionCount = new AtomicLong();
protected DataSourceFailureDetecter failureDetector = new DefaultDataSourceFailureDetecter();
private long failureDetectPeriodMillis = 3000;
private long configLoadPeriodMillis = 1000 * 60;
private int schedulerThreadCount = 3;
private ScheduledExecutorService scheduler;
private ScheduledFuture<?> failureDetectFuture;
private ScheduledFuture<?> configLoadFuture;
private boolean inited = false;
protected final Lock lock = new ReentrantLock();
protected final Condition notFull = lock.newCondition();
protected final Condition notFail = lock.newCondition();
private ConcurrentMap<String, DataSourceHolder> dataSources = new ConcurrentHashMap<String, DataSourceHolder>();
private ObjectName objectName;
private List<Filter> filters = new ArrayList<Filter>();
private boolean enable;
private String name;
private ConfigLoader configLoader;
private int maxPoolSize = 50;
private long activeCount = 0;
private long maxWaitMillis = 0;
private Balancer balancer = new WeightBalancer();
private JdbcDataSourceStat dataSourceStat;
protected Properties connectionProperties = new Properties();
public long getMaxWaitMillis() {
return maxWaitMillis;
}
public void setMaxWaitMillis(long maxWaitMillis) {
this.maxWaitMillis = maxWaitMillis;
}
public void setMaxWait(long seconds) {
this.setMaxWaitMillis(1000 * seconds);
}
public long getMaxWait() {
return this.getMaxWaitMillis() / 1000;
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(int maxPoolSize) throws SQLException {
if (this.isIntited()) {
throw new SQLException("dataSource inited");
}
this.maxPoolSize = maxPoolSize;
}
public ConfigLoader getConfigLoader() {
return configLoader;
}
public void setConfigLoader(ConfigLoader configLoader) {
this.configLoader = configLoader;
}
public String getName() {
if (name == null) {
return "HADataSource-" + System.identityHashCode(this);
}
return name;
}
public String getNameInternal() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public boolean isIntited() {
return this.inited;
}
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public ObjectName getObjectName() {
return objectName;
}
public void setObjectName(ObjectName objectName) {
this.objectName = objectName;
}
public MultiDataSource(){
}
public void init() throws SQLException {
if (inited) {
return;
}
lock.lock();
try {
if (inited) {
return;
}
dataSourceStat = new JdbcDataSourceStat(name, null);
this.balancer.init(this);
initInternal();
scheduler = Executors.newScheduledThreadPool(schedulerThreadCount);
startFailureDetectScheduleTask();
startConfigLoadScheduleTask();
inited = true;
MultiDataSourceStatManager.add(this);
} finally {
lock.unlock();
}
}
public boolean startConfigLoadScheduleTask() {
if (configLoadFuture == null) {
configLoadFuture = scheduler.scheduleAtFixedRate(new ConfigLoadTask(), configLoadPeriodMillis,
configLoadPeriodMillis, TimeUnit.MILLISECONDS);
return true;
}
return false;
}
public boolean stopConfigLoadScheduleTask() {
if (configLoadFuture != null) {
configLoadFuture.cancel(true);
configLoadFuture = null;
return true;
}
return false;
}
public boolean startFailureDetectScheduleTask() {
if (failureDetectFuture == null) {
failureDetectFuture = scheduler.scheduleAtFixedRate(new FailureDetectTask(), failureDetectPeriodMillis,
failureDetectPeriodMillis, TimeUnit.MILLISECONDS);
return true;
}
return false;
}
public boolean stopFailureDetectScheduleTask() {
if (failureDetectFuture != null) {
failureDetectFuture.cancel(true);
failureDetectFuture = null;
return true;
}
return false;
}
protected void initInternal() throws SQLException {
}
public void resetStat() {
}
public void close() {
scheduler.shutdownNow();
Object[] items = this.getDataSources().values().toArray();
for (Object item : items) {
JdbcUtils.close((DataSourceHolder) item);
}
MultiDataSourceStatManager.remove(this);
}
public void failureDetect() {
int changeCount = 0;
for (DataSourceHolder dataSourceHolder : getDataSources().values()) {
boolean isFail = !failureDetector.isValid(dataSourceHolder.getDataSource());
if (isFail != dataSourceHolder.isFail()) {
dataSourceHolder.setFail(isFail);
changeCount++;
}
}
if (changeCount != 0) {
afterDataSourceChanged(null);
}
failureDetectCount.incrementAndGet();
}
protected ScheduledExecutorService getScheduler() {
return scheduler;
}
public void setFailureDetectPeriodMillis(long failureDetectPeriodMillis) {
this.failureDetectPeriodMillis = failureDetectPeriodMillis;
}
public void setConfigLoadPeriodMillis(long configLoadPeriodMillis) {
this.configLoadPeriodMillis = configLoadPeriodMillis;
}
public DataSourceFailureDetecter getFailureDetector() {
return failureDetector;
}
public void setFailureDetector(DataSourceFailureDetecter failureDetector) {
this.failureDetector = failureDetector;
}
public long createConnectionId() {
return connectionIdSeed.getAndIncrement();
}
public long createStatementId() {
return statementIdSeed.incrementAndGet();
}
public Map<String, DataSourceHolder> getDataSources() {
return dataSources;
}
public DataSourceHolder getDataSourceHolder(String name) {
return dataSources.get(name);
}
public void addDataSource(String name, DataSourceHolder dataSourceHolder) {
dataSources.put(name, dataSourceHolder);
afterDataSourceChanged(null);
}
public Properties getProperties() {
return properties;
}
public long getActiveCount() {
return activeCount;
}
public void notFailSignal() {
lock.lock();
try {
notFail.signalAll();
} finally {
lock.unlock();
}
}
public void afterDataSourceChanged(Object event) {
if (!this.balancer.isInited()) {
this.balancer.init(this);
}
this.balancer.afterDataSourceChanged(null);
}
public Connection getConnection() throws SQLException {
init();
lock.lock();
try {
if (activeCount >= maxPoolSize) {
notFull.await();
}
MultiDataSourceConnection conn = new MultiDataSourceConnection(this, createConnectionId());
activeCount++;
return conn;
} catch (InterruptedException e) {
throw new SQLException("thread interrupted", e);
} finally {
lock.unlock();
}
}
protected void afterConnectionClosed(MultiDataSourceConnection conn) {
lock.lock();
try {
activeCount--;
notFull.signal();
} finally {
lock.unlock();
}
}
public MultiConnectionHolder getRealConnection(MultiDataSourceConnection multiConn, String sql) throws SQLException {
return balancer.getConnection(multiConn, sql);
}
public int getEnabledDataSourceCount() {
int count = 0;
for (DataSourceHolder item : this.dataSources.values()) {
if (item.isEnable()) {
count++;
}
}
return count;
}
public void handleNotAwailableDatasource(DataSourceHolder dataSourceHolder) {
}
public long getRetryGetConnectionCount() {
return retryGetConnectionCount.get();
}
public void incrementRetryGetConnectionCount() {
retryGetConnectionCount.incrementAndGet();
}
public Lock getLock() {
return lock;
}
public Condition getNotFail() {
return notFail;
}
public long getBusySkipCount() {
return busySkipCount.get();
}
public void incrementBusySkipCount() {
busySkipCount.incrementAndGet();
}
public String[] getDataSourceNames() {
return this.dataSources.keySet().toArray(new String[this.dataSources.size()]);
}
@Override
public String getDbType() {
return null;
}
@Override
public Driver getRawDriver() {
return null;
}
@Override
public String getUrl() {
return null;
}
@Override
public String getRawJdbcUrl() {
return null;
}
@Override
public List<Filter> getProxyFilters() {
return filters;
}
@Override
public long createResultSetId() {
return resultSetIdSeed.incrementAndGet();
}
@Override
public long createTransactionId() {
return transactionIdSeed.incrementAndGet();
}
public boolean restartDataSource(String name) {
DataSourceHolder holder = this.getDataSources().get(name);
if (holder != null) {
holder.restart();
return true;
}
return false;
}
public long getConfigLoadCount() {
return configLoadCount.get();
}
public long getFailureDetectCount() {
return failureDetectCount.get();
}
public long getFailureDetectPeriodMillis() {
return failureDetectPeriodMillis;
}
public long getConfigLoadPeriodMillis() {
return configLoadPeriodMillis;
}
class ConfigLoadTask implements Runnable {
@Override
public void run() {
if (configLoader != null) {
try {
configLoadCount.incrementAndGet();
configLoader.load();
} catch (Exception e) {
LOG.error("config load error", e);
}
}
}
}
class FailureDetectTask implements Runnable {
@Override
public void run() {
try {
failureDetect();
} catch (Exception ex) {
LOG.error("failure detect error", ex);
}
}
}
@Override
public JdbcDataSourceStat getDataSourceStat() {
return dataSourceStat;
}
@Override
public Properties getConnectProperties() {
return connectionProperties;
}
}