Tars是騰訊從2008年到今天一直在使用的後臺邏輯層的統一應用框架TAF(Total Application Framework),目前支持C++,Java,PHP,Nodejs,Golang語言。該框架爲用戶提供了涉及到開發、運維、以及測試的一整套解決方案,幫助一個產品或者服務快速開發、部署、測試、上線。 它集可擴展協議編解碼、高性能RPC通訊框架、名字路由與發現、發佈監控、日誌統計、配置管理等於一體,經過它能夠快速用微服務的方式構建本身的穩定可靠的分佈式應用,並實現完整有效的服務治理。目前該框架在騰訊內部,各大核心業務都在使用,頗受歡迎,基於該框架部署運行的服務節點規模達到上萬個。c++
Tars 於2017年4月開源,並於2018年6月加入Linux 基金會,項目地址 https://github.com/TarsCloud 。git
TarsGo 是Tars 的Go語言實現版本, 於2018年9月開源, 項目地址 https://github.com/TarsCloud/TarsGo github
Tars協議是一種類c++標識符的語言,用於生成具體的服務接口文件,Tars文件是Tars框架中客戶端和服務端的通訊接口,經過Tars的映射實現遠程對象調用。 Tars 協議是和語言無關,基於IDL接口描述語言的二進制編碼協議。json
詳見 TarsProtocol網絡
Protocol Buffers (簡稱 PB )是 Google 的一種數據交換的格式,它獨立於語言,獨立於平臺,最先公佈於 2008年7月。隨着微服務架構的發展及自身的優異表現,ProtoBuf 可用於諸如網絡傳輸、配置文件、數據存儲等諸多領域,目前在互聯網上有着大量應用。架構
PB協議是單獨的協議,若是要支持RPC,能夠定義service字段,而且基於protoc-gen-go 的grpc 插件生成相應的grpc編碼。app
如下面的 proto 文件爲例框架
syntax = "proto3"; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }
使用protoc生成相應的接口代碼,以Go語言爲例:運維
protoc --go_out=plugins=grpc:. helloworld.proto
若是對於現有已使用grpc,使用proto,想轉換成tars協議的用戶而言,須要將上面的proto文件翻譯成Tars文件。對於Tars而言,Tars是編寫tars文件,而後用相應的工具tars2xxx, 好比tars2go生成相應的接口代碼。上面的proto文件翻譯成tars文件是:tcp
module helloworld{ struct HelloRequest { 1 require string name ; }; struct HelloReply { 1 require string message ; }; interface Greeter { int SayHello(HelloRequest req, out HelloReply resp); }; }
而後調用tars2go生成 相應的tarsgo接口:
tars2go --outdir ./ helloworld.tars
這種翻譯會比較繁瑣,並且容易出錯。 爲此咱們決定編寫插件支持proto直接生成tars的rpc邏輯。
有兩種方案,一種是寫protoc插件,直接讀取protoc解析proto文件的二進制流,對service相應的字段進行解析,以便生成相應的rpc邏輯,其餘交由protoc-gen-go處理
另一種是直接編寫protoc-gen-go的插件,相似gRPC插件,
這裏決定採用方案2 。
protoc-gen-go 並無插件編寫的相關說明,但protoc-gen-go的代碼邏輯裏面是預留了插件編寫的規範的,參照grpc,主要有 grpc/grpc.go 和一個致使插件包的link_grpc.go 。 這裏咱們編寫 tarsrpc/tarsrpc.go 和 link_tarsrpc.go
代碼邏輯基本上就是繼承 generator.Generator,註冊插件, 獲取相應的service,method,和method的input和output,再調用P方法將要生成的代碼輸出便可
func init() { generator.RegisterPlugin(new(tarsrpc)) } // tarsrpc is an implementation of the Go protocol buffer compiler's // plugin architecture. It generates bindings for tars rpc support. type tarsrpc struct { gen *generator.Generator } func (t *tarsrpc) generateService(file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int) { originServiceName := service.GetName() serviceName := upperFirstLatter(originServiceName) t.P("// This following code was generated by tarsrpc") t.P(fmt.Sprintf("// Gernerated from %s", file.GetName())) t.P(fmt.Sprintf(`type %s struct { s model.Servant } `, serviceName)) t.P() ... ... }
這裏主要是生成 service 轉成相應的interface,而後interface裏面有定義的rpc method, 用戶能夠實現本身真正業務邏輯的method,其他的都是tars相應的發包收包邏輯。Tars的請求包體:
type RequestPacket struct { IVersion int16 `json:"iVersion"` CPacketType int8 `json:"cPacketType"` IMessageType int32 `json:"iMessageType"` IRequestId int32 `json:"iRequestId"` SServantName string `json:"sServantName"` SFuncName string `json:"sFuncName"` SBuffer []uint8 `json:"sBuffer"` ITimeout int32 `json:"iTimeout"` Context map[string]string `json:"context"` Status map[string]string `json:"status"` }
咱們只須要將rpc method的名字,放入RequestPacket 的SFuncName ,而後將請求參數調用proto的Marshal序列化後放到 SBuffer。
而對於回包,Tars的回包結構體:
type ResponsePacket struct { IVersion int16 `json:"iVersion"` CPacketType int8 `json:"cPacketType"` IRequestId int32 `json:"iRequestId"` IMessageType int32 `json:"iMessageType"` IRet int32 `json:"iRet"` SBuffer []uint8 `json:"sBuffer"` Status map[string]string `json:"status"` SResultDesc string `json:"sResultDesc"` Context map[string]string `json:"context"` }
一樣,咱們只須要將返回的結果,調用Marshal 將請求放入 SBuffer ,其餘邏輯和tars保持一致。
編寫完插件,就能夠經過和grpc生成代碼相同的方式,將proto 文件轉化成tars的接口文件:
protoc --go_out=plugins=tarsrpc:. helloworld.proto
下面是簡單的服務端例子
package main import ( "github.com/TarsCloud/TarsGo/tars" "helloworld" //上面工具生成的package ) type GreeterImp struct { } func (imp *GreeterImp) SayHello(input helloworld.HelloRequest)(output helloworld.HelloReply, err error) { output.Message = "hello" + input.GetName() return output, nil } func main() { //Init servant imp := new(GreeterImp) //New Imp app := new(helloworld.Greeter) //New init the A JCE cfg := tars.GetServerConfig() //Get Config File Object app.AddServant(imp, cfg.App+"."+cfg.Server+".GreeterTestObj") //Register Servant tars.Run() }
簡單的客戶端調用例子
package main import ( "fmt" "github.com/TarsCloud/TarsGo/tars" "helloworld" ) func main() { comm := tars.NewCommunicator() obj := fmt.Sprintf("StressTest.HelloPbServer.GreeterTestObj@tcp -h 127.0.0.1 -p 10014 -t 60000") app := new(helloworld.Greeter) comm.StringToProxy(obj, app) input := helloworld.HelloRequest{Name: "sandyskies"} output, err := app.SayHello(input) if err != nil { fmt.Println("err: ", err) } fmt.Println("result is:", output.Message) }
protoc-gen-go 的插件放在 TarsGo/tars/tools/pb2tarsgo , 須要要求Protocol Buffer 3.6.0以上。