TarsGo支援Protocol Buffer

Tybyq發表於2018-11-16

Tars是騰訊從2008年到今天一直在使用的後臺邏輯層的統一應用框架TAF(Total Application Framework),目前支援C++,Java,PHP,Nodejs,Golang語言。該框架為使用者提供了涉及到開發、運維、以及測試的一整套解決方案,幫助一個產品或者服務快速開發、部署、測試、上線。 它集可擴充套件協議編解碼、高效能RPC通訊框架、名字路由與發現、釋出監控、日誌統計、配置管理等於一體,透過它可以快速用微服務的方式構建自己的穩定可靠的分散式應用,並實現完整有效的服務治理。目前該框架在騰訊內部,各大核心業務都在使用,頗受歡迎,基於該框架部署執行的服務節點規模達到上萬個。

Tars 於2017年4月開源,並於2018年6月加入Linux 基金會,專案地址   。

TarsGo 是Tars 的Go語言實現版本, 於2018年9月開源, 專案地址 

Tars協議是一種類c++識別符號的語言,用於生成具體的服務介面檔案,Tars檔案是Tars框架中客戶端和服務端的通訊介面,透過Tars的對映實現遠端物件呼叫。 Tars 協議是和語言無關,基於IDL介面描述語言的二進位制編碼協議。

詳見 

Protocol Buffers (簡稱 PB )是 Google 的一種資料交換的格式,它獨立於語言,獨立於平臺,最早公佈於 2008年7月。隨著微服務架構的發展及自身的優異表現,ProtoBuf 可用於諸如網路傳輸、配置檔案、資料儲存等諸多領域,目前在網際網路上有著大量應用。

PB協議是單獨的協議,如果要支援RPC,可以定義service欄位,並且基於protoc-gen-go 的grpc 外掛生成相應的grpc編碼。

以下面的 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 greetingsmessage 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檔案是:

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 mainimport (    "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 mainimport (    "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)
}


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31557424/viewspace-2220416/,如需轉載,請註明出處,否則將追究法律責任。

相關文章