package com.taobao.tddl.atom.jdbc; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; import com.taobao.tddl.atom.TAtomDbStatusEnum; import com.taobao.tddl.atom.TAtomDbTypeEnum; import com.taobao.tddl.atom.config.TAtomDsConfDO; import com.taobao.tddl.atom.exception.AtomNotAvailableException; import com.taobao.tddl.atom.utils.ConnRestrictEntry; import com.taobao.tddl.atom.utils.ConnRestrictSlot; import com.taobao.tddl.atom.utils.ConnRestrictor; import com.taobao.tddl.atom.utils.CountPunisher; import com.taobao.tddl.atom.utils.SmoothValve; import com.taobao.tddl.atom.utils.TimesliceFlowControl; 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.utils.TStringUtil; import com.taobao.tddl.monitor.Monitor; import com.taobao.tddl.monitor.SnapshotValuesOutputCallBack; import com.taobao.tddl.monitor.stat.AbstractStatLogWriter.LogCounter; import com.taobao.tddl.monitor.stat.StatLogWriter; import com.taobao.tddl.monitor.utils.NagiosUtils; public class TDataSourceWrapper implements DataSource, SnapshotValuesOutputCallBack { private static Log logger = LogFactory.getLog(TDataSourceWrapper.class); private final DataSource targetDataSource; /** * 当前线程的threadCount值,如果进行了切换。 那么使用的是不同的Datasource包装类,不会相互影响。 * threadCount输出在切换过程中在那个时候不能反应准确的值。 * 但因为旧的被丢弃前也有用,等于在内存中维持了两份不同的TDataSourceWrapper. 因此线程计数不会额外增加。 */ final AtomicInteger threadCount = new AtomicInteger(); // 包权限 final AtomicInteger threadCountReject = new AtomicInteger(); // 包权限 final AtomicInteger concurrentReadCount = new AtomicInteger(); // 包权限 final AtomicInteger concurrentWriteCount = new AtomicInteger(); // 包权限 volatile TimesliceFlowControl writeFlowControl; // 包权限 volatile TimesliceFlowControl readFlowControl; // 包权限 /** * 写计数 */ // final AtomicInteger writeTimes = new AtomicInteger();//包权限 final AtomicInteger writeTimesReject = new AtomicInteger(); // 包权限 /** * 读计数 */ // final AtomicInteger readTimes = new AtomicInteger();//包权限 final AtomicInteger readTimesReject = new AtomicInteger(); // 包权限 volatile ConnectionProperties connectionProperties = new ConnectionProperties(); // 包权限 /** * 应用连接限制 */ private ConnRestrictor connRestrictor; // changyuan.lh: 并发连接数和阻塞等待的统计对象 private LogCounter statConnNumber; private LogCounter statConnBlocking; // final private Timer timer = new Timer(); // private volatile TimerTask timerTask = new TimerTaskC(); protected TAtomDsConfDO runTimeConf; private static final Map<String, ExceptionSorter> exceptionSorters = new HashMap<String, ExceptionSorter>(2); static { exceptionSorters.put(TAtomDbTypeEnum.ORACLE.name(), new OracleExceptionSorter()); exceptionSorters.put(TAtomDbTypeEnum.MYSQL.name(), new MySQLExceptionSorter()); } private final ReentrantLock lock = new ReentrantLock(); // private volatile boolean isNotAvailable = false; //是否不可用 private volatile SmoothValve smoothValve = new SmoothValve(0); private volatile CountPunisher timeOutPunisher = new CountPunisher(new SmoothValve(0), 3000, 300); // 3秒钟之内超时300次则惩罚,不可能的阀值,相当于关闭了 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; } public TAtomDbStatusEnum getDbStatus() { return connectionProperties.dbStatus; } public void setDbStatus(TAtomDbStatusEnum dbStatus) { this.connectionProperties.dbStatus = dbStatus; } public static class ConnectionProperties { public volatile TAtomDbStatusEnum dbStatus; /** * 当前数据库的名字 */ public volatile String datasourceName; // add by junyu,2012-4-17,日志统计使用 public volatile String ip; public volatile String port; public volatile String realDbName; /** * 写次数限制,0为不限制 */ // public volatile int writeRestrictionTimes; /** * 读次数限制,0为不限制 */ // public volatile int readRestrictionTimes; /** * 线程count限制,0为不限制 */ public volatile int threadCountRestriction; /** * 允许并发读的最大个数,0为不限制 */ public volatile int maxConcurrentReadRestrict; /** * 允许并发写的最大个数,0为不限制 */ public volatile int maxConcurrentWriteRestrict; } public TDataSourceWrapper(DataSource targetDataSource, TAtomDsConfDO runTimeConf){ this.runTimeConf = runTimeConf; this.targetDataSource = targetDataSource; // timerTask = new TimerTaskC(); Monitor.addSnapshotValuesCallbask(this); // Monitor.addGlobalConfigListener(globalConfigListener); // timer.schedule(timerTask, 0, // this.connectionProperties.timeSliceInMillis); this.readFlowControl = new TimesliceFlowControl("读流量", runTimeConf.getTimeSliceInMillis(), runTimeConf.getReadRestrictTimes()); this.writeFlowControl = new TimesliceFlowControl("写流量", runTimeConf.getTimeSliceInMillis(), runTimeConf.getWriteRestrictTimes()); logger.warn("set thread count restrict " + runTimeConf.getThreadCountRestrict()); this.connectionProperties.threadCountRestriction = runTimeConf.getThreadCountRestrict(); // logger.warn("set write restrict times " + // runTimeConf.getWriteRestrictTimes()); // this.connectionProperties.writeRestrictionTimes = // runTimeConf.getWriteRestrictTimes(); // logger.warn("set read restrict times " + // runTimeConf.getReadRestrictTimes()); // this.connectionProperties.readRestrictionTimes = // runTimeConf.getReadRestrictTimes(); logger.warn("set maxConcurrentReadRestrict " + runTimeConf.getMaxConcurrentReadRestrict()); this.connectionProperties.maxConcurrentReadRestrict = runTimeConf.getMaxConcurrentReadRestrict(); logger.warn("set maxConcurrentWriteRestrict " + runTimeConf.getMaxConcurrentWriteRestrict()); this.connectionProperties.maxConcurrentWriteRestrict = runTimeConf.getMaxConcurrentWriteRestrict(); } public void init() { // changyuan.lh: 初始化连接分桶 final String datasourceKey = connectionProperties.datasourceName; List<ConnRestrictEntry> connRestrictEntries = runTimeConf.getConnRestrictEntries(); if (connRestrictEntries != null) { this.connRestrictor = new ConnRestrictor(datasourceKey, connRestrictEntries); } this.statConnNumber = Monitor.connStat(datasourceKey, "-", Monitor.KEY3_CONN_NUMBER); this.statConnBlocking = Monitor.connStat(datasourceKey, "-", Monitor.KEY3_CONN_BLOCKING); // timerTask = new TimerTaskC(); Monitor.addSnapshotValuesCallbask(this); // Monitor.addGlobalConfigListener(globalConfigListener); // timer.schedule(timerTask, 0, // this.connectionProperties.timeSliceInMillis); } // 包权限,给下游对象调用 void countTimeOut() { timeOutPunisher.count(); } private volatile long lastRetryTime = 0; @Override public Connection getConnection() throws SQLException { return getConnection(null, null); } /** * 这里只做了tryLock连接尝试,真正的逻辑委派给getConnection0 */ @Override public Connection getConnection(String username, String password) throws SQLException { SmoothValve valve = smoothValve; try { // modify by junyu,暂时去掉这个功能。 // if (!runTimeConf.isSingleInGroup() && timeOutPunisher.punish()) { // //group里只剩一个时不做超时惩罚。再慢也得干活 // throw new AtomSlowPunishException(this.runTimeConf.getDbName() + // "'s timeout " + timeOutPunisher); //超时惩罚 // } if (valve.isNotAvailable()) { boolean toTry = System.currentTimeMillis() - lastRetryTime > retryBadDbInterval; if (toTry && lock.tryLock()) { try { Connection t = this.getConnection0(username, password); // 同一个时间只会有一个线程继续使用这个数据源。 // isNotAvailable = false; //用一个线程重试,执行成功则标记为可用,自动恢复 valve.setAvailable(); // 用一个线程重试,执行成功则标记为可用,自动恢复 return t; } finally { lastRetryTime = System.currentTimeMillis(); lock.unlock(); } } else { throw new AtomNotAvailableException(this.runTimeConf.getDbName() + " isNotAvailable"); // 其他线程fail-fast } } else { if (valve.smoothThroughOnInitial()) { return this.getConnection0(username, password); } else { throw new AtomNotAvailableException(this.runTimeConf.getDbName() + " squeezeThrough rejected on fatal reset"); // 未通过复位时的限流保护 } } } catch (SQLException e) { ExceptionSorter exceptionSorter = exceptionSorters.get(TStringUtil.upperCase(this.runTimeConf.getDbType())); if (exceptionSorter.isExceptionFatal(e)) { NagiosUtils.addNagiosLog(NagiosUtils.KEY_DB_NOT_AVAILABLE + "|" + this.runTimeConf.getDbName(), e.getMessage()); // isNotAvailable = true; valve.setNotAvailable(); } throw new SQLException("get connection failed,dbKey is " + (connectionProperties.datasourceName != null ? connectionProperties.datasourceName : this.runTimeConf.getDbName()), e); } } private Connection getConnection0(String username, String password) throws SQLException { final long callMillis = System.currentTimeMillis(); ConnRestrictSlot connRestrictSlot = null; TConnectionWrapper tconnectionWrapper; try { recordThreadCount(); if (connRestrictor != null) { connRestrictSlot = connRestrictor.doRestrict(runTimeConf.getBlockingTimeout()); } tconnectionWrapper = new TConnectionWrapper(getConnectionByTargetDataSource(username, password), connRestrictSlot, this); } catch (SQLException e) { if (connRestrictSlot != null) { connRestrictSlot.freeConnection(); } threadCount.decrementAndGet(); throw e; } catch (RuntimeException e) { if (connRestrictSlot != null) { connRestrictSlot.freeConnection(); } threadCount.decrementAndGet(); throw e; } finally { // changyuan.lh: 记录统计信息 final long connMillis = System.currentTimeMillis() - callMillis; if (connRestrictSlot != null) { connRestrictSlot.statConnection(connMillis); } addConnStat(connMillis); } return tconnectionWrapper; } /** * changyuan.lh: 记录统计信息 */ private final void addConnStat(final long connMillis) { statConnNumber.stat(1, threadCount.get()); statConnBlocking.stat(1, connMillis); } private Connection getConnectionByTargetDataSource(String username, String password) throws SQLException { if (username == null && password == null) { return targetDataSource.getConnection(); } else { return targetDataSource.getConnection(username, password); } } private void recordThreadCount() throws SQLException { int threadCountRestriction = connectionProperties.threadCountRestriction; int currentThreadCount = threadCount.incrementAndGet(); if (threadCountRestriction != 0) { if (currentThreadCount > threadCountRestriction) { threadCountReject.incrementAndGet(); throw new SQLException("max thread count : " + currentThreadCount); } } } /** * 设置 * * @param datasourceName */ public synchronized void setDatasourceName(String datasourceName) { this.connectionProperties.datasourceName = datasourceName; } public synchronized void setDatasourceIp(String ip) { this.connectionProperties.ip = ip; } public synchronized void setDatasourcePort(String port) { this.connectionProperties.port = port; } public synchronized void setDatasourceRealDbName(String realDbName) { this.connectionProperties.realDbName = realDbName; } /** * 设置时间片,在这个时候要重新制定计划。 bug fix : 以前没有重新制定schedule.导致这个设置是无效的 * * @param timeSliceInMillis */ public synchronized void setTimeSliceInMillis(int timeSliceInMillis) { if (timeSliceInMillis == 0) { logger.warn("timeSliceInMills is 0,return "); } /* * timerTask.cancel(); timer.purge(); timerTask = new TimerTaskC(); * timer.schedule(timerTask, 0, timeSliceInMillis); */ this.readFlowControl = new TimesliceFlowControl("读流量", timeSliceInMillis, runTimeConf.getReadRestrictTimes()); this.writeFlowControl = new TimesliceFlowControl("写流量", timeSliceInMillis, runTimeConf.getWriteRestrictTimes()); // this.connectionProperties.timeSliceInMillis = timeSliceInMillis; } /* * public ConnectionProperties getConnectionProperties() { return * connectionProperties; } public synchronized void * setConnectionProperties(ConnectionProperties connectionProperties) { * this.connectionProperties = connectionProperties; } */ /* * private volatile Values lastReadWriteSnapshot = new Values(); private * class TimerTaskC extends TimerTask { * @Override public void run() { lastReadWriteSnapshot = new Values(); * lastReadWriteSnapshot.value1.set(readTimes.longValue()); * lastReadWriteSnapshot.value2.set(writeTimes.longValue()); * readTimes.set(0); writeTimes.set(0); } } private * SnapshotValuesOutputCallBack snapshotValuesOutputCallBack = new * SnapshotValuesOutputCallBack() { * @Override public ConcurrentHashMap<String, Values> getValues() { * ConcurrentHashMap<String, Values> concurrentHashMap = new * ConcurrentHashMap<String, Values>(); String prefix = * connectionProperties.datasourceName + "_"; // 添加threadCount Values * threadCountValues = new Values(); * threadCountValues.value1.set(threadCount.longValue()); * threadCountValues.value2 * .set(connectionProperties.threadCountRestriction); * concurrentHashMap.put(prefix + Key.THREAD_COUNT, threadCountValues); * //添加读写拒绝次数 Values rejectCountValues = new Values(); * rejectCountValues.value1.set(readTimesReject.longValue()); * rejectCountValues.value2.set(writeTimesReject.longValue()); * concurrentHashMap.put(prefix + Key.READ_WRITE_TIMES_REJECT_COUNT, * rejectCountValues); // 添加读写count concurrentHashMap.put(prefix + * Key.READ_WRITE_TIMES, lastReadWriteSnapshot); //添加读写并发次数 Values * rwConcurrent = new Values(); * rwConcurrent.value1.set(concurrentReadCount.longValue()); * rwConcurrent.value2.set(concurrentWriteCount.longValue()); * concurrentHashMap.put(prefix + Key.READ_WRITE_CONCURRENT, rwConcurrent); * return concurrentHashMap; } }; private GlobalConfigListener * globalConfigListener = new GlobalConfigListener() { public void * onConfigReceive(Properties p) { for (Map.Entry<Object, Object> entry : * p.entrySet()) { String key = ((String) entry.getKey()).trim(); String * value = ((String) entry.getValue()).trim(); switch * (TDDLConfigKey.valueOf(key)) { case SmoothValveProperties: { SmoothValve * old = smoothValve; SmoothValve nnn = SmoothValve.parse(value); if (nnn != * null) { logger.warn("smoothValve switch from [" + old + "] to [" + nnn + * "]"); smoothValve = nnn; } break; } case CountPunisherProperties: { * CountPunisher old = timeOutPunisher; CountPunisher nnn = * CountPunisher.parse(smoothValve, value); if (nnn != null) { * logger.warn("timeOutPunisher switch from [" + old + "] to [" + nnn + * "]"); timeOutPunisher = nnn; } break; } default: break; } } } }; public * void destroy() { * Monitor.removeSnapshotValuesCallback(snapshotValuesOutputCallBack); * Monitor.removeGlobalConfigListener(globalConfigListener); } */ /* * ======================================================================== * ===== jdbc接口方法,简单委派给targetDataSource * ====================================================================== */ @Override public PrintWriter getLogWriter() throws SQLException { return targetDataSource.getLogWriter(); } @Override public void setLogWriter(PrintWriter out) throws SQLException { targetDataSource.setLogWriter(out); } @Override public void setLoginTimeout(int seconds) throws SQLException { targetDataSource.setLoginTimeout(seconds); } @Override public int getLoginTimeout() throws SQLException { return targetDataSource.getLoginTimeout(); } /** * jdk1.6 新增接口 */ @Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { if (isWrapperFor(iface)) { return (T) this; } else { throw new SQLException("not a wrapper for " + iface); } } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return TDataSourceWrapper.class.isAssignableFrom(iface); } @Override public void snapshotValues(StatLogWriter statLog) { String prefix = connectionProperties.datasourceName + "_"; // 添加threadCount statLog.log(prefix + Key.THREAD_COUNT, threadCount.longValue(), connectionProperties.threadCountRestriction); // 添加读写拒绝次数 statLog.log(prefix + Key.READ_WRITE_TIMES_REJECT_COUNT, readTimesReject.longValue() + this.readFlowControl.getTotalRejectCount(), writeTimesReject.longValue() + this.writeFlowControl.getTotalRejectCount()); // 添加读写count statLog.log(prefix + Key.READ_WRITE_TIMES, this.readFlowControl.getCurrentCount(), this.writeFlowControl.getCurrentCount()); // 添加读写并发次数 statLog.log(prefix + Key.READ_WRITE_CONCURRENT, this.concurrentReadCount.longValue(), this.concurrentWriteCount.longValue()); } }