AI智能
改变未来

Tars | 第8篇 TarsJava Subset最终代码的执行流程与原理分析

[TOC](TarsJava Subset最终代码的执行流程与原理分析)

前言

中期汇报会后,对Tars Subset功能更加熟悉,并根据TarsGo的实现方式,对Java JDK实现代码进行翻新改造。于是有了以下两篇分析文章:

第5篇 基于TarsGo Subset路由规则的Java JDK实现方式(上篇)https://www.geek-share.com/detail/2842342820.html

第6篇 基于TarsGo Subset路由规则的Java JDK实现方式(下篇)https://www.cnblogs.com/dlhjw/p/15245116.html

其中,《上篇》注重TarsGo分析,《下篇》注重TarsJava实现方式。不出意外的话,最终提交的考核成果就在下面的GitHub代码仓库中(以下简称“最终代码”),后续可能会有些许地方需要更改:

TarsJava 实现Subset路由规则JDK GitHub开源地址https://github.com/dlhjw/TarsJava/commit/cc2fe884ecbe8455a8e1f141e21341f4f3dd98a3

最终代码与中期代码在整体思想逻辑上都是一致的都是:先判断Subset路由规则,再根据规则路由到定义的节点。不同点在于:中期在处理整个过程时,用一个方法

filterEndpointsBySubset()

实现;而最终的实现方式则是以

subsetEndpointFilter()

方法作为整个Subset流量路由的入口,通过

subsetManager

管理器调用

getSubset()

方法获取到路由规则的String类型的

subset

字段,与节点自身的

subset

字段一一比较过滤节点;其中Subset路由规则的判断封装在

getSubset()

方法里;

总的来说就是最终代码是在处理subset规则逻辑中增加了很多细节,比如:通过新增的registry接口获取subsetConf配置项;将获取到是配置项存入缓存中;以及将“判断Subset路由规则”进行层层封装,最终返回一个简单的String类型的

subset

字段与节点自身的

subset

字段比较等;

因此,在执行流程上相比较中期有些许区别,其中比较复杂的地方涉及对各种封装方法的调用与传参。本篇将结合最终代码的实现逻辑,以debug的方式,重点介绍其执行流程,即:

  • 首先找到过滤节点的方法入口;
  • 接着通过管理者尝试获取String类型的规则
    subset

    字段;

  • 通过
    getSubset()

    方法匹配路由规则;

  • 调用具体路由规则的
    findSubet()

    方法获取最终的规则

    subset

    字段;

  • 将规则
    subset

    字段与节点的

    subset

    字段对比,实现筛选功能;

通过下面分析可以得出一个Subset的业务执行流程结构图,如下:

  • subset.subsetEndpointFilter():整个业务流程的方法入口;subsetManager.getSubset():通过XxxConf配置获取String类型的subset字段;getSubsetConfig():从缓存或registry接口获取SubsetConf;
  • subsetConf.getRatioConf():获取RatioConf配置;ratioConf.findSubet():通过RatioConf配置获取String类型的subset字段;
  • subsetConf.getKeyConf():获取KeyConf配置;
      keyConf.findSubet():通过KeyConf配置获取String类型的subset字段;
  • for循环匹配subset字段;
  • 当然,涉及到通过新增的registry接口获取subsetConf配置项等逻辑,这些细节就放在下面正文里讲吧;

    最终代码的Subset测试方案设计请参考下面这篇文章:

    第7篇 TarsJava Subset最终代码的测试方案设计https://www.cnblogs.com/dlhjw/p/15245121.html

    《测试方案设计》与《执行流程分析》两篇文章相辅相成,相互观阅能更快更好地理解整个Subset的业务流程与输出示例;

    1. SubsetConf配置项的结构

    在中期,笔者使用一个map来模拟subset的流量规则;而在最终代码里,是用多个对象来模拟Subset的配置,这些对象是理解整个Subset流量过滤规则的基础的,因此很有必要在这里做个介绍;

    1.1 SubsetConf

    public class SubsetConf {private boolean enanle;private String ruleType;private RatioConfig ratioConf;private KeyConfig keyConf;private Instant lastUpdate;……}

    可以看出SubsetConf配置项里有以下属性:

    • enanle:表示是否开启Subset流量管理功能;true:开启;false:关闭;
  • ruleType:表示流量管理的类型;
      目前有

      ratio

      按比例和

      key

      按参数两种模式;

  • RatioConfig:表示按比例路由配置项;
      里面定义了路由比例与路径等信息,详情请参考《1.2 RatioConfig》
  • KeyConfig:表示按参数路由配置项;
      里面定义了规则key与路由路径等信息,详情请参考《1.3 KeyConfig》
  • lastUpdate:表示该配置项上次更新时间,将在缓存那里起作用;
  • 1.2 RatioConfig

    public class RatioConfig {private Map<String, Integer> rules;……}

    RatioConfig里只有一个map类型的

    rules

    路由规则,其中key为一个String类型的subset字段,用来跟节点的subset字段匹配,value为路由权重,如:{ {"v1" , 20} , {"v2" , 60} , {"v3" , 20} }表示路由到subset字段为v1的节点的概率为0.2;路由到subset字段为v2的节点的概率为0.6;路由到subset字段为v3的节点的概率为0.2;

    1.3 KeyConfig

    public class KeyConfig {private String defaultRoute;private List<KeyRoute> rules;……}

    KeyConfig里有两个属性,一个是

    defaultRoute

    默认路由路径;另一个是list类型的

    rules

    ,里面是

    KeyRoute

    ,其定义了按参数匹配的类型、规则key与路径,详情请见《1.4 KeyRoute》

    1.4 KeyRoute

    public class KeyRoute {private String action = null;private String value = null;private String route = null;public static final String TARS_ROUTE_KEY = "TARS_ROUTE_KEY";……}

    KeyRoute里面有四个String类型的属性,如下:

    • action:用来定义参数匹配的类型;目前可设置的类型有:equals精确匹配、match正则匹配、default默认匹配;
  • value:这就是大名鼎鼎的规则key了。当action=equals时,还需满足规则key与请求key匹配,才能进行精确匹配;当action=match时,还需满足规则key与请求key正则匹配,才能进行正则匹配;action=default对规则key没要求;
  • route:用来规定路由路径,其值为一个String类型的subset字段,匹配到节点的subset字段;
  • TARS_ROUTE_KEY:一个常量字段,为Tars请求体里的status(map类型)的key;
  • 1.5 SubsetConf的结构示意图

    上述提到的配置类联系结构图如下:

    从SubsetConf配置类可以看出,按比例路由和按参数在思路上有些许不同,因此下面dubug分析将分为两种情况;

    2. 过滤节点的方法入口

    新增的Sunset流量路由规则应该在查找服务端节点那里,其目的是对获取到的服务端节点根据subset规则进行过滤,因此整个Subset业务逻辑的入口函数在

    getServerNodes()

    方法的

    subsetEndpointFilter()

    里,如下图所示:我们给该方法打上断点,进行debug调试;

    3. subsetEndpointFilter()方法解析

    3.1 方法功能

    方法名 subsetEndpointFilter()
    方法所在类 Subset.java
    方法逻辑 根据Subset路由规则过滤节点
    传入参数 服务名、透传染色key、存活节点列表
    传出参数 过滤后的存活节点列表

    3.2 方法源码

    public Holder<List<EndpointF>> subsetEndpointFilter(String servantName, String routeKey, Holder<List<EndpointF>> eps){if( subsetConf==null || !subsetConf.isEnanle() ){return eps;}if(eps.value == null || eps.value.isEmpty()){return eps;}//调用subsetManager,根据比例/匹配等规则获取到路由规则的subsetString subset = subsetManager.getSubset(servantName, routeKey);if( "".equals(subset) || subset == null){return eps;}//和每一个eps的subset比较,淘汰不符合要求的Holder<List<EndpointF>> epsFilter = new Holder<>(new ArrayList<EndpointF>());for (EndpointF ep : eps.value) {if( subset.equals(ep.getSubset())){epsFilter.getValue().add(ep);}}return epsFilter;}

    3.3 方法解析

    进入

    subsetEndpointFilter()

    方法,首先会对subsetConf配置项进行非空校验,这里的配置项是从前端传过来的,正常情况下不为空;接着对传入的参数存活节点列表eps做非空校验。两步校验通过后,进行核心方法

    getSubset()

    的调用,返回String类型的规则的subset字段,该字段可以表示路由路径。然后将规则的subset字段与节点的subset字段一一比较,选出符合要求的节点;

    测试结果详情请见《TarsJava Subset最终代码的测试方案设计》一文;

    返回的String类型的规则的subset字段在下图中就是RotioConfig的rules中的value或者KeyConfig的rules中的route,表示最终路由路径。以下所有方法都是围绕如何找到这个规则的subset字段做文章;

    我们进入

    getSubset()

    方法查看Tars是怎么获取到规则的subset字段的;

    4. getSubset()方法解析

    4.1 方法功能

    方法名 getSubset()
    方法所在类 SubsetManager.java
    方法逻辑 根据路由规则先获取到比例 / 染色路由的配置,再通过配置获取String类型的subset字段
    传入参数 服务名、透传染色key
    传出参数 String类型的规则的subset字段

    4.2 方法源码

    public String getSubset(String servantName, String routeKey){//check subset config existsSubsetConf subsetConf = getSubsetConfig(servantName);if( subsetConf == null ){return null;}// route key to subsetif("ratio".equals(subsetConf.getRuleType())){RatioConfig ratioConf = subsetConf.getRatioConf();if(ratioConf != null){return ratioConf.findSubet(routeKey);}}KeyConfig keyConf = subsetConf.getKeyConf();if ( keyConf != null ){return keyConf.findSubet(routeKey);}return null;}

    4.3 方法解析

    该方法首先调用

    getSubsetConfig()

    方法获取到一个SubsetConf配置项,该配置项可以是从缓存中拿,也可以是通过registry接口获取(详情请见《5. getSubsetConfig()方法解析》);

    确保SubsetConf配置项存在后,识别SubsetConf配置项的ruleType路由类型属性(按比例、按参数、默认),通过属性获取到对应的配置项(ratioConf、keyConf、keyConf),最后调用

    findSubet()

    方法即可获取到String类型的规则的subset字段,并返回(详情请见《6. 按比例路由的findSubet()方法解析》与《7. 按参数路由的findSubet()方法解析》);

    我们先进入

    getSubsetConfig()

    方法探其源码;

    5. getSubsetConfig()方法解析

    5.1 方法功能

    方法名
    方法所在类 SubsetManager.java
    方法逻辑 获取SubsetConf路由规则配置项,并存到subsetConf配置项
    传入参数 服务名
    传出参数 SubsetConf配置项

    5.2 方法源码

    public SubsetConf getSubsetConfig(String servantName){SubsetConf subsetConf = new SubsetConf();if( cache.containsKey(servantName) ){subsetConf = cache.get(servantName);//小于10秒从缓存中取if( Duration.between(subsetConf.getLastUpdate() , Instant.now()).toMillis() < 1000 ){return subsetConf;}}// get config from registryHolder<SubsetConf> subsetConfHolder = new Holder<SubsetConf>(subsetConf);int ret = queryProxy.findSubsetConfigById(servantName, subsetConfHolder);SubsetConf newSubsetConf = subsetConfHolder.getValue();if( ret == TarsHelper.SERVERSUCCESS ){return newSubsetConf;}//从registry中获取失败时,更新subsetConf添加进缓存subsetConf.setRuleType( newSubsetConf.getRuleType() );subsetConf.setLastUpdate( Instant.now() );cache.put(servantName, subsetConf);//解析subsetConfif( !newSubsetConf.isEnanle() ){subsetConf.setEnanle(false);return subsetConf;}if( "ratio".equals(newSubsetConf.getRuleType())){subsetConf.setRatioConf( newSubsetConf.getRatioConf() );} else {//按参数匹配KeyConfig newKeyConf = newSubsetConf.getKeyConf();List<KeyRoute> keyRoutes = newKeyConf.getRules();for ( KeyRoute kr: keyRoutes) {KeyConfig keyConf = new KeyConfig();//默认if("default".equals(kr.getAction())){keyConf.setDefaultRoute(newKeyConf.getDefaultRoute());subsetConf.setKeyConf(keyConf);}//精确匹配if("match".equals(kr.getAction())){List<KeyRoute> rule = new ArrayList<>();rule.add(new KeyRoute("match", kr.getValue() , kr.getRoute()));keyConf.setRules( rule );}//正则匹配if("equal".equals(kr.getAction())){List<KeyRoute> rule = new ArrayList<>();rule.add(new KeyRoute("equal", kr.getValue() , kr.getRoute()));keyConf.setRules( rule );}}subsetConf.setKeyConf(newKeyConf);}return subsetConf;}

    5.3 方法解析

    getSubsetConfig()

    方法的获取主要分为两个逻辑:从缓存中获取与从registry接口获取,判断依据是:如果缓存中的subsetConf配置项的lastUpdate上次更新属性落后当前时间小于10秒,则直接从缓存中获取;否则重新调用registry接口获取subsetConf配置项;

    测试结果详情请见《TarsJava Subset最终代码的测试方案设计》一文;

    这是与另一个新增功能registry接口接触的方法,由于registry功能还未实现,这部分功能做不了测试,但其大概逻辑是通过registry接口查阅数据库获取到subsetConf配置项;

    getSubsetConfig()

    方法执行完后,回到

    4. getSubset()

    方法里,我们再进入

    findSubet()

    方法窥其究竟;

    6. 按比例路由的findSubet()方法解析

    需要注意,按比例、按参数路由的findSubet()方法实现方式有些许不同,故在这里分开来讲;

    6.1 方法功能

    方法名
    方法所在类 RatioConfig.java
    方法逻辑 根据权重比例获取rules中最后的subset字段
    传入参数 透传的染色key
    传出参数 String类型的规则的subset字段

    6.2 方法源码

    public String findSubet(String routeKey){//routeKey为空时随机if( "".equals(routeKey) || routeKey == null){//赋值routeKey为获取的随机值Random random = new Random();int r = random.nextInt( rules.size() );routeKey = String.valueOf(r);int i = 0;for (String key : rules.keySet()) {if(i == r){return key;}i++;}}//routeKey不为空时实现按比例算法int totalWeight = 0;int supWeight = 0;String subset = null;//获得总权重for (Integer value : rules.values()) {totalWeight+=value;}//获取随机数Random random = new Random();int r = random.nextInt(totalWeight);//根据随机数找到subsetfor (Map.Entry<String, Integer> entry : rules.entrySet()){supWeight+=entry.getValue();if( r < supWeight){subset = entry.getKey();return subset;}}return null;}

    6.3 方法解析

    首先判断透传的染色key是否为空,如果为空,则会进行等比例随机路由到任意一个value;如果不为空,则会计算权重,通过随机数匹配权重的方式按比例路由到对应的value,该value就是需要返回给入口函数的String类型的规则的subset字段;

    该方法实现下图的蓝圈1号流程:

    而在按参数路由里,也有一个findSubet()方法,他们的传入传出参数相同,实现功能类似,但在逻辑上有些不同,下面来看一下吧;

    7. 按参数路由的findSubet()方法解析

    7.1 方法功能

    方法名
    方法所在类 KeyConfig.java
    方法逻辑 根据参数匹配规则获取rules中最后的subset字段
    传入参数 透传的染色key
    传出参数 String类型的规则的subset字段

    7.2 方法源码

    public String findSubet(String routeKey){//非空校验if( routeKey == null || "".equals(routeKey) || rules == null){return null;}for ( KeyRoute rule: rules) {//根据根据分布式上下文信息获取 “请求的染色的key”String routeKeyReq;if( distributedContext != null){routeKeyReq = KeyRoute.getRouteKey(distributedContext);} else {logger.info("无分布式上下文信息distributedContext");return null;}//精确匹配if( "match".equals(rule.getAction())  ){if( routeKeyReq.equals(rule.getValue()) ){return rule.getRoute();} else {logger.info("染色key匹配不上,请求的染色key为:" + routeKeyReq + "; 规则的染色key为:" + rule.getValue());}}//正则匹配if( "equal".equals(rule.getAction()) ){if( StringUtils.matches(routeKeyReq, rule.getValue()) ){return rule.getRoute();} else {logger.info("正则匹配失败,请求的染色key为:" + routeKeyReq + "; 规则的染色key为:" + rule.getValue());}}//默认匹配if( "default".equals(rule.getAction()) ){//默认路由无需考虑染色keyreturn rule.getRoute();}}return null;}

    7.3 方法解析

    按参数匹配路由方式要求跟Taes请求体的染色key做匹配比较,因此这部分逻辑需要加上;

    首先进行非空校验,校验通过后遍历KeyRoute,只需要找到符合条件的rules即可处理返回;在for循环里,首先根据分布式上下文信息获取 “请求的染色key”,根据KeyRoute的action分情况讨论:

    • action=match:精确匹配,需要判断请求的染色key规则的染色key是否相等,相等才能进行精确路由;不相等则不进行路由,并输出一句错误日志;
    • action=equal:正则匹配,其中正则式为请求的染色key,需要与规则的染色key进行匹配,同理匹配成功才能进行精确路由;否则则不进行路由,并输出一句错误日志;
    • action=default:默认路由,不需要使用染色key,路由到用户设定的默认路由路径;
    • 测试结果详情请见《TarsJava Subset最终代码的测试方案设计》一文;

    该方法实现下图的蓝圈2号流程:

    通过上述步骤,最终获取到的规则的subset字段将返回到入口函数进行后续步骤:即将规则的subset字段与节点的subset字段一一比较,选出符合要求的节点;

    8 总结:Subset业务执行流程结构图

    通过上述分析,可以知道整个Subset的业务执行流程结构图如下:

    • subset.subsetEndpointFilter():整个业务流程的方法入口;subsetManager.getSubset():通过XxxConf配置获取String类型的subset字段;getSubsetConfig():从缓存或registry接口获取SubsetConf;
    • subsetConf.getRatioConf():获取RatioConf配置;ratioConf.findSubet():通过RatioConf配置获取String类型的subset字段;
  • subsetConf.getKeyConf():获取KeyConf配置;
      keyConf.findSubet():通过KeyConf配置获取String类型的subset字段;
  • for循环匹配subset字段;
  • 最后

    新人制作,如有错误,欢迎指出,感激不尽!欢迎关注公众号,会分享一些更日常的东西!如需转载,请标注出处!

    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » Tars | 第8篇 TarsJava Subset最终代码的执行流程与原理分析