/** * Alipay.com Inc. * Copyright (c) 2004-2013 All Rights Reserved. */ package com.alipay.zdal.client.util.dispatchanalyzer; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.springframework.jdbc.core.JdbcTemplate; import com.alipay.zdal.client.datasource.keyweight.ZdalDataSourceKeyWeightRandom; import com.alipay.zdal.client.jdbc.AbstractZdalDataSource; import com.alipay.zdal.client.jdbc.DBSelector; import com.alipay.zdal.client.jdbc.ZdalRuntime; import com.alipay.zdal.client.jdbc.dbselector.OneDBSelector; import com.alipay.zdal.common.Constants; import com.alipay.zdal.common.DBType; import com.alipay.zdal.common.RuntimeConfigHolder; /** * * @author zhaofeng.wang * @version $Id: CheckDBAvailableStatus.java, v 0.1 2013-3-20 ����11:32:51 zhaofeng.wang Exp $ */ public class CheckDBAvailableStatus { public static final Logger logger = Logger .getLogger(Constants.CONFIG_LOG_NAME_LOGNAME); /** * Zdal��װ������Դ */ private AbstractZdalDataSource targetDataSource; /** * ��ѵ����Դ״̬���߳� */ private Thread circulateThread = null; /** * Ĭ�ϵ���ѵʱ�䣬��λms */ private long waitTime = 1000L; /** * ͨ��Future��ʽ��ȡ������ӽ���ij�ʱʱ�������������ֵ���׳�timeOutException����λms */ private long timeOutLength = 500L; /** * �������Զ��޳������ݿ�ĸ�����Ĭ��Ϊ-1 */ private int closeDBLimitNumber = -1; /** * �̳߳ص���Сֵ */ private int corePoolSize = 1; /** * �̳߳ص����ֵ */ private int maximumPoolSize = 10; /** * �̳߳ض��г��� */ private int workQueueSize = 100; /** * �̳߳أ�����������ɵ�����IJ��ԣ���ֹһֱ�����ж��� */ private ExecutorService checkDBStatusExecutor; /** * �Ƿ�ʹ���첽�ύ��ʽ */ private boolean isUseFutureMode = true; /** * �첽�߳���ѭ */ void runCirculateThread() { //������ѯ�̳߳� checkDBStatusExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(workQueueSize), new ThreadPoolExecutor.DiscardOldestPolicy()); //�����첽�߳� circulateThread = new Thread(new Runnable() { public void run() { // �ȴ�waitTime�� while (true) { try { Thread.sleep(waitTime); //��������쳣����catchס����ֹ�߳��쳣�˳� circulateDBStatus(); } catch (Exception e) { logger.error("Circulate db status error!", e); } //��ѵ���ݿ⣬��ȡ��״̬,���״̬δ��������������� } } }); circulateThread.start(); } /** * ѭ��ɨ������db��״̬��Ȼ����� */ private synchronized void circulateDBStatus() { //�Ȼ�ȡ���е�γ�ȣ�Ȼ�����α��� Map<String, ZdalDataSourceKeyWeightRandom> keyWeightMapConfig = getTargetDataSource() .getKeyWeightMapConfig(); if (keyWeightMapConfig == null || keyWeightMapConfig.size() == 0) { throw new IllegalArgumentException("The keyWeightMapConfig is empty! "); } boolean isDBStatusChanged = false; //��Χѭ��������ÿһ��ȫ����Ե��� for (Map.Entry<String, ZdalDataSourceKeyWeightRandom> entry : keyWeightMapConfig.entrySet()) { boolean changeFlag = false; String group_key = entry.getKey().trim(); ZdalDataSourceKeyWeightRandom tDataSourceKeyWeightRandom = entry.getValue(); // Map<String, Integer> dbWeightKeysAndWeights = tDataSourceKeyWeightRandom // .getWeightConfig(); String[] dbWeightKeys = tDataSourceKeyWeightRandom.getDBWeightKeys(); int[] weightValues = tDataSourceKeyWeightRandom.getDBWeightValues(); //String[] newDBWeightKeys = new String[dbWeightKeys.length]; int[] newWeightValues = new int[weightValues.length]; //Map<String, Integer> weightKeysAndWeights = new HashMap<String, Integer>(); //�ڲ�ѭ�����������ڵ�ÿһ������Դ�������Ȩ�� int number = 0; //int dbNumberInGroup = dbWeightKeysAndWeights.size(); int dbNumberInGroup = dbWeightKeys.length; for (int i = 0; i < dbNumberInGroup; i++) { String dbKey = dbWeightKeys[i]; Integer weightValue = 10; boolean isAvailable = isAvailableDB(dbKey); //���״̬��������� ������-�����ã� �Լ� ����-�������� ����״̬ if (isAvailable) { //������-������ if (weightValues[i] == 0) { changeFlag = true; logger.warn("The db status will change from NOT to YES,groupName=" + group_key + ",dbName=" + dbKey); } else { if (logger.isDebugEnabled()) { logger.debug("The db is Ok, groupName=" + group_key + ",dbName=" + dbKey); } } } else {// ����-�������� if (weightValues[i] > 0) { if (++number <= getCloseDBLimitNumber(dbNumberInGroup)) { changeFlag = true; weightValue = 0; logger.warn("The db status will change from YES to NOT,dbName=" + dbKey + ",the total number of " + group_key + " is " + dbNumberInGroup + ",and the failed number is " + number); } else { logger.warn("The db status can't change from YES to NOT,dbName=" + dbKey + ",the total number of " + group_key + " is " + dbNumberInGroup + ",and the failed number is " + number); } } else { weightValue = 0; number++; } } newWeightValues[i] = weightValue; //weightKeysAndWeights.put(dbKey, weightValue); } //���������״̬����� if (changeFlag) { isDBStatusChanged = true; // String[] weightKeys = weightKeysAndWeights.keySet().toArray(new String[0]); // int[] weights = new int[weightKeysAndWeights.size()]; //for (int i = 0; i < weights.length; i++) { // weights[i] = weightKeysAndWeights.get(weightKeys[i]); // } ZdalDataSourceKeyWeightRandom TDataSourceKeyWeightRandom = new ZdalDataSourceKeyWeightRandom( dbWeightKeys, newWeightValues); keyWeightMapConfig.put(group_key, TDataSourceKeyWeightRandom); } } if (isDBStatusChanged) { logger.warn("The db status have changed, it is:" + dbKeyWeightToString()); } } /** * ����future�ķ�ʽ������db�Ŀ����� * * @param dbKey * @return */ private boolean isAvailableDB(final String dbKey) { //�����ʹ���첽��ⳬʱ��ʽ if (!isUseFutureMode()) { return checkDBAvailalbeStatusUtil(dbKey); } //�ύִ������ Future<Boolean> future = checkDBStatusExecutor.submit(new Callable<Boolean>() { public Boolean call() throws Exception { try { return checkDBAvailalbeStatusUtil(dbKey); } catch (Exception e) { logger.error("Check db status failed,threadName=" + Thread.currentThread().getName() + ",dbKey=" + dbKey, e); return false; } } }); //��ȡִ�н�� try { return future.get(timeOutLength, TimeUnit.MILLISECONDS); } catch (Exception e) { boolean result = future.cancel(true); logger.error("��ȡ���ݿ�״̬��ʱ:threadName=" + Thread.currentThread().getName() + ",dbKey=" + dbKey + ",cacleResult=" + result + ",workQueueCapacity=" + getWorkQueueCapacity() + ",poolSize=" + this.getPoolSize() + ",activeThreadCount=" + this.getActiveCount() + ",largestPoolSize=" + this.getLargestPoolSize(), e); return false; } finally { if (logger.isDebugEnabled()) { logger.debug("---------------------------ok---------------------------------"); } } } /** * �ж�����Դ�Ƿ����<br> checkDataBaseStatus isAvailableDB * @param dbKey ����Դ��ʶ<br> * @return �Ƿ����<br> */ private boolean checkDBAvailalbeStatusUtil(final String dbKey) { boolean isAvailable = true; RuntimeConfigHolder<ZdalRuntime> runtimeConfigHolder = getTargetDataSource() .getRuntimeConfigHolder(); DBSelector ds = runtimeConfigHolder.get().dbSelectors.get(dbKey); if (ds == null || !(ds instanceof OneDBSelector)) { throw new IllegalArgumentException("DBSelector type error,dbKey=" + dbKey); } DBType dbType = ((OneDBSelector) ds).getDbType(); if (dbType == null) { throw new IllegalArgumentException("The dbType can't be null,please set the property!"); } JdbcTemplate jdbcTemplate = new JdbcTemplate(((OneDBSelector) ds).getDb()); String sql = null; switch (dbType) { case MYSQL: sql = "select 'x' "; break; case ORACLE: sql = "select * from dual"; break; default: throw new IllegalArgumentException("���ݿ����ͳ������������ã�"); } try { jdbcTemplate.queryForList(sql); } catch (Exception e) { logger.error("���Ӹ�db����ThreadName=" + Thread.currentThread().getName() + ",dbKey=" + dbKey + ",sql=" + sql, e); isAvailable = false; } return isAvailable; } /** * ��ȡ����db��״̬ * * @return */ private String dbKeyWeightToString() { Map<String, ZdalDataSourceKeyWeightRandom> keyWeightMapConfig = getTargetDataSource() .getKeyWeightMapConfig(); StringBuilder sb = new StringBuilder(); for (Map.Entry<String, ZdalDataSourceKeyWeightRandom> entry : keyWeightMapConfig.entrySet()) { String groupKey = entry.getKey(); sb.append(groupKey + ":{ "); ZdalDataSourceKeyWeightRandom tDataSourceKeyWeightRandom = entry.getValue(); String[] dbWeightKeys = tDataSourceKeyWeightRandom.getDBWeightKeys(); int[] weightValues = tDataSourceKeyWeightRandom.getDBWeightValues(); for (int i = 0; i < dbWeightKeys.length; i++) { String dbKey = dbWeightKeys[i]; if (weightValues[i] > 0) { sb.append(dbKey + ":Y "); } else { sb.append(dbKey + ":N "); } } sb.append("} "); } return sb.toString(); } /** * Returns the number of elements that this queue can ideally (in * the absence of memory or resource constraints) accept without * blocking. This is always equal to the initial capacity of this queue * less the current <tt>size</tt> of this queue. * <p>Note that you <em>cannot</em> always tell if * an attempt to <tt>add</tt> an element will succeed by * inspecting <tt>remainingCapacity</tt> because it may be the * case that a waiting consumer is ready to <tt>take</tt> an * element out of an otherwise full queue. */ private int getWorkQueueCapacity() { ThreadPoolExecutor tpe = (ThreadPoolExecutor) checkDBStatusExecutor; LinkedBlockingQueue<Runnable> lq = (LinkedBlockingQueue<Runnable>) (tpe.getQueue()); return lq.remainingCapacity(); } /** * Returns the current number of threads in the pool. * * @return the number of threads */ private int getPoolSize() { ThreadPoolExecutor tpe = (ThreadPoolExecutor) checkDBStatusExecutor; return tpe.getPoolSize(); } /** * Returns the approximate number of threads that are actively * executing tasks. * * @return the number of threads */ private int getActiveCount() { ThreadPoolExecutor tpe = (ThreadPoolExecutor) checkDBStatusExecutor; return tpe.getActiveCount(); } /** * Returns the largest number of threads that have ever * simultaneously been in the pool. * * @return the number of threads */ private int getLargestPoolSize() { ThreadPoolExecutor tpe = (ThreadPoolExecutor) checkDBStatusExecutor; return tpe.getLargestPoolSize(); } /** * Returns the approximate total number of tasks that have been * scheduled for execution. Because the states of tasks and * threads may change dynamically during computation, the returned * value is only an approximation, but one that does not ever * decrease across successive calls. * * @return the number of tasks */ @SuppressWarnings("unused") private long getTaskCount() { ThreadPoolExecutor tpe = (ThreadPoolExecutor) checkDBStatusExecutor; return tpe.getTaskCount(); } /** * ��ӡ���� * @see java.lang.Object#toString() */ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("isUseFutureMode=" + this.isUseFutureMode + ",waitTime=" + this.waitTime + ",timeOutLength=" + this.timeOutLength + ",closeDBLimitNumber=" + this.closeDBLimitNumber + ",corePoolSize=" + this.corePoolSize + ",maxinumPoolSize=" + this.maximumPoolSize + ",workQueueSize=" + this.workQueueSize); return sb.toString(); } /** * Setter method for property <tt>targetDataSource</tt>. * * @param targetDataSource value to be assigned to property targetDataSource */ public void setTargetDataSource(AbstractZdalDataSource targetDataSource) { this.targetDataSource = targetDataSource; } /** * Getter method for property <tt>targetDataSource</tt>. * * @return property value of targetDataSource */ public AbstractZdalDataSource getTargetDataSource() { return targetDataSource; } /** * Getter method for property <tt>waitTime</tt>. * * @return property value of waitTime */ public long getWaitTime() { return waitTime; } /** * Setter method for property <tt>waitTime</tt>. * * @param waitTime value to be assigned to property waitTime */ public void setWaitTime(long waitTime) { this.waitTime = waitTime; } /** * Getter method for property <tt>timeOutLength</tt>. * * @return property value of timeOutLength */ public long getTimeOutLength() { return timeOutLength; } /** * Setter method for property <tt>timeOutLength</tt>. * * @param timeOutLength value to be assigned to property timeOutLength */ public void setTimeOutLength(long timeOutLength) { this.timeOutLength = timeOutLength; } /** * Getter method for property <tt>closeDBLimitNumber</tt>. * * @return property value of closeDBLimitNumber */ public int getCloseDBLimitNumber(int dbNumberInGroup) { if (closeDBLimitNumber == -1) { return dbNumberInGroup / 2; } return closeDBLimitNumber; } /** * Setter method for property <tt>closeDBLimitNumber</tt>. * * @param closeDBLimitNumber value to be assigned to property closeDBLimitNumber */ public void setCloseDBLimitNumber(int closeDBLimitNumber) { this.closeDBLimitNumber = closeDBLimitNumber; } /** * Getter method for property <tt>corePoolSize</tt>. * * @return property value of corePoolSize */ public int getCorePoolSize() { return corePoolSize; } /** * Setter method for property <tt>corePoolSize</tt>. * * @param corePoolSize value to be assigned to property corePoolSize */ public void setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; } /** * Getter method for property <tt>maximumPoolSize</tt>. * * @return property value of maximumPoolSize */ public int getMaximumPoolSize() { return maximumPoolSize; } /** * Setter method for property <tt>maximumPoolSize</tt>. * * @param maximumPoolSize value to be assigned to property maximumPoolSize */ public void setMaximumPoolSize(int maximumPoolSize) { this.maximumPoolSize = maximumPoolSize; } /** * Getter method for property <tt>workQueueSize</tt>. * * @return property value of workQueueSize */ public int getWorkQueueSize() { return workQueueSize; } /** * Setter method for property <tt>workQueueSize</tt>. * * @param workQueueSize value to be assigned to property workQueueSize */ public void setWorkQueueSize(int workQueueSize) { this.workQueueSize = workQueueSize; } /** * Getter method for property <tt>isUseFutureMode</tt>. * * @return property value of isUseFutureMode */ public boolean isUseFutureMode() { return isUseFutureMode; } /** * Setter method for property <tt>isUseFutureMode</tt>. * * @param isUseFutureMode value to be assigned to property isUseFutureMode */ public void setIsUseFutureMode(boolean isUseFutureMode) { this.isUseFutureMode = isUseFutureMode; } }