您好,登錄后才能下訂單哦!
本文作者:陳明杰(sandyskies)
Tars是騰訊從2008年到今天一直在使用的后臺(tái)邏輯層的統(tǒng)一應(yīng)用框架,目前支持C++,Java,PHP,Nodejs,Golang語(yǔ)言。該框架為用戶提供了涉及到開發(fā)、運(yùn)維、以及測(cè)試的一整套解決方案,幫助一個(gè)產(chǎn)品或者服務(wù)快速開發(fā)、部署、測(cè)試、上線。 它集可擴(kuò)展協(xié)議編解碼、高性能RPC通信框架、名字路由與發(fā)現(xiàn)、發(fā)布監(jiān)控、日志統(tǒng)計(jì)、配置管理等于一體,通過(guò)它可以快速用微服務(wù)的方式構(gòu)建自己的穩(wěn)定可靠的分布式應(yīng)用,并實(shí)現(xiàn)完整有效的服務(wù)治理。目前該框架在騰訊內(nèi)部,各大核心業(yè)務(wù)都在使用,頗受歡迎,基于該框架部署運(yùn)行的服務(wù)節(jié)點(diǎn)規(guī)模達(dá)到上萬(wàn)個(gè)。
Tars 于2017年4月開源,并于2018年6月加入Linux 基金會(huì)。
TarsGo 是Tars 的Go語(yǔ)言實(shí)現(xiàn)版本, 于2018年9月開源, 項(xiàng)目地址 https://github.com/TarsCloud/TarsGo ,歡迎star 。
在上次開源之后,有些用戶反饋了一些需求,基于用戶反饋的需求,我們進(jìn)行了實(shí)現(xiàn),并發(fā)布了1.1.0版本。 本次發(fā)布新增了:支持pb、支持zipkin分布式追蹤、支持filter(自定義插件編寫)、支持context 等,除此之外還做了一系列優(yōu)化和bugfix。
Protocol Buffers (簡(jiǎn)稱 PB )是 Google 的一種數(shù)據(jù)交換的格式,它獨(dú)立于語(yǔ)言,獨(dú)立于平臺(tái),最早公布于 2008年7月。隨著微服務(wù)架構(gòu)的發(fā)展及自身的優(yōu)異表現(xiàn),ProtoBuf 可用于諸如網(wǎng)絡(luò)傳輸、配置文件、數(shù)據(jù)存儲(chǔ)等諸多領(lǐng)域,目前在互聯(lián)網(wǎng)上有著大量應(yīng)用。
如果對(duì)于現(xiàn)有已使用grpc,使用proto文件,想轉(zhuǎn)換成tars協(xié)議的用戶而言,需要將上面的proto文件翻譯成Tars文件。這種翻譯會(huì)比較繁瑣,而且容易出錯(cuò)。 為此我們決定編寫插件支持proto文件直接生成tars的rpc邏輯。protoc-gen-go的代碼邏輯里面是預(yù)留了插件編寫的規(guī)范的,參照grpc,主要有 grpc/grpc.go 和一個(gè)導(dǎo)入插件的link_grpc.go 。 這里我們編寫 tarsrpc/tarsrpc.go 和 link_tarsrpc.go
使用方面:
將這兩個(gè)文件放到protoc-gen-go 下面,go install重新生成protoc-gen-go 二進(jìn)制
定義proto 文件
protoc --go_out=plugins=tarsrpc:. helloworld.proto
編寫tars 客戶端和服務(wù)端代碼,參數(shù)使用pb生成的結(jié)構(gòu)體,其余代碼邏輯和正常的tars服務(wù)一致。
為了支持用戶編寫插件,我們支持了filter機(jī)制,分為服務(wù)端的過(guò)濾器和客戶端過(guò)濾器,用戶可以基于這個(gè)機(jī)制,實(shí)現(xiàn)自己的TarsGo插件。
//服務(wù)端過(guò)濾器, 傳入dispatch,和f, 用于調(diào)用用戶代碼, req, 和resp為傳入的用戶請(qǐng)求和服務(wù)端相應(yīng)包體
type ServerFilter func(ctx context.Context, d Dispatch, f interface{}, req *requestf.RequestPacket, resp *requestf.ResponsePacket, withContext bool) (err error)
//客戶端過(guò)濾器, 傳入msg(包含obj信息,adapter信息,req和resp包體), 還有用戶設(shè)定的調(diào)用超時(shí)
type ClientFilter func(ctx context.Context, msg *Message, invoke Invoke, timeout time.Duration) (err error)
//注冊(cè)服務(wù)端過(guò)濾器
//func RegisterServerFilter(f ServerFilter)
//注冊(cè)客戶端過(guò)濾器
//func RegisterClientFilter(f ClientFilter)
有了過(guò)濾器,我們就能對(duì)服務(wù)端和客戶端的請(qǐng)求做一些過(guò)濾,比如使用 hook用于分布式追蹤的opentracing 的span。
我們來(lái)看下客戶端filter的例子:
//生成客戶端tars filter,通過(guò)注冊(cè)這個(gè)filter來(lái)實(shí)現(xiàn)span的注入
func ZipkinClientFilter() tars.ClientFilter {
return func(ctx context.Context, msg *tars.Message, invoke tars.Invoke, timeout time.Duration) (err error) {
var pCtx opentracing.SpanContext
req := msg.Req
//先從客戶端調(diào)用的context 里面看下有沒(méi)有傳遞來(lái)調(diào)用鏈的信息,
//如果有,則以這個(gè)做為父span,如果沒(méi)有,則起一個(gè)新的span,span名字是RPC請(qǐng)求的函數(shù)名
if parent := opentracing.SpanFromContext(ctx); parent != nil {
pCtx = parent.Context()
}
cSpan := opentracing.GlobalTracer().StartSpan(
req.SFuncName,
opentracing.ChildOf(pCtx),
ext.SpanKindRPCClient,
)
defer cSpan.Finish()
cfg := tars.GetServerConfig()
//設(shè)置span的信息,比如我們調(diào)用的客戶端的ip地址,請(qǐng)求的接口,方法,協(xié)議,客戶端版本等信息
cSpan.SetTag("client.ipv4", cfg.LocalIP)
cSpan.SetTag("tars.interface", req.SServantName)
cSpan.SetTag("tars.method", req.SFuncName)
cSpan.SetTag("tars.protocol", "tars")
cSpan.SetTag("tars.client.version", tars.TarsVersion)
//將span注入到 請(qǐng)求包體的 Status里面,status 是一個(gè)map[strint]string 的結(jié)構(gòu)體
if req.Status != nil {
err = opentracing.GlobalTracer().Inject(cSpan.Context(), opentracing.TextMap, opentracing.TextMapCarrier(req.Status))
if err != nil {
logger.Error("inject span to status error:", err)
}
} else {
s := make(map[string]string)
err = opentracing.GlobalTracer().Inject(cSpan.Context(), opentracing.TextMap, opentracing.TextMapCarrier(s))
if err != nil {
logger.Error("inject span to status error:", err)
} else {
req.Status = s
}
}
//沒(méi)什么其他需要修改的,就進(jìn)行客戶端調(diào)用
err = invoke(ctx, msg, timeout)
if err != nil {
//調(diào)用錯(cuò)誤,則記錄span的錯(cuò)誤信息
ext.Error.Set(cSpan, true)
cSpan.LogFields(oplog.String("event", "error"), oplog.String("message", err.Error()))
}
return err
}
服務(wù)端也會(huì)注冊(cè)一個(gè)filter,主要功能就是從request包體的status 提取調(diào)用鏈的上下文,以這個(gè)作為父span,進(jìn)行調(diào)用信息的記錄。
整體的一個(gè)效果:
詳細(xì)代碼參見 TarsGo/tars/plugin/zipkintracing
完整的zipkin tracing的客戶端和服務(wù)端例子,詳見 TarsGo/examples下面的ZipkinTraceClient和ZipkinTraceServer
TarsGo 之前在生成的客戶端代碼,或者用戶傳入的實(shí)現(xiàn)代碼里面,都沒(méi)有使用context。 這使得我們想傳遞一些框架的信息,比如客戶端ip,端口等,或者用戶傳遞一些調(diào)用鏈的信息給框架,都很難于實(shí)現(xiàn)。 通過(guò)接口的一次重構(gòu),支持了context,這些上下文的信息,將都通過(guò)context來(lái)實(shí)現(xiàn)。 這次重構(gòu)為了兼容老的用戶行為,采用了完全兼容的設(shè)計(jì)。
服務(wù)端使用context
type ContextTestImp struct {
}
//只需在接口上添加 ctx context.Context參數(shù)
func (imp *ContextTestImp) Add(ctx context.Context, a int32, b int32, c *int32) (int32, error) {
//我們可以通過(guò)context 獲取框架傳遞的信息,比如下面的獲取ip, 甚至返回一些信息給框架,詳見tars/util/current下面的接口
ip, ok := current.GetClientIPFromContext(ctx)
if !ok {
logger.Error("Error getting ip from context")
}
return 0, nil
}
//以前使用AddServant ,現(xiàn)在只需改成AddServantWithContext
app.AddServantWithContext(imp, cfg.App+"."+cfg.Server+".ContextTestObj")
客戶端使用context
ctx := context.Background()
c := make(map[string]string)
c["a"] = "b"
//以前使用app.Add 進(jìn)行客戶端調(diào)用,這里只要變成app.AddWithContext ,就可以傳遞context給框架,如果要設(shè)置給tars請(qǐng)求的context
//可以多傳入?yún)?shù),比如c,參數(shù)c是可選的,格式是 ...[string]string
ret, err := app.AddWithContext(ctx, i, i*2, &out, c)
服務(wù)端和客戶端的完整例子,詳見 TarGo/examples
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。