/**
* Alipay.com Inc.
* Copyright (c) 2004-2012 All Rights Reserved.
*/
package com.alipay.zdal.client.jdbc.dbselector;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import com.alipay.zdal.client.jdbc.DBSelector.DataSourceTryer;
import com.alipay.zdal.client.jdbc.ZdalStatement.DB_OPERATION_TYPE;
import com.alipay.zdal.client.util.ExceptionUtils;
import com.alipay.zdal.common.Constants;
import com.alipay.zdal.common.WeightRandom;
import com.alipay.zdal.common.exception.sqlexceptionwrapper.ZdalCommunicationException;
import com.alipay.zdal.common.jdbc.sorter.ExceptionSorter;
import com.alipay.zdal.common.jdbc.sorter.MySQLExceptionSorter;
import com.alipay.zdal.common.jdbc.sorter.OracleExceptionSorter;
/**
* ������DataSourceHolder��̬�࣬��Ҫ���ڹ�������Դ������ָ�
* @author zhaofeng.wang
* @version $Id: DataSourceHolder.java,v 0.1 2011-4-14 ����04:00:55 zhaofeng.wang Exp $
*/
public class DataSourceHolder {
private static final Logger logger = Logger
.getLogger(Constants.CONFIG_LOG_NAME_LOGNAME);
private final DataSource ds;
/**
* �������������ڿ���ֻ����һ��ҵ���߳�ȥ���ԣ�
*/
private final ReentrantLock lock = new ReentrantLock();
/**
* ����Դ�Ƿ���ã�Ĭ�Ͽ���
*/
private volatile boolean isNotAvailable = false;
/**
* �ϴ�����ʱ��
*/
private volatile long lastRetryTime = 0;
/**
* �쳣����
*/
private volatile int exceptionTimes = 0;
/**
* ��һ�β����쳣��ʱ�䣬��λ����
*/
private volatile long firstExceptionTime = 0;
/**
* ���Թ���db��ʱ������Ĭ��ֵ��Ϊ2s,��λ����
*/
private final int retryBadDbInterval = 2000;
/**
* ��λʱ��Σ�Ĭ��Ϊ1���ӣ�����ͳ��ʱ�����ij��db���쳣�Ĵ�������λ����
*/
private final int timeInterval = 60000;
/**
* ��λʱ���������쳣�Ĵ���������������ֵ�㽫����Դ��Ϊ������
*/
private final int allowExceptionTimes = 10;
/**
* ���캯��
* @param ds
*/
public DataSourceHolder(DataSource ds) {
this.ds = ds;
}
public DataSource getDs() {
return ds;
}
/**
* ��ѡ�е�����Դ�Ͻ��в��������ݸ�����ԴĿǰ�Ŀ����Խ����ж����ĸ���֧�����ߵ�������Ҫ���������㣺
* ��1���������ԴĿǰ�����ã�ֻ����ҵ���߳����ԣ�
* ��2�������ڴ����쳣�Ļ��ڴ���һ��������Ҫ�����Ŀǰ�����ã����̳߳�����ʧ��ʱ���Ͳ���ͳ���쳣�����������Ŀǰ���ã������쳣ʱ��Ҫ
* ͳ�Ƶ�λʱ���ڵ��쳣����
* @param <T>
* @param operationType �������������
* @param weightRandom ��������Դ��Ȩ��
* @param dataSourceHolders ��װ��������Դ
* @param failedDataSources ʧ�ܵ�datasource
* @param tryer
* @param exceptions
* @param excludeKeys
* @param exceptionSorter
* @param name
* @param args
* @return
* @throws SQLException
*/
public <T> T tryOnSelectedDataSource(DB_OPERATION_TYPE operationType,
WeightRandom weightRandom,
Map<String, DataSourceHolder> dataSourceHolders,
Map<DataSource, SQLException> failedDataSources,
DataSourceTryer<T> tryer, List<SQLException> exceptions,
List<String> excludeKeys, ExceptionSorter exceptionSorter,
String name, Object... args) throws SQLException {
T t = null;
if (isNotAvailable) {
t = tryOnFailedDataSource(failedDataSources, tryer, exceptions, excludeKeys,
exceptionSorter, name, args);
} else {
t = tryOnAvailableDataSource(operationType, weightRandom, dataSourceHolders,
failedDataSources, tryer, exceptions, excludeKeys, exceptionSorter, name, args);
}
return t;
}
/**
* �������Դ�Ѿ������Ϊ�����ã���ֻ����һ���߳̽��г��ԣ����Գɹ�ֱ�ӱ��Ϊ���ã������ų���������Դ��ȥѰ�������Ŀ�������Դ��
* ����Ѿ���ҵ���߳��ڳ��Ը�����Դ����������ʱ���������ò�����������ô�����߳�ֱ���ų���������Դ��ȥѰ������������Դ
* @param <T>
* @param failedDataSources
* @param tryer
* @param exceptions
* @param excludeKeys
* @param exceptionSorter
* @param name
* @param args
* @return
* @throws SQLException
*/
public <T> T tryOnFailedDataSource(Map<DataSource, SQLException> failedDataSources,
DataSourceTryer<T> tryer, List<SQLException> exceptions,
List<String> excludeKeys, ExceptionSorter exceptionSorter,
String name, Object... args) throws SQLException {
T t = null;
boolean toTry = System.currentTimeMillis() - lastRetryTime > retryBadDbInterval;
//ÿ������룬ֻ����һ��ҵ���̼߳���ʹ���������Դ��
if (toTry && lock.tryLock()) {
try {
logger.warn("�߳�" + Thread.currentThread().getName() + "��"
+ getCurrentDateTime(null) + "��������Դ" + name + "�ĵ��߳�����״̬��");
//��һ�����������ݿ����ӣ�������ɹ��ͱ�ʾ����Դ�������ã�ֱ�ӷ��أ�
boolean isSussessful = tryToConnectDataBase(exceptionSorter, name);
if (!isSussessful) {
excludeKeys.add(name);
return t;
}
Long beginTime = System.currentTimeMillis();
t = tryer.tryOnDataSource(ds, name, args);
logger.warn("���߳�" + Thread.currentThread().getName() + "ȥ��ȡ������Դ����" + name
+ "�ɹ�����ʱΪ��" + (System.currentTimeMillis() - beginTime));
//����߳�ִ�гɹ�����Ϊ���ã���������Դ���Ϊ����
isNotAvailable = false;
exceptionTimes = 0;
logger.warn("����Դ" + name + "��" + getCurrentDateTime(null) + "�Ѿ��ָ������Ϊ���ã�");
} catch (SQLException e) {
exceptions.add(e);
boolean isFatal = exceptionSorter.isExceptionFatal(e);
if (!isFatal || failedDataSources == null) {
logger.warn("�쳣������: " + e.getErrorCode() + " �������ݿⲻ�����쳣��Ҫ�����ԣ�ֱ���׳�.isFatal= "
+ isFatal, e);
return tryer.onSQLException(exceptions, exceptionSorter, args);
}
excludeKeys.add(name);
logger.warn("���߳�" + Thread.currentThread().getName() + "���Թ�������Դ" + name
+ "ʱʧ�ܣ�����ȥѰ���������õ�����Դ��");
} finally {
lastRetryTime = System.currentTimeMillis();
lock.unlock();
}
} else {
//�����ϳ��Ը�����Դ��������������Χforѭ��ȥ��Ѱ����������Դ
logger.warn("�߳�" + Thread.currentThread().getName() + "��" + getCurrentDateTime(null)
+ "ʱ����㳢�Թ�������Դ" + name + "ʱʧ�ܣ������߳����ڷ��ʻ��߲�����2s��ʱ������");
excludeKeys.add(name);
}
return t;
}
/**
* �ڵ��߳����Ե�ʱ��ȡ�����Ӻ����������ݿ⽨�����ӣ�
*/
public boolean tryToConnectDataBase(ExceptionSorter exceptionSorter, String name) {
Long beginTime = System.currentTimeMillis();
boolean isSussessful = true;
String sql = null;
if (exceptionSorter instanceof MySQLExceptionSorter) {
sql = "select 'x' ";
} else if (exceptionSorter instanceof OracleExceptionSorter) {
sql = "select * from dual";
} else {
logger.error("���ݿ����ͳ������������ã�");
isSussessful = false;
return isSussessful;
}
Connection con = null;
Statement stmt = null;
try {
con = ds.getConnection();
logger.warn("Get the connection success,time="
+ getCurrentDateTime(System.currentTimeMillis()));
stmt = con.createStatement();
logger.warn("Create the statement success,time="
+ getCurrentDateTime(System.currentTimeMillis()));
stmt.executeQuery(sql);
logger.warn("���߳�" + Thread.currentThread().getName() + "����sql=" + sql + "������У�����Ӹ�����Դ"
+ name + "�ɹ�����ʱΪ:" + (System.currentTimeMillis() - beginTime));
} catch (SQLException e) {
logger.error("���߳�" + Thread.currentThread().getName() + "����sql=" + sql + "������У�����Ӹ�����Դ"
+ name + "ʧ��,��ʱΪ:" + (System.currentTimeMillis() - beginTime) + "ms", e);
isSussessful = false;
} finally {
try {
if (stmt != null) {
stmt.close();
}
if (con != null) {
con.close();
}
} catch (Exception e) {
logger.warn("close the resource has an error", e);
}
}
return isSussessful;
}
/**
* ���������Դ���ã�ֱ���ڸ�����Դ�Ͻ��в�������������쳣������catch������������Ӧ�Ĵ����������̻�����tryOnFailedDataSource�������ƣ�
* Ψһ��ͬ�������ʱ����Ҫ�����쳣�������ۼӣ����õķ�ʽ�ǣ������λʱ���ڷ����쳣�Ĵ��������������õ���ֵ������Ŀǰ������Դ����
* ����һ����������Դ��Ϊ�����ã������д�⣬��ʹֻ��һ����Ҳ����Ϊ������
* @param <T>
* @param failedDataSources
* @param tryer
* @param exceptions
* @param excludeKeys
* @param exceptionSorter
* @param name
* @param args
* @return
* @throws SQLException
*/
public <T> T tryOnAvailableDataSource(DB_OPERATION_TYPE operationType,
WeightRandom weightRandom,
Map<String, DataSourceHolder> dataSourceHolders,
Map<DataSource, SQLException> failedDataSources,
DataSourceTryer<T> tryer, List<SQLException> exceptions,
List<String> excludeKeys,
ExceptionSorter exceptionSorter, String name,
Object... args) throws SQLException {
T t = null;
try {
t = tryer.tryOnDataSource(ds, name, args);
} catch (SQLException e) {
exceptions.add(e);
boolean isFatal = exceptionSorter.isExceptionFatal(e);
if (!isFatal || failedDataSources == null) {
logger.warn("�쳣������: " + e.getErrorCode() + " �������ݿⲻ�����쳣��Ҫ�����ԣ�ֱ���׳�.isFatal= "
+ isFatal, e);
return tryer.onSQLException(exceptions, exceptionSorter, args);
}
logger.error("�߳�" + Thread.currentThread().getName()
+ "�ڷ���dataSourceHolder����Դ�������ϣ�name=" + name, e);
excludeKeys.add(name);
//�˴�����������Դ�ų������õIJ����Ǹ��ݵ�λʱ��������ʧ�ܵĴ����������ij����ֵ��������Դ������ʣ��һ��ʱ������������Դ���;����Ƿ��߳���
calcFailedDSExceptionTimes(name, operationType, weightRandom, dataSourceHolders);
}
return t;
}
/**
* �ⲿ���������������ֱ��Ǹ�������Դ��Ȩ��weightRandom���Լ���װ��������ԴdataSourceHolders��
* ��������һ������ͳ�Ƶ�ǰ���õ�����Դ����ʱʹ��
* �ж�һ������Դ���õ�ǰ�������ǣ�Ȩ�ش���0����isNotAvailable��ֵΪfalse��
*/
/**
* ���㵱ǰ����Դ���쳣����
* ��1��ͳ�Ƶ�ǰ���õ�����Դ����������operationType���ж���д��Ƕ��⣻
* ����Ƕ��⣬Ŀǰ��ֻʣ��һ����������Դ�������κδ��������д�⣬��û�д����ƣ�
* ��2���ڿ��߳�������£������λʱ�䣨����Ϊ1���ӣ����쳣��������ij����ֵ������Ϊ20�Σ���������Դ���Ϊ�����ã�
* @param name
* @param operationType
* @param weightRandom
* @param dataSourceHolders
*/
public synchronized void calcFailedDSExceptionTimes(
String name,
DB_OPERATION_TYPE operationType,
WeightRandom weightRandom,
Map<String, DataSourceHolder> dataSourceHolders) {
//���������Դ�Ѿ����Ϊ�����ã���ֱ�ӷ���
if (isNotAvailable) {
logger.warn("����Դ" + name + "�Ѿ��������ˣ�������ͳ���쳣�����ˣ�");
return;
}
//ͳ�Ƶ�ǰ���õ�����Դ������
int availableDSNumber = 0;
for (Map.Entry<String, DataSourceHolder> entry : dataSourceHolders.entrySet()) {
int weight = weightRandom.getWeightByKey(entry.getKey());
//����Դ��Ȩ�ش���0�����Ҹ�����Դ����
if ((weight > 0) && !(entry.getValue().isNotAvailable)) {
availableDSNumber++;
}
logger.warn("The weight of key=" + entry.getKey() + ",isNotAvailable="
+ isNotAvailable);
}
logger.warn("Ŀǰ���õ�����Դ�ĸ���Ϊ" + availableDSNumber + ",operationType=" + operationType
+ ",name=" + name);
if (availableDSNumber <= 1 && (operationType == DB_OPERATION_TYPE.READ_FROM_DB)) {
logger.error("������Դ" + name + "�Ѿ������쳣����Ŀǰ���õĶ�����Դ������ʣ��" + availableDSNumber + "�����ʲ��߳���");
return;
}
if (this.exceptionTimes == 0) {
this.firstExceptionTime = System.currentTimeMillis();
}
long currentTime = System.currentTimeMillis();
//С��ָ��ʱ�������ۼ��쳣����������쳣��������ij����ֵ�ͽ�������Դ��Ϊ�����ã������������¼���
if (currentTime - this.firstExceptionTime <= timeInterval) {
++exceptionTimes;
logger.error("����Դ" + name + "��λʱ���ڵ�" + exceptionTimes + "���쳣����ǰʱ�䣺"
+ getCurrentDateTime(currentTime) + "���״��쳣ʱ�䣺"
+ getCurrentDateTime(firstExceptionTime) + "��ʱ����Ϊ��"
+ (currentTime - firstExceptionTime) + "ms.");
if (exceptionTimes >= allowExceptionTimes) {
this.isNotAvailable = true;
logger.error("����Դ" + name + "��ʱ��" + getCurrentDateTime(null) + "���߳���Ŀǰ"
+ operationType + "���Ϳ��õ�����Դ����Ϊ��" + (availableDSNumber - 1));
}
} else {
logger.warn("ͳ���쳣����������λʱ����,�ϴε�λʱ�������쳣����Ϊ" + exceptionTimes + "��,���ڿ�ʼ���¼�����");
this.exceptionTimes = 0;
}
}
/**
* ���쳣���д�����׳���
* @param exceptions
* @param exceptionSorter
* @throws SQLException
*/
public void throwZdalCommnicationException(List<SQLException> exceptions,
ExceptionSorter exceptionSorter) throws SQLException {
int size = exceptions.size();
if (size <= 0) {
throw new IllegalArgumentException("should not be here!");
} else {
//��������µĴ���
int lastElementIndex = size - 1;
//ȡ���һ��exception.�ж��Ƿ������ݿⲻ�����쳣.����ǣ�ȥ�����һ���쳣������ͷ�쳣��װΪZdalCommunicationException�׳�
SQLException lastSQLException = exceptions.get(lastElementIndex);
if (exceptionSorter.isExceptionFatal(lastSQLException)) {
exceptions.remove(lastElementIndex);
exceptions.add(0, new ZdalCommunicationException("zdal communicationException:",
lastSQLException));
}
}
ExceptionUtils.throwSQLException(exceptions, null, null);
}
/**
* ��ȡ��ǰ��ʱ��ĸ�ʽ���ַ���
*/
public String getCurrentDateTime(Long time) {
java.util.Date now;
if (time != null) {
now = new java.util.Date(time);
} else {
now = new java.util.Date();
}
java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(now);
}
// /**
// * �ر�statement
// *
// * @param stmt
// */
// private void closeStatement(Statement stmt) {
// if (stmt != null) {
// try {
// stmt.close();
// } catch (SQLException e) {
// logger.warn("Could not close JDBC Statement", e);
// } catch (Throwable e) {
// logger.warn("Unexpected exception on closing JDBC Statement", e);
// }
// }
// }
//
// /**
// * �ر�����
// *
// * @param conn
// */
// private void closeConnection(Connection conn) {
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// logger.warn("Could not close JDBC Connection", e);
// } catch (Throwable e) {
// logger.warn("Unexpected exception on closing JDBC Connection", e);
// }
// }
// }
}