同限流的格式、存储定义方式
具体值、区间值、比例值、复杂规则
灰度规则的数据结构支持快速判定是否在灰度对象中
// 代码目录结构
com.xzg.darklaunch
--DarkLaunch(框架的最顶层入口类)
--DarkFeature(每个feature的灰度规则)
--DarkRule(灰度规则)
--DarkRuleConfig(用来映射配置到内存中)
// Demo示例 public class DarkDemo { public static void main(String[] args) { DarkLaunch darkLaunch = new DarkLaunch(); DarkFeature darkFeature = darkLaunch.getDarkFeature("call_newapi_getUserById"); System.out.println(darkFeature.enabled()); System.out.println(darkFeature.dark(893)); } }
// 灰度规则配置(dark-rule.yaml)放置在classpath路径下 <!--对应DarkRuleConfig--> features: - key: call_newapi_getUserById <!--对应DarkFeatureConfig--> enabled: true rule: {893,342,1020-1120,%30} - key: call_newapi_registerUser <!--对应DarkFeatureConfig--> enabled: true rule: {1391198723, %10} - key: newalgo_loan <!--对应DarkFeatureConfig--> enabled: true rule: {0-1000}
public class DarkLaunch { private static final Logger log = LoggerFactory.getLogger(DarkLaunch.class); private static final int DEFAULT_RULE_UPDATE_TIME_INTERVAL = 60; // in seconds private DarkRule rule; private ScheduledExecutorService executor; public DarkLaunch(int ruleUpdateTimeInterval) { loadRule(); this.executor = Executors.newSingleThreadScheduledExecutor(); this.executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { loadRule(); } }, ruleUpdateTimeInterval, ruleUpdateTimeInterval, TimeUnit.SECONDS); } public DarkLaunch() { this(DEFAULT_RULE_UPDATE_TIME_INTERVAL); } private void loadRule() { // 将灰度规则配置文件dark-rule.yaml中的内容读取DarkRuleConfig中 InputStream in = null; DarkRuleConfig ruleConfig = null; try { in = this.getClass().getResourceAsStream("/dark-rule.yaml"); if (in != null) { Yaml yaml = new Yaml(); ruleConfig = yaml.loadAs(in, DarkRuleConfig.class); } } finally { if (in != null) { try { in.close(); } catch (IOException e) { log.error("close file error:{}", e); } } } if (ruleConfig == null) { throw new RuntimeException("Can not load dark rule."); } // 更新规则并非直接在this.rule上进行, // 而是通过创建一个新的DarkRule,然后赋值给this.rule, // 来避免更新规则和规则查询的并发冲突问题 DarkRule newRule = new DarkRule(ruleConfig); this.rule = newRule; } public DarkFeature getDarkFeature(String featureKey) { DarkFeature darkFeature = this.rule.getDarkFeature(featureKey); return darkFeature; } }
为了避免查询和更新冲突,先创建一个新的rule对象,创建完成过后在替换老对象。
public class DarkRuleConfig { private List<DarkFeatureConfig> features; public List<DarkFeatureConfig> getFeatures() { return this.features; } public void setFeatures(List<DarkFeatureConfig> features) { this.features = features; } public static class DarkFeatureConfig { private String key; private boolean enabled; private String rule; // 省略getter、setter方法 } }
public class DarkRule { private Map<String, DarkFeature> darkFeatures = new HashMap<>(); public DarkRule(DarkRuleConfig darkRuleConfig) { List<DarkRuleConfig.DarkFeatureConfig> darkFeatureConfigs = darkRuleConfig.getFeatures(); for (DarkRuleConfig.DarkFeatureConfig darkFeatureConfig : darkFeatureConfigs) { darkFeatures.put(darkFeatureConfig.getKey(), new DarkFeature(darkFeatureConfig)); } } public DarkFeature getDarkFeature(String featureKey) { return darkFeatures.get(featureKey); } }
public class DarkFeature { private String key; private boolean enabled; private int percentage; private RangeSet<Long> rangeSet = TreeRangeSet.create(); public DarkFeature(DarkRuleConfig.DarkFeatureConfig darkFeatureConfig) { this.key = darkFeatureConfig.getKey(); this.enabled = darkFeatureConfig.getEnabled(); String darkRule = darkFeatureConfig.getRule().trim(); parseDarkRule(darkRule); } @VisibleForTesting protected void parseDarkRule(String darkRule) { if (!darkRule.startsWith("{") || !darkRule.endsWith("}")) { throw new RuntimeException("Failed to parse dark rule: " + darkRule); } String[] rules = darkRule.substring(1, darkRule.length() - 1).split(","); this.rangeSet.clear(); this.percentage = 0; for (String rule : rules) { rule = rule.trim(); if (StringUtils.isEmpty(rule)) { continue; } if (rule.startsWith("%")) { int newPercentage = Integer.parseInt(rule.substring(1)); if (newPercentage > this.percentage) { this.percentage = newPercentage; } } else if (rule.contains("-")) { String[] parts = rule.split("-"); if (parts.length != 2) { throw new RuntimeException("Failed to parse dark rule: " + darkRule); } long start = Long.parseLong(parts[0]); long end = Long.parseLong(parts[1]); if (start > end) { throw new RuntimeException("Failed to parse dark rule: " + darkRule); } this.rangeSet.add(Range.closed(start, end)); } else { long val = Long.parseLong(rule); this.rangeSet.add(Range.closed(val, val)); } } } public boolean enabled() { return this.enabled; } public boolean dark(long darkTarget) { boolean selected = this.rangeSet.contains(darkTarget); if (selected) { return true; } long reminder = darkTarget % 100; if (reminder >= 0 && reminder < this.percentage) { return true; } return false; } public boolean dark(String darkTarget) { long target = Long.parseLong(darkTarget); return dark(target); } }
上面的代码可以用工厂+策略模式来优化多个if-else的逻辑,来更好的满足OCP。
RuleConfig和FeatureConfig都被抽离出来,并没有直接在Rule和Feature上面去做解析。
// 第一步的代码目录结构
com.xzg.darklaunch
--DarkLaunch(框架的最顶层入口类)
--DarkFeature(每个feature的灰度规则)
--DarkRule(灰度规则)
--DarkRuleConfig(用来映射配置到内存中)
// 第二步的代码目录结构
com.xzg.darklaunch
--DarkLaunch(框架的最顶层入口类,代码有改动)
--IDarkFeature(抽象接口)
--DarkFeature(实现IDarkFeature接口,基于配置文件的灰度规则,代码不变)
--DarkRule(灰度规则,代码有改动)
--DarkRuleConfig(用来映射配置到内存中,代码不变)
public interface IDarkFeature { boolean enabled(); boolean dark(long darkTarget); boolean dark(String darkTarget); }
为了避免配置文件的灰度规则热更新时候覆盖掉编程实现的灰度规则,对从配置文件中加载的灰度规则和编程实现的灰度规则分开存储。
public class DarkRule { // 从配置文件中加载的灰度规则 private Map<String, IDarkFeature> darkFeatures = new HashMap<>(); // 编程实现的灰度规则 private ConcurrentHashMap<String, IDarkFeature> programmedDarkFeatures = new ConcurrentHashMap<>(); public void addProgrammedDarkFeature(String featureKey, IDarkFeature darkFeature) { programmedDarkFeatures.put(featureKey, darkFeature); } public void setDarkFeatures(Map<String, IDarkFeature> newDarkFeatures) { this.darkFeatures = newDarkFeatures; } public IDarkFeature getDarkFeature(String featureKey) { IDarkFeature darkFeature = programmedDarkFeatures.get(featureKey); if (darkFeature != null) { return darkFeature; } return darkFeatures.get(featureKey); } }
public class DarkLaunch { private static final Logger log = LoggerFactory.getLogger(DarkLaunch.class); private static final int DEFAULT_RULE_UPDATE_TIME_INTERVAL = 60; // in seconds private DarkRule rule = new DarkRule(); private ScheduledExecutorService executor; public DarkLaunch(int ruleUpdateTimeInterval) { loadRule(); this.executor = Executors.newSingleThreadScheduledExecutor(); this.executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { loadRule(); } }, ruleUpdateTimeInterval, ruleUpdateTimeInterval, TimeUnit.SECONDS); } public DarkLaunch() { this(DEFAULT_RULE_UPDATE_TIME_INTERVAL); } private void loadRule() { InputStream in = null; DarkRuleConfig ruleConfig = null; try { in = this.getClass().getResourceAsStream("/dark-rule.yaml"); if (in != null) { Yaml yaml = new Yaml(); ruleConfig = yaml.loadAs(in, DarkRuleConfig.class); } } finally { if (in != null) { try { in.close(); } catch (IOException e) { log.error("close file error:{}", e); } } } if (ruleConfig == null) { throw new RuntimeException("Can not load dark rule."); } // 修改:单独更新从配置文件中得到的灰度规则,不覆盖编程实现的灰度规则 Map<String, IDarkFeature> darkFeatures = new HashMap<>(); List<DarkRuleConfig.DarkFeatureConfig> darkFeatureConfigs = ruleConfig.getFeatures(); for (DarkRuleConfig.DarkFeatureConfig darkFeatureConfig : darkFeatureConfigs) { darkFeatures.put(darkFeatureConfig.getKey(), new DarkFeature(darkFeatureConfig)); } this.rule.setDarkFeatures(darkFeatures); } // 新增:添加编程实现的灰度规则的接口 public void addProgrammedDarkFeature(String featureKey, IDarkFeature darkFeature) { this.rule.addProgrammedDarkFeature(featureKey, darkFeature); } public IDarkFeature getDarkFeature(String featureKey) { IDarkFeature darkFeature = this.rule.getDarkFeature(featureKey); return darkFeature; } }
灰度规则配置(dark-rule.yaml),放到classpath路径下
features: - key: call_newapi_getUserById enabled: true rule: {893,342,1020-1120,%30} - key: call_newapi_registerUser enabled: true rule: {1391198723, %10} - key: newalgo_loan enabled: true rule: {0-100}
编程实现的灰度规则
public class UserPromotionDarkRule implements IDarkFeature { @Override public boolean enabled() { return true; } @Override public boolean dark(long darkTarget) { // 灰度规则自己想怎么写就怎么写 return false; } @Override public boolean dark(String darkTarget) { // 灰度规则自己想怎么写就怎么写 return false; } }
Demo
public class Demo { public static void main(String[] args) { DarkLaunch darkLaunch = new DarkLaunch(); // 默认加载classpath下dark-rule.yaml文件中的灰度规则 darkLaunch.addProgrammedDarkFeature("user_promotion", new UserPromotionDarkRule()); // 添加编程实现的灰度规则 IDarkFeature darkFeature = darkLaunch.getDarkFeature("user_promotion"); System.out.println(darkFeature.enabled()); System.out.println(darkFeature.dark(893)); } }