Tars | 第5篇 基于TarsGo Subset路由规则的Java JDK实现方式(上)
前言
利开园导师(下称“利导师")用Go语言实现了Subset路由规则,并在中期汇报分享会里介绍出来;这篇文章将基于利导师的实现方式,对Subset路由规则的细节做些理解与补充。
此篇文章为上半部分,旨在记录利导师对TarsGo代码的修改,并对分析其Subset路由规则。下半部分将对照与参考Go语言JDK的实现方式,对TarsJava相关Subset路由规则做代码改进。
上下部分文章在目录上一一对应,上半注重TarsGo分析,下半部分注重TarsJava实现方式。如上篇文章第一点修改.tars协议文件记录利导师在TarsGo的代码修改,下片文章第一点也是修改.tars协议文件,侧重点在如何用Java语言实现。上下文章相辅相成,建议对照学习。
一些资源链接如下:
下半部分文章链接
https://blog.csdn.net/dlhjw1412/article/details/119810186
TarsJava 实现Subset路由规则JDK链接地址
https://github.com/dlhjw/TarsJava/commit/cc2fe884ecbe8455a8e1f141e21341f4f3dd98a3
TarsGo 实现Subset路由规则JDK链接地址
https://github.com/defool/TarsGo/commit/136878e9551d68c4b54c402df564729f51f3dd9c#
1. 修改.tars协议文件
在Tars协议文件里;
1.1 Go语言修改部分
协议文件共有两处地方需要更改,一是给EndpointF节点增加Subset配置,二是在查找助手里添加根据ID获取Subset配置信息的接口配置;
给EndpointF节点增加Subset配置:

根据ID获取Subset配置信息的接口:

1.2 修改地方的逻辑
| 原逻辑 | 现逻辑 |
|---|---|
| 无 | 给节点增加Subset配置,增加的是一个Tars协议的结构体 |
| 无 | 增加获取Subset信息的接口,同样Tars协议的结构体 |
注意:
- 第一处:给EndpointF节点增加Subset配置
- 最终结果可能不是这样,要看Registry接口是怎样的;
- 修改协议文件后需要运行一段命令自动生成相应代码;
- TarsGo的自动生成命令在
tars/protocol/res/Makefile里;
- 第二处:根据ID获取Subset配置信息的接口
- String id 为“应用名.服务名.端口名”;
- id这样设置是考虑到与其他接口命令对其;
- 该接口要与Tars Registry新增的接口名对上;
1.3 通过协议文件自动生成代码
Tars有个强大的功能,它能根据.tars里的配置文件自动生成相应Bean代码;
而在TarsGo里,对应代码如下:

执行上述命令后,对应代码会发生改变,如下:

这告诉我们老师发布的TarsGo代码里,有些代码不需要我们手动去更改,而是通过命令自动生成的。在Java中命令为在项目根路径执行mvn tars:tars2java。具体过程将在下半部分文章里详解,这里仅记录TarsGo代码在哪发生变化。
2. 【核心】增添Subset核心功能
Go语言在tars/subset.go内
2.1 Go语言修改部分
package tars
import (
"encoding/json"
"math/rand"
"regexp"
"strconv"
"sync"
"time"
"github.com/TarsCloud/TarsGo/tars/protocol/res/endpointf"
"github.com/TarsCloud/TarsGo/tars/protocol/res/queryf"
"github.com/TarsCloud/TarsGo/tars/util/consistenthash"
"github.com/TarsCloud/TarsGo/tars/util/endpoint"
"github.com/serialx/hashring"
)
var (
enableSubset = true
subsetMg = &subsetManager{}
)
type hashString string
func (h hashString) String() string {
return string(h)
}
type subsetConf struct {
enable bool
ruleType string // ratio/key
ratioConf *ratioConfig
keyConf *keyConfig
lastUpdate time.Time
}
type ratioConfig struct {
ring *hashring.HashRing
}
type keyRoute struct {
action string
value string
route string
}
type keyConfig struct {
rules []keyRoute
defaultRoute string
}
type subsetManager struct {
lock *sync.RWMutex
cache map[string]*subsetConf
registry *queryf.QueryF
}
//根据服务名获取它的Subset方法,返回subsetConf配置项
func (s *subsetManager) getSubsetConfig(servantName string) *subsetConf {
s.lock.RLock()
defer s.lock.RUnlock()
var ret *subsetConf
//如果缓存在最近时间之内,就直接返回
if v, ok := s.cache[servantName]; ok {
ret = v
if v.lastUpdate.Add(time.Second * 10).After(time.Now()) {
return ret
}
}
//如果上次获取时间超时,则调用registry接口获取对应配置
// get config from registry
conf := &endpointf.SubsetConf{}
retVal, err := s.registry.FindSubsetConfigById(servantName, conf)
if err != nil || retVal != 0 {
// log error
return ret
}
ret = &subsetConf{
ruleType: conf.RuleType,
lastUpdate: time.Now(),
}
s.cache[servantName] = ret
// 解析从registry那获取的配置信息
// parse subset conf
if !conf.Enable {
ret.enable = false
return ret
}
//按比例路由
if conf.RuleType == "ratio" {
kv := make(map[string]int)
json.Unmarshal([]byte(conf.RuteData), &kv)
ret.ratioConf = &ratioConfig{ring: hashring.NewWithWeights(kv)}
} else {
keyConf := &keyConfig{}
kvlist := make([]map[string]string, 0)
json.Unmarshal([]byte(conf.RuteData), &kvlist)
for _, kv := range kvlist {
//默认路由
if vv, ok := kv["default"]; ok {
keyConf.defaultRoute = vv
}
if vv, ok := kv["match"]; ok {
//精确匹配
keyConf.rules = append(keyConf.rules, keyRoute{
action: "match",
value: vv,
route: kv["route"],
})
} else if vv, ok := kv["equal"]; ok {
//正则匹配
keyConf.rules = append(keyConf.rules, keyRoute{
action: "equal",
value: vv,
route: kv["route"],
})
}
}
ret.keyConf = keyConf
}
return ret
}
func (s *subsetManager) getSubset(servantName, routeKey string) string {
// check subset config exists
subsetConf := subsetMg.getSubsetConfig(servantName)
if subsetConf == nil {
return ""
}
// route key to subset
if subsetConf.ruleType == "ratio" {
return subsetConf.ratioConf.findSubet(routeKey)
}
return subsetConf.keyConf.findSubet(routeKey)
}
//根据subset规则过滤节点
func subsetEndpointFilter(servantName, routeKey string, eps []endpoint.Endpoint) []endpoint.Endpoint {
if !enableSubset {
return eps
}
subset := subsetMg.getSubset(servantName, routeKey)
if subset == "" {
return eps
}
ret := make([]endpoint.Endpoint, 0)
for i := range eps {
if eps[i].Subset == subset {
ret = append(ret, eps[i])
}
}
return ret
}
func subsetHashEpFilter(servantName, routeKey string, m *consistenthash.ChMap) *consistenthash.ChMap {
if !enableSubset {
return m
}
subset := subsetMg.getSubset(servantName, routeKey)
if subset == "" {
return m
}
ret := consistenthash.NewChMap(32)
for _, v := range m.GetNodes() {
vv, ok := v.(endpoint.Endpoint)
if ok && vv.Subset == subset {
ret.Add(vv)
}
}
return ret
}
func (k *ratioConfig) findSubet(key string) string {
// 为空时使用随机方式
if key == "" {
key = strconv.Itoa(rand.Int())
}
v, _ := k.ring.GetNode(key)
return v
}
func (k *keyConfig) findSubet(key string) string {
for _, v := range k.rules {
if v.action == "equal" && key == v.value {
return v.route
} else if v.action == "match" {
if matched, _ := regexp.Match(v.value, []byte(key)); matched {
return v.route
}
}
}
return k.defaultRoute
}
2.2 新增地方的逻辑
| 新增类型 | 新增内容 |
|---|---|
| 结构体 | 新增Subset配置项的结构体 subsetConf |
| 结构体 | 新增路由规则配置项的结构体ratioConfig |
| 结构体 | 新增染色路径的结构体keyRoute |
| 结构体 | 新增染色配置项的结构体keyConfig |
| 结构体 | 新增subset管理者的结构体subsetManager |
| 方法 | 新增获取subset配置项的方法getSubsetConfig |
| 方法 | 新增获取比例 / 染色路由配置项的方法getSubset |
| 方法 | 新增根据subset规则过滤节点的方法subsetEndpointFilter |
| 方法 | 新增根据一致hash的subset规则过滤节点的方法subsetHashEpFilter |
| 方法 | 新增按比例路由路由路径的方法findSubet |
| 方法 | 新增按默认路由路径findSubet |
3. 添加常量与获取染色key的方法
在tars/util/current/tarscurrent相关包里,处理上下文信息相关;
3.1 Go语言修改部分

3.2 修改地方的逻辑
| 原逻辑 | 现逻辑 |
|---|---|
| 无 | 新增一个常量字段STATUS_ROUTE_KEY |
| 无 | 新增两个方法,分别是设置与获取染色Key |
4. 【核心】修改获取服务IP规则
在节点管理的相关文件里;方法是实现在第8点;
4.1 Go语言修改部分

4.2 修改地方的逻辑
| 原逻辑 | 现逻辑 |
|---|---|
| 获取所有的服务IP列表 | 在原来IP列表的基础上根据请求包的current.STATUS_ROUTE_KEY值过滤部分节点 |
5. 实现透传染色Key功能(客户端)
在tars/tarsprotocol相关文件里;
5.1 Go语言修改部分

5.2 修改地方的逻辑
| 原逻辑 | 现逻辑 |
|---|---|
| 无透传染色Key | 在客户端最终执行的方法里增加透传染色Key功能 |
注意:
- 这里的染色Key为新建的,与源代码里的染色Key不同;
- 可以参考染色一致的实现方式,区别是Key的名称不同,实现思路类似;
- 在TarsJava客户端中,这里的最终执行的方法指
TarsInvoker类里的三个执行方法;- 即:同步调用方法
invokeWithSync()、异步调用方法invokeWithAsync()和协程调用方法invokeWithPromiseFuture()
- 即:同步调用方法
6. 实现透传染色Key功能(服务端)
在tars/tarsprotocol.go相关文件里;
6.1 Go语言修改部分

6.2 修改地方的逻辑
| 原逻辑 | 现逻辑 |
|---|---|
| 无透传染色Key | 在服务端最终执行的方法里增加透传染色Key功能 |
- 在TarsJava服务端中,这里的最终执行的方法指
TarsServantProcessor.process()
7. 给节点信息增添Subset字段
在节点信息相关文件里;
7.1 Go语言修改部分

7.2 修改地方的逻辑
| 原逻辑 | 现逻辑 |
|---|---|
| 无 | 给节点信息增加Subset字段 |
| 无 | 修改解析函数,能识别出sunset字段 |
注意:
- 不一定是String类型,只要在Endpoint对象结构体里添加一个Subset相关属性,有地方用即可;
- 这部分在Java中仅为Endpoint.java类,故放在一起;
* 8. 新增工具类
在工具类包里;
8.1 Go语言修改部分

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

Tars | 第5篇 基于TarsGo Subset路由规则的Java JDK实现方式(上)的更多相关文章
- Tars | 第6篇 基于TarsGo Subset路由规则的Java JDK实现方式(下)
目录 前言 1. 修改.tars协议文件 1.1 Java源码位置及逻辑分析 1.2 Java语言实现方式 1.3 通过协议文件自动生成代码 1.4 变更代码的路径 2. [核心]增添Subset核心 ...
- Tars | 第4篇 Subset路由规则业务分析与源码探索
目录 前言 1. Subset不是负载均衡 1.1 任务需求 1.2 负载均衡源码结构图 1.3 负载均衡四种调用器 1.4 新增两种负载均衡调用器 1.5 Subset应该是"过滤&quo ...
- Tars | 第3篇 Tars中期汇报测试文档(Java语言实现Subset路由规则)
目录 前言 1. 任务介绍 2. 测试模拟方案 2.0 *前置工作 2.1 添加路由规则 2.2 添加存活节点 2.3 [输出]遍历输出当前存活节点 2.4 [核心]对存活节点按subset规则过滤 ...
- Tars | 第7篇 TarsJava Subset最终代码的测试方案设计
目录 前言 1. SubsetConf配置项的结构 1.1 SubsetConf 1.2 RatioConfig 1.3 KeyConfig 1.4 KeyRoute 1.5 SubsetConf的结 ...
- Tars | 第8篇 TarsJava Subset最终代码的执行流程与原理分析
目录 前言 1. SubsetConf配置项的结构 1.1 SubsetConf 1.2 RatioConfig 1.3 KeyConfig 1.4 KeyRoute 1.5 SubsetConf的结 ...
- Tars | 第0篇 腾讯犀牛鸟开源人才培养计划Tars实战笔记目录
腾讯犀牛鸟开源人才培养计划Tars实战笔记目录 前言 在2021年夏,笔者参加了腾讯首届开源人才培养计划的Tars项目,负责Subset流量管理规则的Java语言JDK实现.其中写作几篇开源实战笔记, ...
- 【ASP.NET Core】给路由规则命名有何用处
上一篇中老周给伙伴们介绍了自定义视图搜索路径的方法,本篇咱们扯一下有关 URL 路径规则的名称问题.在扯今天的话题之前,先补充点东东.在上一篇中设置视图搜索路径时用到三个有序参数:{2}{1}{0}, ...
- react-router v4 路由规则解析
前言 react-router升级到4之后,跟前面版本比有了很大的差别. 例如包的拆分,动态路由等详细的差别就不说了,各位大神的总结也很到位,详细可以点击看看,All About React Rout ...
- MVC路由规则以及前后台获取Action、Controller、ID名方法
1.前后台获取Action.Controller.ID名方法 前台页面:ViewContext.RouteData.Values["Action"].ToString(); Vie ...
随机推荐
- 微信开发者工具获取位置错误(定位到北京)---调用wx.getLocation不出现获取定位提示
微信开发者工具获取不到自己当前的位置可能是以下几个原因: 1.调用wx.getLocation方法之后需要在app.json中声明permission字段 { "pages": [ ...
- FreeRTOS-04-内核控制函数+时间管理函数
说明 本文仅作为学习FreeRTOS的记录文档,作为初学者肯定很多理解不对甚至错误的地方,望网友指正. FreeRTOS是一个RTOS(实时操作系统)系统,支持抢占式.合作式和时间片调度.适用于微处理 ...
- MySQL 优化特定类型的查询
优化COUNT()查询 COUNT() 是一个特殊的函数,有两种非常不同的作用: 统计某个列值的数量,也可以统计行数.在统计列值时要求列值是非空的(不统计NULL ).如果在COUNT() 的括号中指 ...
- 程序员被老板要求两个月做个APP,要不比京东差,网友:做一个快捷方式,直接链到京东
隔行如隔山,这句话说得一点都没错.做一个程序员,很多人都会羡慕,也有很多人会望而却步. 作为一个外行人,你别看程序员每天坐在电脑前敲敲键盘打打代码,以为很简单,其实啊也只有程序员自己明白,任何一个看似 ...
- 树莓派3B/3B+/4B 刷机装系统烧录镜像教程
树莓派3B/3B+/4B 刷机装系统烧录镜像教程 树莓派 背景故事 刚拿到树莓派的第一件事,应该就是要装系统了,那么应该怎么操作呢?下面就给大家介绍一下吧. 硬件准备 树莓派:3B/3B+/4B,本教 ...
- Hello World!!
已经工作了一年多,现在才开始写博客.话说,种一棵树最好的时机是十年前,其次是现在,我觉得不迟.俗话说滴水穿石,我想把一些东西,都慢慢积累起来,看见自己的成长.既方便查看,更不容易忘记.可能在网上已经有 ...
- 修改Linux系统的默认语言编码集
RedHat 今天晚上发现服务器上vi的界面提示变成了乱码,只能将XShell的编码改为GBK才能正常显示,导致consolas字体无法使用,GBK编码下的字体丑陋无比,无法忍受,一轮google之后 ...
- Spring学习02(DI依赖注入)
5.依赖注入(Dependency Injection,DI) 5.1 概念 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 . 注入 : 指Bean对象所依赖的资源 , 由容器 ...
- ECDSA—模乘模块
如果a,b属于GF(P),则有乘法运算a*b=r (mod p), 其中r满足0<r<p-1,即a*b除以p的余数.该操作成为模p乘法.本模块输入两个数,完成两个数的模乘运算. 信号名 方 ...
- NOIP 模拟 $21\; \rm Median$
题解 \(by\;zj\varphi\) 对于这个序列,可以近似得把它看成随机的,而对于随机数列,每个数的分布都是均匀的,所以中位数的变化可以看作是常数 那么可以维护一个指向中位数的指针,同时维护有多 ...