古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。这篇文章主要讲述Tars | 第6篇 基于TarsGo Subset路由规则的Java JDK实现方式(下)#yyds干货盘点#相关的知识,希望能为你提供帮助。
@[TOC](基于TarsGo Subset路由规则的java JDK实现方式(下))
前言利开园导师用Go语言实现了Subset路由规则,并在中期汇报分享会里介绍出来;这篇文章将基于利导师的实现方式,对Subset路由规则的细节做些理解与补充。
此篇文章为下半部分,将对上半部分提到的TarsGo对Subset路由规则的实现做一一分析,重点放在“如果开发语言是Java,对应功能将如何实现”问题上。
【Tars | 第6篇 基于TarsGo Subset路由规则的Java JDK实现方式(下)#yyds干货盘点#】上下部分文章在目录上一一对应,上半注重TarsGo分析,下半部分注重TarsJava实现方式。如上篇文章第一点修改.tars协议文件记录利导师在TarsGo的代码修改,下片文章第一点也是修改.tars协议文件,侧重点在如何用Java语言实现。上下文章相辅相成,建议对照学习。
一些资源链接如下:
上半部分文章链接
https://blog.csdn.net/dlhjw1412/article/details/119809470
TarsJava 实现Subset路由规则JDK链接地址
https://github.com/TarsCloud/TarsJava/commit/cc2fe884ecbe8455a8e1f141e21341f4f3dd98a3
TarsGo 实现Subset路由规则JDK链接地址
https://github.com/defool/TarsGo/commit/136878e9551d68c4b54c402df564729f51f3dd9c#
1. 修改.tars协议文件1.1 Java源码位置及逻辑分析该部分的含义是:增加Subset配置与增加获取Subset信息;
通过上半文章的分析,增加的配置是在EndpointF.tars
与QueryF.tars
协议文件里面添加,而tars协议文件在所有语言中是统一的,一样的;在Java中,EndpointF
协议文件在src/main/resources/EndpointF.tars;QueryF
协议文件在src/main/resources/QueryF.tars;
因此,我们可以得到以下信息:
- 定位对应源码位置如下:
Go语言 | Java |
---|---|
tars/protocol/res/EndpointF.tars | TarsJava-1.7.x\\core\\src\\main\\resources\\EndpointF.tars |
tars/protocol/res/QueryF.tars | TarsJava-1.7.x\\core\\src\\main\\resources\\QueryF.tars |
- 直接添加subset配置即可;
module tars/**
* Port information
*/
struct EndpointF0requirestring host;
1requireintport;
2requireinttimeout;
3requireintistcp;
4requireintgrid;
5optional intgroupworkid;
6optional intgrouprealid;
7optional string setId;
8optional intqos;
9optional intbakFlag;
11 optional intweight;
12 optional intweightType;
13 optional string subset;
;
key[EndpointF, host, port, timeout, istcp, grid, qos, weight, weightType];
;
文章图片
1.3 通过协议文件自动生成代码在Java语言里,具体操作如下:
1. 在项目的pom.xml里配置对应插件
<
build>
<
pluginManagement>
<
!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<
plugins>
<
!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<
plugin>
<
groupId>
com.tencent.tars<
/groupId>
<
artifactId>
tars-maven-plugin<
/artifactId>
<
version>
1.7.2<
/version>
<
configuration>
<
tars2JavaConfig>
<
!-- tars文件位置 -->
<
tarsFiles>
<
tarsFile>
$basedir/src/main/resources/EndpointF.tars<
/tarsFile>
<
/tarsFiles>
<
!-- 源文件编码 -->
<
tarsFileCharset>
UTF-8<
/tarsFileCharset>
<
!-- 生成服务端代码 -->
<
servant>
false<
/servant>
<
!-- 生成源代码编码 -->
<
charset>
UTF-8<
/charset>
<
!-- 生成的源代码目录 -->
<
srcPath>
$basedir/src/main/java<
/srcPath>
<
!-- 生成源代码包前缀 -->
<
packagePrefixName>
com.qq.tars.common.support.<
/packagePrefixName>
<
/tars2JavaConfig>
<
/configuration>
<
/plugin>
<
/plugins>
<
/pluginManagement>
<
/build>
我们仅需要修改的地方在 tars文件位置 与 生成源代码包前缀 ;
2. 在项目根路径下执行mvn tars:tars2java命令
文章图片
接着输入
mvn tars:tars2java
命令后出现下面日志则说明生成成功;文章图片
3. 检查生成代码
我们回到项目代码,经检查,
EndpointF
类发生了修改,新增SubsetConf
类。(因为笔者在第一步生成源代码包前缀没有配置好,所有将生成后的代码直接复制黏贴到源代码路径里了,影响不大。)文章图片
4. 用同样的方法可以自动生成QueryF代码
1.4 变更代码的路径通过上述操作,以下路径的代码发生改变,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\main\\resources\\EndpointF.tars
- TarsJava-1.7.x\\core\\src\\main\\resources\\QueryF.tars
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\support\\query\\prx\\EndpointF.java
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\support\\query\\prx\\QueryFPrx.java
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\support\\query\\prx\\QueryFPrxCallback.java
由于Subset路由业务与客户端相关,在Tars中的地位是:Tars支持(support)的功能之一,因此,笔者打算在参照原来的项目结构,在
TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client
路径下新建包subset
,包内实现以下功能:新增类型 | 新增内容 |
---|---|
结构体 | 新增Subset配置项的结构体 subsetConf |
结构体 | 新增路由规则配置项的结构体ratioConfig |
结构体 | 新增染色路径的结构体keyRoute |
结构体 | 新增染色配置项的结构体keyConfig |
结构体 | 新增subset管理者的结构体subsetManager |
方法 | 新增获取subset配置项的方法getSubsetConfig |
方法 | 新增获取比例 / 染色路由配置项的方法getSubset |
方法 | 新增根据subset规则过滤节点的方法subsetEndpointFilter |
方法 | 新增根据一致hash的subset规则过滤节点的方法subsetHashEpFilter |
方法 | 新增按比例路由路由路径的方法findSubet |
方法 | 新增按默认路由路径findSubet |
- 定位对应源码位置如下:
Go语言 | Java |
---|---|
tars/subset.go | TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\ |
- 在
SubsetConf
类里定义一些属性,并生成对应getter与setter方法; - 在
RatioConfig
类里实现findSubet()
方法;- *在
KeyRoute
类里实现getRouteKey()
、setRouteKey()
和setRouteKeyToRequest()
方法; - 这里提到的方法请见《3. 添加常量与获取染色key的方法》与《5. 实现透传染色Key功能》分析;
- *在
- 在
KeyConfig
类里实现findSubet()
方法; - 在
SubsetManager
类里实现getSubsetConfig()
和getSubset()
方法; - 在
Subset
类里实现subsetEndpointFilter()
和subsetHashEpFilter()
方法
SubsetConf:
public class SubsetConf private boolean enanle;
private String ruleType;
private RatioConfig ratioConf;
private KeyConfig keyConf;
private Instant lastUpdate;
public SubsetConf()
lastUpdate =Instant.now();
public SubsetConf(boolean enanle, String ruleType, RatioConfig ratioConf, KeyConfig keyConf)
this.enanle = enanle;
this.ruleType = ruleType;
this.ratioConf = ratioConf;
this.keyConf = keyConf;
lastUpdate =Instant.now();
public boolean isEnanle()
return enanle;
public void setEnanle(boolean enanle)
this.enanle = enanle;
public String getRuleType()
return ruleType;
public void setRuleType(String ruleType)
this.ruleType = ruleType;
public RatioConfig getRatioConf()
return ratioConf;
public void setRatioConf(RatioConfig ratioConf)
this.ratioConf = ratioConf;
public KeyConfig getKeyConf()
return keyConf;
public void setKeyConf(KeyConfig keyConf)
this.keyConf = keyConf;
public Instant getLastUpdate()
return lastUpdate;
public void setLastUpdate(Instant lastUpdate)
this.lastUpdate = lastUpdate;
RatioConfig:
public class RatioConfig private Map<
String, Integer>
rules;
//进行路由规则的具体实现,返回subset字段
public String findSubet(String routeKey)
//routeKey为空时随机
if( "".equals(routeKey) )
//赋值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);
//根据随机数找到subset
for (Map.Entry<
String, Integer>
entry : rules.entrySet())
supWeight+=entry.getValue();
if( r <
supWeight)
subset = entry.getKey();
return subset;
return null;
public Map<
String, Integer>
getRules()
return rules;
public void setRules(Map<
String, Integer>
rules)
this.rules = rules;
KeyRoute:
- 这里提到的方法请见《3. 添加常量与获取染色key的方法》分析;
public class KeyRoute private String action = null;
private String value = https://www.songbingjia.com/android/null;
private String route = null;
public static final String TARS_ROUTE_KEY ="TARS_ROUTE_KEY";
private static final Logger logger = LoggerFactory.getClientLogger();
//根据分布式上下文信息获取KeyRoute
public static String getRouteKey(DistributedContext distributedContext)
if( distributedContext == null )
logger.info("无分布式上下文信息distributedContext");
String routeValuehttps://www.songbingjia.com/android/= "";
if(distributedContext != null)
TarsServantRequest tarsServantRequest = distributedContext.get(DyeingSwitch.REQ);
if( tarsServantRequest != null)
routeValue = https://www.songbingjia.com/android/tarsServantRequest.getStatus().get(TARS_ROUTE_KEY);
return routeValue;
//根据分布式上下文信息设置KeyRoute
public static void setRouteKey(DistributedContext distributedContext, String routeKey)if(distributedContext != null &
&
routeKey != null )
TarsServantRequest tarsServantRequest = distributedContext.get(DyeingSwitch.REQ);
tarsServantRequest.getStatus().put(TARS_ROUTE_KEY, routeKey);
public static void setRouteKeyToRequest(DistributedContext distributedContext, TarsServantRequest request)
if( distributedContext == null )
logger.info("无分布式上下文信息distributedContext");
String routeValue = https://www.songbingjia.com/android/KeyRoute.getRouteKey(distributedContext);
if( routeValue != null &
&
!"".equals(routeValue))
if(request.getStatus() != null)
request.getStatus().put(KeyRoute.TARS_ROUTE_KEY ,routeValue);
else
HashMap<
String, String>
status = new HashMap<
>
();
status.put(KeyRoute.TARS_ROUTE_KEY ,routeValue);
request.setStatus(status);
//将分布式上下文信息的routeValue 设置到KeyRoute.value
public void setValue(DistributedContext distributedContext)
String routeKey = getRouteKey(distributedContext);
if( !"".equals(routeKey) &
&
routeKey != null)
this.value = https://www.songbingjia.com/android/routeKey;
public KeyRoute() public KeyRoute(String action, String value, String route)
this.action = action;
this.value = value;
this.route = route;
public String getValue()
return value;
public String getAction()
return action;
public void setAction(String action)
this.action = action;
public String getRoute()
return route;
public void setRoute(String route)
this.route = route;
KeyConfig:
- 因为这里涉及正则匹配,所有在StringUtils工具类里有正则算法的实现,详情见《8. 正则算法的实现》;
public class KeyConfig private String defaultRoute;
private List<
KeyRoute>
rules;
private DistributedContext distributedContext = DistributedContextManager.getDistributedContext();
private static final Logger logger = LoggerFactory.getClientLogger();
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()) )
//默认路由无需考虑染色key
return rule.getRoute();
return null;
public KeyConfig() public KeyConfig(String defaultRoute, List<
KeyRoute>
rules)
this.defaultRoute = defaultRoute;
this.rules = rules;
public String getDefaultRoute()
return defaultRoute;
public void setDefaultRoute(String defaultRoute)
this.defaultRoute = defaultRoute;
public List<
KeyRoute>
getRules()
return rules;
public void setRules(List<
KeyRoute>
rules)
this.rules = rules;
SubsetManager:
public class SubsetManager private Map<
String, SubsetConf>
cache = new HashMap<
>
();
private QueryFPrx queryProxy;
//获取Subset路由规则,并存到subsetConf配置项
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 registry
Holder<
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);
//解析subsetConf
if( !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;
// 根据路由规则先获取到比例 / 染色路由的配置,再通过配置获取String的subset字段
public String getSubset(String servantName, String routeKey)
//check subset config exists
SubsetConf subsetConf = getSubsetConfig(servantName);
if( subsetConf == null )
return null;
// route key to subset
if("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;
public SubsetManager() public SubsetManager(Map<
String, SubsetConf>
cache)
if(cache == null)
this.cache = new HashMap<
>
();
public Map<
String, SubsetConf>
getCache()
return cache;
public void setCache(Map<
String, SubsetConf>
cache)
this.cache = cache;
Subset:
public class Subset private String hashString;
private SubsetConf subsetConf;
private KeyConfig keyConfig;
private KeyRoute keyRoute;
private RatioConfig ratioConfig;
private SubsetManager subsetManager;
//获取到规则后的subset,与节点的subset比较,过滤不匹配节点
public Holder<
List<
EndpointF>
>
subsetEndpointFilter(String servantName, String routeKey, Holder<
List<
EndpointF>
>
eps)if( subsetConf==null || !subsetConf.isEnanle() )
return eps;
if(eps.value =https://www.songbingjia.com/android/= null || eps.value.isEmpty())
return eps;
//调用subsetManager,根据比例/匹配等规则获取到路由规则的subset
String 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;
public Subset() public Subset(String hashString, SubsetConf subsetConf, KeyConfig keyConfig, KeyRoute keyRoute, RatioConfig ratioConfig)
this.hashString = hashString;
this.subsetConf = subsetConf;
this.keyConfig = keyConfig;
this.keyRoute = keyRoute;
this.ratioConfig = ratioConfig;
public String getHashString()
return hashString;
public void setHashString(String hashString)
this.hashString = hashString;
public SubsetConf getSubsetConf()
return subsetConf;
public void setSubsetConf(SubsetConf subsetConf)
this.subsetConf = subsetConf;
public KeyConfig getKeyConfig()
return keyConfig;
public void setKeyConfig(KeyConfig keyConfig)
this.keyConfig = keyConfig;
public KeyRoute getKeyRoute()
return keyRoute;
public void setKeyRoute(KeyRoute keyRoute)
this.keyRoute = keyRoute;
public RatioConfig getRatioConfig()
return ratioConfig;
public void setRatioConfig(RatioConfig ratioConfig)
this.ratioConfig = ratioConfig;
public SubsetManager getSubsetManager()
return subsetManager;
public void setSubsetManager(SubsetManager subsetManager)
this.subsetManager = subsetManager;
2.3 变更代码的路径通过上述操作,新增了以下代码,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\SubsetConf.java
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\KeyConfig.java
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\KeyRoute.java
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\RatioConfig.java
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\Subset.java
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\SubsetManager.java
在TarsJava中,染色相关的逻辑在
DyeingKeyCache
和DyeingSwitch
类里;但我们新增的TARS_ROUTE_KEY
染色key与原染色逻辑相关性不大,这里的TARS_ROUTE_KEY
是随着Tars的请求体TarsServantRequest
里的中获取status参数(map类型)传递而来的;- Tars的请求体路径:TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\rpc\\protocol\\tars\\TarsServantRequest.java
DistributedContext
获取到TarsServantRequest
请求体,再从请求体里的status
map数据设置 / 获取染色key相关;因此,我们可以得到以下信息:
- 定位对应源码位置如下:
Go语言 | Java |
---|---|
tars/subset.go | TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\KeyRoute.java |
3.3 变更代码的路径通过上述操作,改变了以下代码,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\KeyRoute.java
在Go语言中,我们点进去tars/endpointmanager.go查看源码发现该代码的作用是:创建一个结点管理器,通过管理器可以实现查看节点状态
checkEpStatus()
、更新节点信息updateEndpoints()
等功能。修改的方法为
SelectAdapterProxy()
选择适配器代理,原逻辑为获取服务端节点列表,新增逻辑为subsetEndpointFilter()
为根据subset规则过滤节点;而在Java语言中,类似功能在
ObjectProxyFactory
类里,该类的功能主要是:创建代理对象,通过代理对象实现更新节点updateServantEndpoints()
、创建服务代理配置项createServantProxyConfig()
等功能。其中在
updateServantEndpoints()
方法里涉及到更新服务节点列表,但在Java中使用了一个QueryHelper
查询工具,里面有个getServerNodes()
方法获取服务端节点列表,我们要修改的地方就在这里。因此,我们可以得到以下信息:
- 定位对应源码位置如下:
Go语言 | Java语言 |
---|---|
tars/endpointmanager.go | TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\support\\query\\QueryHelper.java |
- 增加的方法逻辑
项目 | 说明 |
---|---|
方法名 | subsetEndpointFilter |
实现逻辑 | 根据subset规则过滤节点 |
传入参数 | 服务名String、染色状态String、存活的节点Holder |
返回参数 | 过滤后的节点Holder |
新添加的获取染色key的方法与原来染色逻辑类似,可以参照相应实现逻辑;
在TarsGo里,通过
msg.Req.状态[current.STATUS_ROUTE_KEY]
获取routeKey
字段;通过msg.Req.SServantName
获取服务名;而在TarsJava里,通过
ServantProxyConfig.getSimpleObjectName()
获取服务名,获取routeKey
字段则比较复杂;我们需要的最终染色字段在Tars请求体TarsServantRequest
里的status参数(map类型);获取的逻辑是:通过分布式上下文信息
DistributedContext
获取到TarsServantRequest
请求体,再从请求体里的status
map获取染色key;4.2 Java语言实现方式
public String getServerNodes(ServantProxyConfig config)
QueryFPrx queryProxy = getPrx();
//【新增】通过KeyRoute类与分布式上下文信息获取routeKey
String routeKey = getRouteKeyByContext();
String name = config.getSimpleObjectName();
//存活的节点
Holder<
List<
EndpointF>
>
activeEp = new Holder<
List<
EndpointF>
>
(new ArrayList<
EndpointF>
());
//挂掉的节点
Holder<
List<
EndpointF>
>
inactiveEp = new Holder<
List<
EndpointF>
>
(new ArrayList<
EndpointF>
());
int ret = TarsHelper.SERVERSUCCESS;
//判断是否为启用集
if (config.isEnableSet())
ret = queryProxy.findObjectByIdInSameSet(name, config.getSetDivision(), activeEp, inactiveEp);
else
ret = queryProxy.findObjectByIdInSameGroup(name, activeEp, inactiveEp);
if (ret != TarsHelper.SERVERSUCCESS)
return null;
Collections.sort(activeEp.getValue());
//【新增】根据Subset规则过滤节点
Holder<
List<
EndpointF>
>
activeEpFilter = subset.subsetEndpointFilter(name, routeKey, activeEp);
//将获取到的节点列表格式化为一个字符串格式
StringBuilder value = https://www.songbingjia.com/android/new StringBuilder();
if (activeEpFilter.value != null &
&
!activeEpFilter.value.isEmpty())
for (EndpointF endpointF : activeEpFilter.value)
if (value.length() >
0)
value.append(":");
value.append(ParseTools.toFormatString(endpointF, true));
//个格式化后的字符串加上Tars的服务名
if (value.length() <
1)
return null;
value.insert(0, Constants.TARS_AT);
value.insert(0, name);
return value.toString();
//【新增】根据分布式上下文信息获取RouteKey
public String getRouteKeyByContext()
KeyRoute routeKey = new KeyRoute();
return KeyRoute.getRouteKey(DistributedContextManager.getDistributedContext())
4.3 变更代码的路径通过上述操作,改变了以下代码,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\support\\query\\QueryHelper.java
是指染色key和value放到tars请求结构体的status参数,需要透传给下游。这里讨论客户端。
在TarsGo里,这部分代码位置在tars/servant.go,通过阅读源码上下文,我们可以得知这个类主要围绕
ServantProxy
服务代理器而工作的;透传染色Key是在ServantProxy
的 Tars_invoke
方法里实现的,invoke方法一般是最终要执行的方法;在TarsJava里,对
Tars_invoke
类似的方法进行了层层封装;通过之前分析的客户端负载均衡源码分析可知,最终的执行方法在TarsInvoker
类的doInvokeServant
方法里,而该方法又对异步调用、同步调用、协程调用三种形式,这三个调用才是最终执行方法。因此,我们可以得到以下信息:
- 定位对应源码位置如下:
Go语言 | Java |
---|---|
tars/servant.go | TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\rpc\\tars\\TarsInvoker.java |
setRouteKeyToRequest()
,逻辑是通过分布式上下文信息,判断Tars请求体的status(map类型)是否存在TARS_ROUTE_KEY
键值对,存在则设置到Tars的响应体透传给下游,不存在则不处理;之所以添加到KeyRoute类,是因为该方法需要在多处地方重用,如《6.2 Java语言实现方式》;
public static void setRouteKeyToRequest(DistributedContext distributedContext, TarsServantRequest request)
String routeKey = KeyRoute.getRouteKey(distributedContext);
if( routeKey != null &
&
!"".equals(routeKey))
if(request.getStatus() != null)
request.getStatus().put(KeyRoute.TARS_ROUTE_KEY ,routeKey);
else
HashMap<
String, String>
status = new HashMap<
>
();
status.put(KeyRoute.TARS_ROUTE_KEY ,routeKey);
request.setStatus(status);
然后在同步调用方法
invokeWithSync()
、异步调用方法invokeWithAsync()
和协程调用方法invokeWithPromiseFuture()
里,调用上述方法即可。文章图片
5.3 变更代码的路径通过上述操作,改变了以下代码,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\rpc\\tars\\TarsInvoker.java
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\client\\subset\\KeyRoute.java
是指染色key和value放到tars请求结构体的status参数,需要透传给下游。这里讨论服务端。
在TarsGo里,这部分代码位置在tars/tarsprotocol.go,通过阅读源码上下文,我们可以得知这个类主要围绕
TarsProtocol
Tars服务端协议而工作的;透传染色Key是在TarsProtocol
的 Invoke
方法里实现的,其主要功能是将request请求作为字节数组,调用dispather,然后以字节数组返回response响应;在TarsJava中,Tars服务处理器为
TarsServantProcessor
,其中的process()
方法逻辑是处理request请求到response响应转换;因此,我们可以得到以下信息:
- 定位对应源码位置如下:
Go语言 | Java |
---|---|
tars/tarsprotocol.go | TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\server\\core\\TarsServantProcessor.java |
setRouteKeyToRequest()
方法即可;文章图片
6.3 变更代码的路径通过上述操作,改变了以下代码,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\server\\core\\TarsServantProcessor.java
在TarsGo中,这部分代码位置在endpoint.go,比较简单,增加了一个String类型的Subset字段属性;
在TarsJava中,endpoint的源码位置很容易找到,直接修改即可;主要修改两处,增加一个subset字段以及修改解析方法;
因此,我们可以得到以下信息:
- 定位对应源码位置如下:
Go语言 | Java |
---|---|
tars/util/endpoint/endpoint.go和tars/util/endpoint/convert.go | TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\common\\support\\Endpoint.java |
public class Endpoint private final String type;
private final String host;
private final int port;
private final int timeout;
private final int grid;
private final int qos;
private final String setDivision;
//新增
private String subset;
……
7.3 变更代码的路径通过上述操作,改变了以下代码,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\common\\support\\Endpoint.java
因为在参数匹配里要求正则匹配,因此在String工具类里新增一个算法实现正则匹配;
8.2 Java语言实现方式
public static Boolean matches(String regex, String input)
//非空校验
if(regex==null || "".equals(regex) || input == null)
return false;
char[] chars = regex.toCharArray();
boolean flage = true;
if( chars[0] == *)
//如果regex是*开头,如:*d123等。从d往后匹配;
if( regex.length() <
2)
return true;
int i;
flage = false;
for (i = 0;
i <
input.length();
i++)
if( input.charAt(i) == regex.charAt(1))
flage = true;
for (int j = 1;
j <
regex.length();
j++) if( i >
input.length() -1 &
&
regex.charAt(j) != * )
return false;
if( regex.charAt(j) == * || input.charAt(i) == regex.charAt(j))
i++;
else
flage = false;
else
if( chars[chars.length-1] == *)
//如果regex是*结尾,如uid12*。从第一个字符开始匹配
for (int i = 0;
i <
Math.min(regex.length(), input.length());
i++)
if(regex.charAt(i) == input.charAt(i) || regex.charAt(i) == *)
if( i == Math.min(regex.length(), input.length()) -1 &
&
regex.length() >
input.length()+1 )
flage = false;
else
flage = false;
else
//如果没有*,如uid123。
flage = regex.equals(input);
return flage;
8.3 变更代码的路径通过上述操作,改变了以下代码,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\main\\java\\com\\qq\\tars\\common\\util\\StringUtils.java
测试中包含按比例路由单次测试、按比例路由多次测试、按参数精确路由测试、按参数路由正则测试,以及registry测试;
由于其他同学部分的相关registry接口功能还未完成,故registry测试会失败。
9.2 Java语言实现方式
public class TestSubset //创建Subset过滤器
Subset subsetFilter = new Subset();
//模拟objectName
String objectName = "objectName";
//模拟routeKey
String routeKey = "routeKey";
//存活节点list列表
List<
EndpointF>
endpointFList = new ArrayList<
EndpointF>
();
Holder<
List<
EndpointF>
>
activeEp = new Holder<
List<
EndpointF>
>
(new ArrayList<
EndpointF>
());
//定义一个Session域,用来构建Tars请求体
Session session;
/**
* 按比例路由规则 - 单次测试
* 没有测试registry获取subsetConf功能
*/
@Test
public void testRatioOnce() //1. 给过滤器设置过滤规则
//1.1 创建SubsetManager管理器
SubsetManager subsetManager = new SubsetManager();
//1.1 设置比例路由规则
RatioConfig ratioConf = new RatioConfig();
Map<
String , Integer>
map = new HashMap<
>
();
map.put("v1",20);
map.put("v2",80);
//map.put("v3",20);
ratioConf.setRules(map);
//1.2 设置subsetConf,并加入缓存
SubsetConf subsetConf = new SubsetConf();
subsetConf.setEnanle(true);
subsetConf.setRuleType("ratio");
subsetConf.setRatioConf(ratioConf);
subsetConf.setLastUpdate( Instant.now() );
Map<
String, SubsetConf>
cache = new HashMap<
>
();
cache.put(objectName,subsetConf);
subsetManager.setCache(cache);
//1.3 给过滤器设置过滤规则和管理者
subsetFilter.setSubsetConf(subsetConf);
subsetFilter.setSubsetManager(subsetManager);
//2. 模拟存活节点
endpointFList.add(new EndpointF("host1",1,2,3,4,5,6,"setId1",7,8,9,10,"v1"));
endpointFList.add(new EndpointF("host2",1,2,3,4,5,6,"setId2",7,8,9,10,"v1"));
endpointFList.add(new EndpointF("host3",1,2,3,4,5,6,"setId3",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host4",1,2,3,4,5,6,"setId4",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v3"));
activeEp.setValue(endpointFList);
//3. 输出过滤前信息
System.out.println("过滤前节点信息如下:");
for( EndpointF endpoint : endpointFList)
System.out.println(endpoint.toString());
//4. 对存活节点按subset规则过滤
Holder<
List<
EndpointF>
>
filterActiveEp = subsetFilter.subsetEndpointFilter(objectName, routeKey, activeEp);
//5. 输出过滤结果System.out.println("过滤后节点信息如下:");
for( EndpointF endpoint : filterActiveEp.getValue() )
System.out.println(endpoint.toString());
/**
* 按比例路由规则 - 多次测试
* 没有测试registry获取subsetConf功能
*/
@Test
public void testRatioTimes() //1. 给过滤器设置过滤规则
//1.1 创建SubsetManager管理器
SubsetManager subsetManager = new SubsetManager();
//1.1 设置比例路由规则
RatioConfig ratioConf = new RatioConfig();
Map<
String , Integer>
map = new HashMap<
>
();
map.put("v1",20);
map.put("v2",80);
map.put("v3",20);
ratioConf.setRules(map);
//1.2 设置subsetConf,并加入缓存
SubsetConf subsetConf = new SubsetConf();
subsetConf.setEnanle(true);
subsetConf.setRuleType("ratio");
subsetConf.setRatioConf(ratioConf);
subsetConf.setLastUpdate( Instant.now() );
Map<
String, SubsetConf>
cache = new HashMap<
>
();
cache.put(objectName,subsetConf);
subsetManager.setCache(cache);
//1.3 给过滤器设置过滤规则和管理者
subsetFilter.setSubsetConf(subsetConf);
subsetFilter.setSubsetManager(subsetManager);
//2. 模拟存活节点
endpointFList.add(new EndpointF("host1",1,2,3,4,5,6,"setId1",7,8,9,10,"v1"));
endpointFList.add(new EndpointF("host2",1,2,3,4,5,6,"setId2",7,8,9,10,"v1"));
endpointFList.add(new EndpointF("host3",1,2,3,4,5,6,"setId3",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host4",1,2,3,4,5,6,"setId4",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v3"));
activeEp.setValue(endpointFList);
//3. 循环times次
int times = 1000000;
int v1Times = 0;
int v2Times = 0;
int v3Times = 0;
int errTimes = 0;
for (int i = 0;
i <
times;
i++)
//对存活节点按subset规则过滤
Holder<
List<
EndpointF>
>
filterActiveEp = subsetFilter.subsetEndpointFilter(objectName, routeKey, activeEp);
String subsetValue = https://www.songbingjia.com/android/filterActiveEp.getValue().get(0).getSubset();
if("v1".equals(subsetValue))
v1Times++;
else if("v2".equals(subsetValue))
v2Times++;
else if("v3".equals(subsetValue))
v3Times++;
else
errTimes++;
//输出结果
System.out.println("一共循环次数:" + times);
System.out.println("路由到v1次数:" + v1Times);
System.out.println("路由到v2次数:" + v2Times);
System.out.println("路由到v3次数:" + v3Times);
System.out.println("路由异常次数:" + errTimes);
/**
* 测试参数匹配 - 精确匹配
* 没有测试registry获取subsetConf功能
* 注意要成功必须routeKey和match匹配上
*/
@Test
public void testMatch() //1. 给过滤器设置过滤规则
//1.1 创建SubsetManager管理器
SubsetManager subsetManager = new SubsetManager();
//1.1 设置参数路由规则,这里的KeyRoute的value为 “规则的染色key”
KeyConfig keyConf = new KeyConfig();
List<
KeyRoute>
krs = new LinkedList<
>
();
krs.add(new KeyRoute("match","routeKey","v1"));
keyConf.setRules(krs);
//1.2 设置subsetConf,并加入缓存
SubsetConf subsetConf = new SubsetConf();
subsetConf.setEnanle(true);
subsetConf.setRuleType("key");
subsetConf.setKeyConf(keyConf);
subsetConf.setLastUpdate( Instant.now() );
Map<
String, SubsetConf>
cache = new HashMap<
>
();
cache.put(objectName,subsetConf);
subsetManager.setCache(cache);
//1.3 给过滤器设置过滤规则和管理者
subsetFilter.setSubsetConf(subsetConf);
subsetFilter.setSubsetManager(subsetManager);
//1.4 模拟Tars “请求的染色key” TARS_ROUTE_KEY,但请求染色key和规则染色key匹配时,才能精确路由
//1.4.1 创建Tars的请求体TarsServantRequest
TarsServantRequest request = new TarsServantRequest( session );
//1.4.2 往请求体的status添加TARS_ROUTE_KEY, "routeKey"键值对
Map<
String, String>
status = new HashMap<
>
();
status.put("TARS_ROUTE_KEY", "routeKey");
request.setStatus(status);
//1.4.3 构建分布式上下文信息,将请求放入分布式上下文信息中,因为getSubset()的逻辑是从分布式上下文信息中取
DistributedContext distributedContext = new DistributedContextImpl();
distributedContext.put(DyeingSwitch.REQ,request);
//2. 模拟存活节点
endpointFList.add(new EndpointF("host1",1,2,3,4,5,6,"setId1",7,8,9,10,"v1"));
endpointFList.add(new EndpointF("host2",1,2,3,4,5,6,"setId2",7,8,9,10,"v1"));
endpointFList.add(new EndpointF("host3",1,2,3,4,5,6,"setId3",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host4",1,2,3,4,5,6,"setId4",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v2"));
endpointFList.add(new EndpointF("host5",1,2,3,4,5,6,"setId5",7,8,9,10,"v3"));
activeEp.setValue(endpointFList);
//3. 输出过滤前信息
System.out.println("过滤前节点信息如下:");
for( EndpointF endpoint : endpointFList)
System.out.println(endpoint.toString());
//4. 对存活节点按subset规则过滤
Holder<
List<
EndpointF>
>
filterActiveEp = subsetFilter.subsetEndpointFilter(objectName, routeKey, activeEp);
//5. 输出过滤结果System.out.println("过滤后节点信息如下:");
for( EndpointF endpoint : filterActiveEp.getValue() )
System.out.println(endpoint.toString());
/**
* 测试参数匹配 - 正则匹配
* 没有测试registry获取subsetConf功能
* 注意要成功必须routeKey和match匹配上
*/
@Test
public void testEqual() //1. 给过滤器设置过滤规则
//1.1 创建SubsetManager管理器
9.3 变更代码的路径通过上述操作,改变了以下代码,需要在github上提交:
- TarsJava-1.7.x\\core\\src\\test\\java\\com\\qq\\tars\\client\\subset\\TestSubset.java
新人制作,如有错误,欢迎指出,感激不尽!
:::
::: hljs-center
欢迎关注公众号,会分享一些更日常的东西!
:::
::: hljs-center
如需转载,请标注出处!
:::
::: hljs-center
文章图片
:::
推荐阅读
- 探索 AVL 树基础原理
- 实战(使用Spring Boot Admin实现运维监控平台)
- 一文搞懂决策树! #51CTO博主之星评选#
- OpenHarmony啃论文成长计划-零基础解读分布式软总线通讯(绪论)
- Docker容器实战二(功能组件)
- Terraform系列二腾讯云CVM进一步相关玩法
- mysql备份与恢复
- DIG命令
- 云服务器