package com.taobao.tddl.group.dbselector; import java.sql.SQLException; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; import com.taobao.tddl.common.jdbc.SQLPreParser; import com.taobao.tddl.common.jdbc.sorter.ExceptionSorter; import com.taobao.tddl.common.jdbc.sorter.MySQLExceptionSorter; import com.taobao.tddl.common.jdbc.sorter.OracleExceptionSorter; import com.taobao.tddl.common.model.DBType; import com.taobao.tddl.common.utils.TStringUtil; import com.taobao.tddl.common.utils.logger.Logger; import com.taobao.tddl.common.utils.logger.LoggerFactory; import com.taobao.tddl.group.config.GroupExtraConfig; import com.taobao.tddl.group.config.GroupIndex; import com.taobao.tddl.group.exception.NoMoreDataSourceException; import com.taobao.tddl.group.exception.SqlForbidException; import com.taobao.tddl.group.jdbc.DataSourceWrapper; import com.taobao.tddl.monitor.utils.NagiosUtils; /** * @author linxuan * @author yangzhu */ public abstract class AbstractDBSelector implements DBSelector { private static final Logger logger = LoggerFactory.getLogger(AbstractDBSelector.class); private static final Map<DBType, ExceptionSorter> exceptionSorters = new HashMap<DBType, ExceptionSorter>(2); static { exceptionSorters.put(DBType.ORACLE, new OracleExceptionSorter()); exceptionSorters.put(DBType.MYSQL, new MySQLExceptionSorter()); } private DBType dbType = DBType.MYSQL; protected ExceptionSorter exceptionSorter = exceptionSorters.get(dbType); private String id = "undefined"; // id值未使用 private static final int default_retryBadDbInterval = 2000; // milliseconds protected static int retryBadDbInterval; // milliseconds static { int interval = default_retryBadDbInterval; String propvalue = System.getProperty("com.taobao.tddl.DBSelector.retryBadDbInterval"); if (propvalue != null) { try { interval = Integer.valueOf(propvalue.trim()); } catch (Exception e) { logger.error("", e); } } retryBadDbInterval = interval; } protected boolean readable = false; public void setReadable(boolean readable) { this.readable = readable; } protected boolean isSupportRetry = true; // 默认情况下支持重试 public boolean isSupportRetry() { return isSupportRetry; } public void setSupportRetry(boolean isSupportRetry) { this.isSupportRetry = isSupportRetry; } public AbstractDBSelector(){ } public AbstractDBSelector(String id){ this.id = id; } protected static class DataSourceHolder { public final DataSourceWrapper dsw; public final ReentrantLock lock = new ReentrantLock(); public volatile boolean isNotAvailable = false; public volatile long lastRetryTime = 0; public DataSourceHolder(DataSourceWrapper dsw){ this.dsw = dsw; } } /** * 在一个数据库上执行,有单线程试读 * * @param <T> * @param dsHolder * @param failedDataSources * @param tryer * @param times * @param args * @return * @throws SQLException */ protected <T> T tryOnDataSourceHolder(DataSourceHolder dsHolder, Map<DataSource, SQLException> failedDataSources, DataSourceTryer<T> tryer, int times, Object... args) throws SQLException { List<SQLException> exceptions = new LinkedList<SQLException>(); if (failedDataSources != null) { exceptions.addAll(failedDataSources.values()); } if (failedDataSources != null && failedDataSources.containsKey(dsHolder.dsw)) { return tryer.onSQLException(exceptions, exceptionSorter, args); } try { if (dsHolder.isNotAvailable) { boolean toTry = System.currentTimeMillis() - dsHolder.lastRetryTime > retryBadDbInterval; if (toTry && dsHolder.lock.tryLock()) { try { T t = tryer.tryOnDataSource(dsHolder.dsw, args); // 同一个时间只会有一个线程继续使用这个数据源。 dsHolder.isNotAvailable = false; // 用一个线程重试,执行成功则标记为可用,自动恢复 return t; } finally { dsHolder.lastRetryTime = System.currentTimeMillis(); dsHolder.lock.unlock(); } } else { exceptions.add(new NoMoreDataSourceException("dsKey:" + dsHolder.dsw.getDataSourceKey() + " not Available,toTry:" + toTry)); return tryer.onSQLException(exceptions, exceptionSorter, args); } } else { return tryer.tryOnDataSource(dsHolder.dsw, args); // 有一次成功直接返回 } } catch (SQLException e) { if (exceptionSorter.isExceptionFatal(e)) { NagiosUtils.addNagiosLog(NagiosUtils.KEY_DB_NOT_AVAILABLE + "|" + dsHolder.dsw.getDataSourceKey(), e.getMessage()); dsHolder.isNotAvailable = true; } exceptions.add(e); return tryer.onSQLException(exceptions, exceptionSorter, args); } } /** * 在指定单库上执行,不过调用方为直接设定group index的,如果指定的数据库不可用, * 然后又指定了ThreadLocalString.RETRY_IF_SET_DS_INDEX 为true,那么走权重(如果有权重的话) * * @param <T> * @param dsHolder * @param failedDataSources * @param tryer * @param times * @param args * @return * @throws SQLException */ protected <T> T tryOnDataSourceHolderWithIndex(DataSourceHolder dsHolder, Map<DataSource, SQLException> failedDataSources, DataSourceTryer<T> tryer, int times, GroupIndex index, Object... args) throws SQLException { List<SQLException> exceptions = new LinkedList<SQLException>(); if (failedDataSources != null) { exceptions.addAll(failedDataSources.values()); } if (failedDataSources != null && failedDataSources.containsKey(dsHolder.dsw)) { return tryer.onSQLException(exceptions, exceptionSorter, args); } try { if (dsHolder.isNotAvailable) { boolean toTry = System.currentTimeMillis() - dsHolder.lastRetryTime > retryBadDbInterval; if (toTry && dsHolder.lock.tryLock()) { try { T t = tryer.tryOnDataSource(dsHolder.dsw, args); // 同一个时间只会有一个线程继续使用这个数据源。 dsHolder.isNotAvailable = false; // 用一个线程重试,执行成功则标记为可用,自动恢复 return t; } finally { dsHolder.lastRetryTime = System.currentTimeMillis(); dsHolder.lock.unlock(); } } else if (index.failRetry) { // FIXME:这里需要看下,如果在事务中,是否应该重试。 return tryExecuteInternal(failedDataSources, tryer, times, args); } else { exceptions.add(new NoMoreDataSourceException("dsKey:" + dsHolder.dsw.getDataSourceKey() + " not Available,toTry:" + toTry)); return tryer.onSQLException(exceptions, exceptionSorter, args); } } else { return tryer.tryOnDataSource(dsHolder.dsw, args); // 有一次成功直接返回 } } catch (SQLException e) { if (exceptionSorter.isExceptionFatal(e)) { NagiosUtils.addNagiosLog(NagiosUtils.KEY_DB_NOT_AVAILABLE + "|" + dsHolder.dsw.getDataSourceKey(), e.getMessage()); dsHolder.isNotAvailable = true; } exceptions.add(e); return tryer.onSQLException(exceptions, exceptionSorter, args); } } protected GroupExtraConfig groupExtraConfig; public <T> T tryExecute(Map<DataSource, SQLException> failedDataSources, DataSourceTryer<T> tryer, int times, Object... args) throws SQLException { // dataSourceIndex放在args最后一个.以后改动要注意 // local set dataSourceIndex was placed first GroupIndex dataSourceIndex = null; if (args != null && args.length > 0) { dataSourceIndex = (GroupIndex) args[args.length - 1]; } if (groupExtraConfig != null) { Boolean defaultMain = groupExtraConfig.isDefaultMain(); Map<String, Integer> tableDsIndexMap = groupExtraConfig.getTableDsIndexMap(); Map<String, Integer> sqlDsIndexMap = groupExtraConfig.getSqlDsIndexMap(); Set<String> sqlForbidSet = groupExtraConfig.getSqlForbidSet(); // 1.when batch ,args have no sql parameter,so,should check // the args // 2.table dataSourceIndex relation have 2th priority // 3.sql dataSourceIndex relation have 3th priority if (args != null && args.length > 0 && args[0] instanceof String) { if (sqlForbidSet != null && sqlForbidSet.size() > 0) { String sql = (String) args[0]; String nomalSql = TStringUtil.fillTabWithSpace(sql); boolean isForbidden = false; if (sqlForbidSet.contains(nomalSql)) { isForbidden = true; } if (!isForbidden) { String actualTable = SQLPreParser.findTableName(nomalSql); for (String configSql : sqlForbidSet) { String nomalConfigSql = TStringUtil.fillTabWithSpace(configSql); String actualConfigTable = SQLPreParser.findTableName(nomalConfigSql); if (TStringUtil.isTableFatherAndSon(actualConfigTable, actualTable)) { nomalConfigSql = nomalConfigSql.replaceAll(actualConfigTable, actualTable); } if (nomalConfigSql.equals(nomalSql)) { isForbidden = true; break; } } } if (isForbidden) { String message = "sql : '" + sql + "' is in forbidden set."; logger.error(message); throw new SqlForbidException(message); } } if (tableDsIndexMap != null && tableDsIndexMap.size() > 0 && (dataSourceIndex == null || dataSourceIndex.index == NOT_EXIST_USER_SPECIFIED_INDEX)) { String sql = (String) args[0]; String actualTable = SQLPreParser.findTableName(sql); Integer index = tableDsIndexMap.get(actualTable); if (index == null || index == NOT_EXIST_USER_SPECIFIED_INDEX) { Set<String> tableSet = tableDsIndexMap.keySet(); for (String configTable : tableSet) { if (TStringUtil.isTableFatherAndSon(configTable, actualTable)) { index = tableDsIndexMap.get(configTable); break; } } } // 这里换了引用,外部引用是不会变的,但是最终清理的时候是同一个线程 // 上的threadlocal变量,所以应该不会有影响。 if (index != null) dataSourceIndex = new GroupIndex(index, false); } if (sqlDsIndexMap != null && sqlDsIndexMap.size() > 0 && (dataSourceIndex == null || dataSourceIndex.index == NOT_EXIST_USER_SPECIFIED_INDEX)) { String sql = ((String) args[0]).toLowerCase(); String nomalSql = TStringUtil.fillTabWithSpace(sql); Integer index = sqlDsIndexMap.get(nomalSql); if (index == null || index == NOT_EXIST_USER_SPECIFIED_INDEX) { String actualTable = SQLPreParser.findTableName(nomalSql); Set<String> sqlSet = sqlDsIndexMap.keySet(); for (String configSql : sqlSet) { String nomalConfigSql = TStringUtil.fillTabWithSpace(configSql); String actualConfigTable = SQLPreParser.findTableName(nomalConfigSql); if (TStringUtil.isTableFatherAndSon(actualConfigTable, actualTable)) { nomalConfigSql = nomalConfigSql.replaceAll(actualConfigTable, actualTable); } if (nomalConfigSql.equals(nomalSql)) { index = sqlDsIndexMap.get(configSql); break; } } } if (index != null) dataSourceIndex = new GroupIndex(index, false); } } // 1.this case simple handled,just set dataSourceIndex=0 // 2.default main have 4th priority if ((dataSourceIndex == null || dataSourceIndex.index == NOT_EXIST_USER_SPECIFIED_INDEX) && defaultMain) { dataSourceIndex = new GroupIndex(0, false); } } // 如果业务层直接指定了一个数据源,就直接在指定的数据源上进行查询更新操作,失败时不再重试。 if (dataSourceIndex != null && dataSourceIndex.index != NOT_EXIST_USER_SPECIFIED_INDEX) { DataSourceHolder dsHolder = findDataSourceWrapperByIndex(dataSourceIndex.index); if (dsHolder == null) { throw new IllegalArgumentException("找不到索引编号为 '" + dataSourceIndex + "'的数据源"); } // return tryOnDataSourceHolder(dsHolder, failedDataSources, tryer, // times, args); return tryOnDataSourceHolderWithIndex(dsHolder, failedDataSources, tryer, times, dataSourceIndex, args); } else { return tryExecuteInternal(failedDataSources, tryer, times, args); } } public <T> T tryExecute(DataSourceTryer<T> tryer, int times, Object... args) throws SQLException { return this.tryExecute(new LinkedHashMap<DataSource, SQLException>(0), tryer, times, args); } public DBType getDbType() { return dbType; } public void setDbType(DBType dbType) { this.dbType = dbType; this.exceptionSorter = exceptionSorters.get(this.dbType); } public final void setExceptionSorter(ExceptionSorter exceptionSorter) { // add by shenxun:主要还是方便测试。。构造整个dbSelector结构太复杂 this.exceptionSorter = exceptionSorter; } public String getId() { return id; } // public abstract DataSource findDataSourceByIndex(int dataSourceIndex); protected abstract DataSourceHolder findDataSourceWrapperByIndex(int dataSourceIndex); protected <T> T tryExecuteInternal(DataSourceTryer<T> tryer, int times, Object... args) throws SQLException { return this.tryExecuteInternal(new LinkedHashMap<DataSource, SQLException>(0), tryer, times, args); } protected abstract <T> T tryExecuteInternal(Map<DataSource, SQLException> failedDataSources, DataSourceTryer<T> tryer, int times, Object... args) throws SQLException; }