TarsGo支持Protocol Buffer

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

Tars 於2017年4月開源,並於2018年6月加入Linux 基金會,項目地址 https://github.com/TarsCloudgit

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以上。

相關文章
相關標籤/搜索