前言
利開園導師(下稱“利導師")用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. 新增工具類
在工具類包裡;