Tars | 第5篇 基於TarsGo Subset路由規則的Java JDK實現方式(上)



前言

利開園導師(下稱“利導師")用Go語言實現了Subset路由規則,並在中期匯報分享會里介紹出來;這篇文章將基於利導師的實現方式,對Subset路由規則的細節做些理解與補充。

此篇文章為上半部分,旨在記錄利導師對TarsGo代碼的修改,並對分析其Subset路由規則。下半部分將對照與參考Go語言JDK的實現方式,對TarsJava相關Subset路由規則做代碼改進。

上下部分文章在目錄上一一對應,上半注重TarsGo分析,下半部分注重TarsJava實現方式。如上篇文章第一點修改.tars協議文件記錄利導師在TarsGo的代碼修改,下片文章第一點也是修改.tars協議文件,側重點在如何用Java語言實現。上下文章相輔相成,建議對照學習。

一些資源鏈接如下:

下半部分文章鏈接
https://www.cnblogs.com/dlhjw/p/15245116.html

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自動生成代碼
執行上述命令后,對應代碼會發生改變,如下:

根據協議文件自動生成代碼

這告訴我們老師發布的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語言修改部分

第八處修改



最后

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標注出處!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM