package com.taobao.tddl.rule; import java.io.IOException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.taobao.tddl.common.exception.TddlException; import com.taobao.tddl.common.model.lifecycle.AbstractLifecycle; import com.taobao.tddl.common.model.lifecycle.Lifecycle; import com.taobao.tddl.common.utils.TStringUtil; import com.taobao.tddl.config.ConfigDataHandler; import com.taobao.tddl.config.ConfigDataHandlerFactory; import com.taobao.tddl.config.ConfigDataListener; import com.taobao.tddl.config.impl.UnitConfigDataHandlerFactory; import com.taobao.tddl.monitor.logger.LoggerInit; import com.taobao.tddl.rule.config.RuleChangeListener; import com.taobao.tddl.rule.exceptions.TddlRuleException; import com.taobao.tddl.rule.utils.StringXmlApplicationContext; import com.taobao.tddl.common.utils.logger.Logger; import com.taobao.tddl.common.utils.logger.LoggerFactory; /** * tddl rule config管理 * * @author jianghang 2013-11-5 下午3:34:36 * @since 5.0.0 */ public class TddlRuleConfig extends AbstractLifecycle implements Lifecycle { protected static final Logger logger = LoggerFactory.getLogger(TddlRuleConfig.class); private static final int TIMEOUT = 10 * 1000; private static final String ROOT_BEAN_NAME = "vtabroot"; private static final String TDDL_RULE_LE_PREFIX = "com.taobao.tddl.rule.le."; private static final String TDDL_RULE_LE_VERSIONS_FORMAT = "com.taobao.tddl.rule.le.{0}.versions"; private static final String NO_VERSION_NAME = "__VN__"; private String appName; private String unitName; // 本地规则 private String appRuleFile; private String appRuleString; // 多套规则(动态推) private volatile ConfigDataHandlerFactory cdhf; private volatile ConfigDataHandler versionHandler; private volatile Map<String, ConfigDataHandler> ruleHandlers = Maps.newHashMap(); private volatile List<RuleChangeListener> listeners = Lists.newArrayList(); /** * key = 0(old),1(new),2,3,4... value= version */ private volatile Map<String, VirtualTableRoot> vtrs = Maps.newLinkedHashMap(); private volatile Map<String, String> ruleStrs = Maps.newHashMap(); private volatile Map<Integer, String> versionIndex = Maps.newHashMap(); private volatile Map<String, AbstractXmlApplicationContext> oldCtxs = Maps.newHashMap(); private ClassLoader outerClassLoader = null; // 是否兼容历史老的rule,主要是tdd5代码修改过类的全路径,针对tddl3之前的rule需要考虑做兼容处理 private boolean compatibleOldRule = true; public void doInit() { if (appRuleFile != null) { // 如果存在本地规则 String[] rulePaths = appRuleFile.split(";"); if (rulePaths.length == 1 && !rulePaths[0].matches("^V[0-9]*#.+$")) { // 本地文件单版本规则 ApplicationContext ctx = buildRuleByFile(NO_VERSION_NAME, appRuleFile); vtrs.put(NO_VERSION_NAME, (VirtualTableRoot) ctx.getBean(ROOT_BEAN_NAME)); } else { // 本地文件存在多版本规则 // 一种文件配置写法: V0#classpath:xxx-rule.xml for (int i = 0; i < rulePaths.length; i++) { if (rulePaths[i].matches("^V[0-9]*#.+$")) { continue; } else { throw new TddlRuleException("rule file path \"" + rulePaths[i] + " \" does not fit the pattern!"); } } for (int i = 0; i < rulePaths.length; i++) { String rulePath = rulePaths[i]; String[] temp = rulePath.split("#"); ApplicationContext ctx = buildRuleByFile(temp[0], temp[1]); vtrs.put(temp[0], (VirtualTableRoot) ctx.getBean(ROOT_BEAN_NAME)); } } } else if (appRuleString != null) { // 直接设置了规则字符串 ApplicationContext ctx = buildRuleByStr(NO_VERSION_NAME, appRuleString); vtrs.put(NO_VERSION_NAME, (VirtualTableRoot) ctx.getBean(ROOT_BEAN_NAME)); } else if (appName != null) { // 使用动态rule配置 String versionsDataId = getVersionsDataId(appName); if (cdhf == null) { cdhf = new UnitConfigDataHandlerFactory(unitName, appName); } versionHandler = cdhf.getConfigDataHandler(versionsDataId, new VersionsConfigListener()); String versionData = versionHandler.getData(TIMEOUT, ConfigDataHandler.FIRST_CACHE_THEN_SERVER_STRATEGY); if (versionData == null) { // fallback处理一下,无版本的rule String dataId = getNonversionedRuledataId(versionData); if (!dataSub(dataId, NO_VERSION_NAME, new SingleRuleConfigListener())) { throw new TddlRuleException("subscribe the rule data or init rule error!check the error log!"); } } else { String[] versions = versionData.split(","); for (String version : versions) { String dataId = getVersionedRuleDataId(appName, version); if (!dataSub(dataId, version, new SingleRuleConfigListener())) { throw new RuntimeException("subscribe the rule data or init rule error!check the error log! the rule version is:" + version); } } // 记下日志,方便分析 logRecieveRuleVersions(versionData); } } // 构建versionIndex int index = 0; Map<Integer, String> tempIndexMap = new HashMap<Integer, String>(); for (String version : vtrs.keySet()) { tempIndexMap.put(index, version); index++; } this.versionIndex = tempIndexMap; } /** * <pre> * 返回当前使用的rule规则 * 1. 如果是本地文件,则直接返回本地文件的版本 (本地文件存在多版本时,直接返回第一个版本) * 2. 如果是动态规则,则直接返回第一个版本 * * ps. 正常情况,只有一个版本会处于使用中,也就是在数据库动态切换出现多版本使用中. * </pre> */ public VirtualTableRoot getCurrentRule() { if (versionIndex.size() == 0) { throw new TddlRuleException("规则对象为空!请检查是否存在规则!"); } return vtrs.get(versionIndex.get(0)); } public VirtualTableRoot getVersionRule(String version) { VirtualTableRoot vtr = vtrs.get(version); if (vtr == null) { throw new TddlRuleException("规则对象为空!请检查是否存在规则!"); } return vtr; } /** * 获取当前在用的版本,理论上正常只有一个版本(切换时出现两个版本),顺序返回版本,第一个版本为当前正在使用中的旧版本 */ public List<String> getAllVersions() { int size = versionIndex.size(); List<String> versions = Lists.newArrayList(); for (int i = 0; i < size; i++) { versions.add(versionIndex.get(i)); } return versions; } /** * 初始化某个版本的rule */ private synchronized boolean initVersionRule(String data, String version) { if (version == null) { version = NO_VERSION_NAME; } ApplicationContext ctx = null; try { // this rule may be wrong rule,don't throw it but log it, // and will not change the vtr! ctx = buildRuleByStr(version, data); } catch (Exception e) { logger.error("init rule error,rule str is:" + data, e); return false; } VirtualTableRoot tempvtr = (VirtualTableRoot) ctx.getBean(ROOT_BEAN_NAME); if (tempvtr != null) { // 直接覆盖 vtrs.put(version, tempvtr); ruleStrs.put(version, data); AbstractXmlApplicationContext oldCtx = this.oldCtxs.get(version); // 销毁旧有容器 if (oldCtx != null) { oldCtx.close(); } // 记录一下当前ctx this.oldCtxs.remove(version); this.oldCtxs.put(version, (AbstractXmlApplicationContext) ctx); } else { logger.error("rule no vtabroot!!"); return false; } return true; } /** * 尝试订阅一下 * * @throws TddlException */ private boolean dataSub(String dataId, String version, ConfigDataListener listener) { ConfigDataHandler ruleHandler = cdhf.getConfigDataHandler(dataId, listener); try { String data = ruleHandler.getData(TIMEOUT, ConfigDataHandler.FIRST_CACHE_THEN_SERVER_STRATEGY); if (data == null) { logger.error("use diamond rule config,but recieve no config at all!"); return false; } if (initVersionRule(data, version)) { this.ruleHandlers.put(version, ruleHandler); return true; } } catch (Exception e) { try { ruleHandler.destory(); } catch (TddlException e1) { logger.error("destory failed!", e); } logger.error("get diamond data error!", e); } return false; } /** * remove listeners * * @throws TddlException */ public void doDestory() throws TddlException { if (versionHandler != null) { versionHandler.destory(); } for (ConfigDataHandler ruleListener : this.ruleHandlers.values()) { ruleListener.destory(); } } // ======================= help metod ================== /** * 基于文件创建rule的spring容器 * * @param file * @return */ private ApplicationContext buildRuleByFile(String version, String file) { try { Resource resource = new PathMatchingResourcePatternResolver().getResource(file); String ruleStr = StringUtils.join(IOUtils.readLines(resource.getInputStream()), SystemUtils.LINE_SEPARATOR); return buildRuleByStr(version, ruleStr); } catch (IOException e) { throw new TddlRuleException(e); } } /** * 基于string字符流创建rule的spring容器 * * @param data * @return */ private ApplicationContext buildRuleByStr(String version, String data) { if (compatibleOldRule) { data = RuleCompatibleHelper.compatibleRule(data); } ApplicationContext applicationContext = new StringXmlApplicationContext(data, outerClassLoader); ruleStrs.put(version, data); // 记录一下 return applicationContext; } private String getCurrentRuleStr() { if (this.ruleStrs != null && this.ruleStrs.size() > 0) { String ruleStr = this.ruleStrs.get(versionIndex.get(0)); return ruleStr; } else { throw new TddlRuleException("规则对象为空!请检查diamond上是否存在动态规则!"); } } public void logRecieveRuleVersions(String version) { if (LoggerInit.DYNAMIC_RULE_LOG.isInfoEnabled()) { SimpleDateFormat df = new SimpleDateFormat("yyy-MM-dd HH:mm:ss:SSS"); String logFieldSep = "#@#"; String linesep = System.getProperty("line.separator"); String time = df.format(new Date()); StringBuilder sb = new StringBuilder().append(appName) .append(logFieldSep) .append(version) .append(logFieldSep) .append(time) .append(logFieldSep) .append(1) .append(linesep); LoggerInit.DYNAMIC_RULE_LOG.info(sb.toString()); } } private void logReceiveRule(String dataId, String data) { StringBuilder sb = new StringBuilder("recieve versions data!dataId:"); sb.append(dataId); sb.append(" data:"); sb.append(data); logger.info(sb.toString()); } /** * 获取appname的versions列表的dataId * * @param appName * @return */ public static String getVersionsDataId(String appName) { String versionsDataId = new MessageFormat(TDDL_RULE_LE_VERSIONS_FORMAT).format(new Object[] { appName }); return versionsDataId; } /** * 获取appname指定version的dataId * * @param appName * @param version * @return */ public static String getVersionedRuleDataId(String appName, String version) { return TDDL_RULE_LE_PREFIX + appName + "." + version; } /** * 获取appname无版本信息的dataId * * @param appName * @return */ public static String getNonversionedRuledataId(String appName) { return TDDL_RULE_LE_PREFIX + appName; } // ================== listener ======================= private class VersionsConfigListener implements ConfigDataListener { public synchronized void onDataRecieved(String dataId, String data) { if (TStringUtil.isNotEmpty(data)) { String[] versions = data.split(","); Map<String, String> checkMap = new HashMap<String, String>(); // 添加新增的规则订阅 int index = 0; Map<Integer, String> tempIndexMap = new HashMap<Integer, String>(); for (String version : versions) { if (ruleHandlers.get(version) == null) { String ruleDataId = getVersionedRuleDataId(appName, version); if (!dataSub(ruleDataId, version, new SingleRuleConfigListener())) { return; } } checkMap.put(version, version); tempIndexMap.put(index, version); index++; } versionIndex = tempIndexMap; // 删除没有在version中存在的订阅 List<String> needRemove = new ArrayList<String>(); for (Map.Entry<String, ConfigDataHandler> handler : ruleHandlers.entrySet()) { if (checkMap.get(handler.getKey()) == null) { needRemove.add(handler.getKey()); } } // 清理 for (String version : needRemove) { ConfigDataHandler handler = ruleHandlers.get(version); try { handler.destory(); } catch (TddlException e) { logger.error("destory failed!", e); } ruleHandlers.remove(version); vtrs.remove(version); ruleStrs.remove(version); oldCtxs.get(version).close(); oldCtxs.remove(version); } // 在versions data收到为null,或者为空,不调用,保护AppServer // 调用listener,但只返回位列第一个的VirtualTableRoot for (RuleChangeListener listener : listeners) { try { // may be wrong,so try catch it ,not to affect // other! listener.onRuleRecieve(getCurrentRuleStr()); } catch (Exception e) { logger.error("one listener error!", e); } } } // 记下日志,方便分析 logRecieveRuleVersions(data); } } private class SingleRuleConfigListener implements ConfigDataListener { public synchronized void onDataRecieved(String dataId, String data) { if (TStringUtil.isNotEmpty(data)) { logReceiveRule(dataId, data); String prefix = TDDL_RULE_LE_PREFIX + appName + "."; int i = dataId.indexOf(prefix); String version = NO_VERSION_NAME; // non-versioned rule if (i >= 0) { version = dataId.substring(i + prefix.length()); } if (initVersionRule(data, version)) { for (RuleChangeListener listener : listeners) { try { // may be wrong,so try catch it ,not to // affect other ! listener.onRuleRecieve(getCurrentRuleStr()); } catch (Exception e) { logger.error("one listener error!", e); } } } } } } // =================== setter / getter ====================== public void setOuterClassLoader(ClassLoader outerClassLoader) { this.outerClassLoader = outerClassLoader; } public void addRuleChangeListener(RuleChangeListener listener) { this.listeners.add(listener); } public void setAppRuleFile(String appRuleFile) { this.appRuleFile = appRuleFile; } public void setAppRuleString(String appRuleString) { this.appRuleString = appRuleString; } public void setAppName(String appName) { this.appName = appName; } public void setUnitName(String unitName) { this.unitName = unitName; } public void setCompatibleOldRule(boolean compatibleOldRule) { this.compatibleOldRule = compatibleOldRule; } }