package com.taobao.tddl.group.jdbc; import java.io.PrintWriter; import java.sql.SQLException; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; import com.taobao.tddl.common.TddlConstants; import com.taobao.tddl.common.model.DBType; import com.taobao.tddl.common.model.DataSourceType; import com.taobao.tddl.common.utils.mbean.TddlMBeanServer; import com.taobao.tddl.group.config.GroupConfigManager; import com.taobao.tddl.group.dbselector.DBSelector; import com.taobao.tddl.group.exception.TGroupDataSourceException; import com.taobao.tddl.group.listener.DataSourceChangeListener; import com.taobao.tddl.monitor.Monitor; /** * TGroupDataSource并不是像名字所暗示的那样有一组DataSource, * 而是指TGroupDataSource内部包含一组(>=1个)同构的数据库, 这一组数据库的不同个体有不同的读写优先级和权重, * 当读写数据时也只是按读写优先级和权重对其中的一个数据库操作, 如果第一个数据库读写失败了,再尝试下一个数据库, * 如果第一个数据库读写成功了,直接返回结果给应用层, 其他数据库的同步更新由底层数据库内部完成, TGroupDataSource不负责数据同步。 * 使用TGroupDataSource的步骤: * * <pre> * TGroupDataSource tGroupDataSource = new TGroupDataSource(); * tGroupDataSource.setDbGroupKey("myDbGroup"); * // ......调用其他setter * tGroupDataSource.init(); * tGroupDataSource.getConnection(); * </pre> * * @author yangzhu * @author linxuan */ public class TGroupDataSource implements DataSource { public static final String VERSION = "2.4.1"; public static final String PREFIX = "com.taobao.tddl.jdbc.group_V" + VERSION + "_"; public static final String EXTRA_PREFIX = "com.taobao.tddl.jdbc.extra_config.group_V" + VERSION + "_"; private GroupConfigManager configManager; /** * 下面三个为一组,支持本地配置 */ private String dsKeyAndWeightCommaArray; private DataSourceFetcher dataSourceFetcher; private DBType dbType = DBType.MYSQL; private String appName; // app名字 private String unitName; // 单元化名字 private String dbGroupKey; private String fullDbGroupKey = null; // dataId private int retryingTimes = 3; // 默认读写失败时重试3次 private long configReceiveTimeout = TddlConstants.DIAMOND_GET_DATA_TIMEOUT; // 取配置信息的默认超时时间为30秒 // 当运行期间主备发生切换时是否需要查找第一个可写的库 private boolean autoSelectWriteDataSource = false; /* * ======================================================================== * 以下是保留当前写操作是在哪个库上执行的, 满足类似日志库插入的场景 * ====================================================================== */ private static ThreadLocal<DataSourceWrapper> targetThreadLocal; // 下面两个字段当建立实际的DataSource时必须传递过去 // jdbc规范: DataSource刚建立时LogWriter为null private PrintWriter out = null; // jdbc规范: DataSource刚建立时LoginTimeout为0 private int seconds = 0; /** * 使用tbdatasource还是druid */ private DataSourceType dataSourceType = DataSourceType.DruidDataSource; public TGroupDataSource(){ } public TGroupDataSource(String dbGroupKey, String appName){ this.dbGroupKey = dbGroupKey; this.appName = appName; } public TGroupDataSource(String dbGroupKey, String appName, DataSourceType dataSourceType){ this.dbGroupKey = dbGroupKey; this.appName = appName; this.dataSourceType = dataSourceType; } /** * 基于dbGroupKey、appName来初始化多个TAtomDataSource * * @throws com.taobao.tddl.jdbc.group.exception.ConfigException */ public void init() { if (dsKeyAndWeightCommaArray != null) { // 本地配置方式:dsKeyAndWeightCommaArray + dataSourceFetcher + dyType DataSourceFetcher wrapper = new DataSourceFetcher() { @Override public DataSource getDataSource(String key) { return dataSourceFetcher.getDataSource(key); } @Override public DBType getDataSourceDBType(String key) { DBType type = dataSourceFetcher.getDataSourceDBType(key); return type == null ? dbType : type; // 如果dataSourceFetcher没dbType,用tgds的dbType } }; List<DataSourceWrapper> dss = GroupConfigManager.buildDataSourceWrapper(dsKeyAndWeightCommaArray, wrapper); init(dss); } else { checkProperties(); configManager = new GroupConfigManager(this); configManager.init(); } Monitor.setAppName(appName); } public void init(DataSourceWrapper... dataSourceWrappers) { init(Arrays.asList(dataSourceWrappers)); } public void init(List<DataSourceWrapper> dataSourceWrappers) { configManager = new GroupConfigManager(this); configManager.init(dataSourceWrappers); } public static TGroupDataSource build(String groupKey, String dsWeights, DataSourceFetcher fetcher, DataSourceType dataSourceType) { List<DataSourceWrapper> dss = GroupConfigManager.buildDataSourceWrapper(dsWeights, fetcher); TGroupDataSource tGroupDataSource = new TGroupDataSource(); tGroupDataSource.setDataSourceType(dataSourceType); tGroupDataSource.setDbGroupKey(groupKey); tGroupDataSource.init(dss); return tGroupDataSource; } /** * 如果构造的是TAtomDataSource,必须检查dbGroupKey、appName两个属性的值是否合法 */ private void checkProperties() { if (dbGroupKey == null) { throw new TGroupDataSourceException("dbGroupKey不能为null"); } dbGroupKey = dbGroupKey.trim(); if (dbGroupKey.length() < 1) { throw new TGroupDataSourceException("dbGroupKey的长度要大于0,前导空白和尾部空白不算在内"); } if (appName == null) { throw new TGroupDataSourceException("appName不能为null"); } appName = appName.trim(); if (appName.length() < 1) { throw new TGroupDataSourceException("appName的长度要大于0,前导空白和尾部空白不算在内"); } if (dataSourceType == null) { throw new TGroupDataSourceException("dataSouceType不能为null"); } } /** * 危险接口。一般用于测试。应用也可以直接通过该接口重置数据源配置 */ public void resetDbGroup(String configInfo) { configManager.resetDbGroup(configInfo); } // 包访问级别,调用者不能缓存,否则会失去动态性 DBSelector getDBSelector(boolean isRead) { return configManager.getDBSelector(isRead, this.autoSelectWriteDataSource); } /** * 通过spring注入或直接调用该方法开启、关闭目标库记录 */ public void setTracerWriteTarget(boolean isTraceTarget) { if (isTraceTarget) { if (targetThreadLocal == null) { targetThreadLocal = new ThreadLocal<DataSourceWrapper>(); } } else { targetThreadLocal = null; } } /** * 在执行完写操作后,调用改方法获得当前线程写操作是在哪个数据源执行的 获取完自动立即清空 */ public DataSourceWrapper getCurrentTarget() { if (targetThreadLocal == null) { return null; } DataSourceWrapper dsw = targetThreadLocal.get(); targetThreadLocal.remove(); return dsw; } /** * 下游调用该方法设置目标库 */ void setWriteTarget(DataSourceWrapper dsw) { if (targetThreadLocal != null) { targetThreadLocal.set(dsw); } } /* * ======================================================================== * 遍历需求API * ====================================================================== */ // 在ConfigManager中我们将配置信息最终封装为读写DBSelector,要得到从dbKey到DataSource的映射,将DBSelector中的信息方向输出。 public Map<String, DataSource> getDataSourceMap() { Map<String, DataSource> dsMap = new LinkedHashMap<String, DataSource>(); dsMap.putAll(this.getDBSelector(true).getDataSources()); dsMap.putAll(this.getDBSelector(false).getDataSources()); return dsMap; } public Map<String, DataSource> getDataSourcesMap(boolean isRead) { return this.getDBSelector(isRead).getDataSources(); } public void setDataSourceChangeListener(DataSourceChangeListener dataSourceChangeListener) { this.configManager.setDataSourceChangeListener(dataSourceChangeListener); } /* * ======================================================================== * 以下是javax.sql.DataSource的API实现 * ====================================================================== */ @Override public TGroupConnection getConnection() throws SQLException { return new TGroupConnection(this); } @Override public TGroupConnection getConnection(String username, String password) throws SQLException { return new TGroupConnection(this, username, password); } @Override public PrintWriter getLogWriter() throws SQLException { return out; } @Override public void setLogWriter(PrintWriter out) throws SQLException { this.out = out; } @Override public int getLoginTimeout() throws SQLException { return seconds; } @Override public void setLoginTimeout(int seconds) throws SQLException { this.seconds = seconds; } public static void setShutDownMBean(boolean shutDownMBean) { TddlMBeanServer.shutDownMBean = shutDownMBean; } public String getUnitName() { return unitName; } public void setUnitName(String unitName) { this.unitName = unitName; } public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public String getDbGroupKey() { return dbGroupKey; } public String getFullDbGroupKey() { if (fullDbGroupKey == null) { fullDbGroupKey = PREFIX + getDbGroupKey(); } return fullDbGroupKey; } public String getDbGroupExtraConfigKey() { return EXTRA_PREFIX + getDbGroupKey() + "." + getAppName(); } public void setDbGroupKey(String dbGroupKey) { this.dbGroupKey = dbGroupKey; } public int getRetryingTimes() { return retryingTimes; } public void setRetryingTimes(int retryingTimes) { this.retryingTimes = retryingTimes; } public long getConfigReceiveTimeout() { return configReceiveTimeout; } public void setConfigReceiveTimeout(long configReceiveTimeout) { this.configReceiveTimeout = configReceiveTimeout; } public void setDsKeyAndWeightCommaArray(String dsKeyAndWeightCommaArray) { this.dsKeyAndWeightCommaArray = dsKeyAndWeightCommaArray; } public boolean getAutoSelectWriteDataSource() { return autoSelectWriteDataSource; } public void setAutoSelectWriteDataSource(boolean autoSelectWriteDataSource) { this.autoSelectWriteDataSource = autoSelectWriteDataSource; } public void setDataSourceFetcher(DataSourceFetcher dataSourceFetcher) { this.dataSourceFetcher = dataSourceFetcher; } public void setDbType(DBType dbType) { this.dbType = dbType; } public String getDsKeyAndWeightCommaArray() { return dsKeyAndWeightCommaArray; } public static final String getFullDbGroupKey(String dbGroupKey) { return PREFIX + dbGroupKey; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return this.getClass().isAssignableFrom(iface); } @Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { try { return (T) this; } catch (Exception e) { throw new SQLException(e); } } public DataSourceType getDataSourceType() { return dataSourceType; } public void setDataSourceType(DataSourceType dataSourceType) { this.dataSourceType = dataSourceType; } public void setDataSourceType(String dataSourceType) { this.dataSourceType = DataSourceType.valueOf(dataSourceType); } public DBType getDbType() { return dbType; } /** * 销毁数据源,慎用 * * @throws Exception */ public void destroyDataSource() throws Exception { if (configManager != null) { configManager.destroyDataSource(); } } }