protobuffer、gRPC、restful gRPC的相互轉化

文檔

protobuf

 Google Protocol Buffer(簡稱 Protobuf)是一種輕便高效的結構化數據存儲格式,平臺無關、語言無關、可擴展,可用於通信協議和數據存儲等領域。前端

優勢

  • 平臺無關,語言無關,可擴展;
  • 提供了友好的動態庫,使用簡單;
  • 解析速度快,比對應的XML快約20-100倍;
  • 序列化數據很是簡潔、緊湊,與XML相比,其序列化以後的數據量約爲1/3到1/10。

安裝

tax -xvf protobuf-all-xxx.tar.gz
cd protobuf-xxx
./configure
make
make check
sudo make install
  • 安裝其餘庫
go get github.com/golang/protobuf/proto   // golang的protobuf庫文件

// 插件
go get github.com/golang/protobuf/protoc-gen-go  // 用於根據protobuf生成golang代碼,語法 protoc --go_out=. *.proto

語法

book/book.protogit

syntax="proto3";
package book;

// import "xxx/xx.proto"

// 出版社
message Publisher{
    required string name = 1
}  
// 書籍信息
message Book {
     required string name = 1;
    message Author {
        required string name = 1;
        required string address = 1;
    }
    required Author author = 2;

    enum BookType{
        SCIENCE = 1 ;
        LITERATURE = 2;
    }

    optional BookType type = 3;
    optional Publisher publisher = 4
}
  • syntax="proto3":指定protobuf的版本
  • package book:聲明一個報名,通常與文件目錄名相同
  • import "xxx/xx.proto":導入其餘的包,這樣你就可使用其餘的包的數據結構
  • required、optional、repeated:表示該字段是否必須填充;required表示必須指定且只能指定一個;當optional表示可選,可指定也可不指定,但不可超過一個不指定值的時候會採用空值,如string類型的字段會用字符串表示;repeated表示能夠重複,相似與編程語言中的list
  • message Author:在一個message體內定義一個message結構體
  • enum:是枚舉類型結構體
  • 數字:字段的標識符,不可重複
  • 數據類型: int3二、int6四、uint3二、uint6四、sint3二、sint6四、double、float、 string、bool、bytes、enum、message等等

在golang使用

protobuf採用以上的book.proto文件github

並使用如下命令生成go文件golang

protoc --go_out=. *.proto

在代碼中使用編程

package main

import (
    b "book"
    "github.com/golang/protobuf/proto"
)

func main(){
    ...
    // 將實例轉爲proto編碼
    var b = &b.Book{Name:"xxx", Author:b.Author{Name:"yyy"}}
    protoBook, err := proto.Marshal(b)
    ...
    // 講proto編碼轉化爲實例
    var b2 b.Book
    err = proto.Unmarshal(protoBook, &b2)    
    ...
}

grpc

gRPC是由Google主導開發的RPC框架,使用HTTP/2協議並用ProtoBuf做爲序列化工具。其客戶端提供Objective-C、Java接口,服務器側則有Java、Golang、C++等接口。使用grpc能夠方便的調用其餘進程的方法,調用須要傳輸的數據使用的是proto編碼。這對於大型項目來講,能夠有效的提升數據的解編碼效率和數據傳輸率。json

proto service定義

一個RPC service就是一個可以經過參數和返回值進行遠程調用的method,咱們能夠簡單地將它理解成一個函數。由於gRPC是經過將數據編碼成protocal buffer來實現傳輸的。所以,咱們經過protocal buffers interface definitioin language(IDL)來定義service method,同時將參數和返回值也定義成protocal buffer message類型。具體實現以下所示,包含下面代碼的文件叫helloworld.proto:api

syntax = "proto3";
 
 package helloworld;
 
// The greeter 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;
}

接着,根據上述定義的service,咱們能夠利用protocal buffer compiler ,即protoc生成相應的服務器端和客戶端的GoLang代碼。生成的代碼中包含了客戶端可以進行RPC的方法以及服務器端須要進行實現的接口。服務器

假設如今所在的目錄是$GOPATH/src/helloworld/helloworld,咱們將經過以下命令生成gRPC對應的GoLang代碼:restful

protoc --go_out=plugins=grpc:. helloworld.proto

此時,將在目錄下生成helloworld.pb.go文件數據結構

server

server.go

package main
 
// server.go
 
import (
    "log"
    "net"
 
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "helloworld/helloworld"
)
 
const (
    port = ":50051"
)
 
type server struct {}

// 當接收到請求的時候回調用該方法
// 參數由grpc本身根據請求進行構造 
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
 
func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatal("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    s.Serve(lis)
}

其中pb是咱們剛纔根據proto生成的go文件的包

client

package main
 
//client.go
 
import (
    "log"
    "os"
 
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "helloworld/helloworld"
)
 
const (
    address     = "localhost:50051"
    defaultName = "world"
)
 
func main() {
    // 創建一個grpc鏈接
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatal("did not connect: %v", err)
    }
    defer conn.Close()
    // 新建一個客戶端,方法爲:NewXXXClinent(conn),XXX爲你在proto定義的服務的名字
    c := pb.NewGreeterClient(conn)
 
    name := defaultName
    if len(os.Args) >1 {
        name = os.Args[1]
    }
    // 調用遠程,並獲得返回
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatal("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}

restful轉grpc

使用grpc的優勢不少,二進制的數據能夠加快傳輸速度,基於http2的多路複用能夠減小服務之間的鏈接次數,和函數同樣的調用方式也有效的提高了開發效率。不過使用grpc也會面臨一個問題,咱們的微服務對外必定是要提供Restful接口的,若是內部調用使用grpc,在某些狀況下要同時提供一個功能的兩套API接口,這樣就不只下降了開發效率,也增長了調試的複雜度。因而就想着有沒有一個轉換機制,讓Restful和gprc能夠相互轉化。

grpc-gateway應運而生

安裝

首先你得要根據本文以前的步驟安裝proto和grpc,而後以下安裝一些庫

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u github.com/golang/protobuf/protoc-gen-go

用法

定義service的proto文件

syntax = "proto3";
 package example;

 import "google/api/annotations.proto";

 message StringMessage {
   string value = 1;
 }
 
 service YourService {
   rpc Echo(StringMessage) returns (StringMessage) {
     option (google.api.http) = {
       post: "/v1/example/echo"
       body: "*"
     };
   }
 }

option 表示處理哪些path的請求以及如何處理請求體(參數),見https://cloud.google.com/serv...

生成go文件

protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --go_out=plugins=grpc:. \
  path/to/your_service.proto

protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --grpc-gateway_out=logtostderr=true:. \
  path/to/your_service.proto

以上生成的兩個文件,第一個是pb.go文件,給grpc server用的;第二個是pb.gw.go文件,給grpc-gateway用的,用於grpc和restful的相互轉化

服務器

package main

import (
  "flag"
  "net/http"

  "github.com/golang/glog"
  "golang.org/x/net/context"
  "github.com/grpc-ecosystem/grpc-gateway/runtime"
  "google.golang.org/grpc"
    
  gw "path/to/your_service_package"
)

var (
  echoEndpoint = flag.String("echo_endpoint", "localhost:9090", "endpoint of YourService")
)

func run() error {
  ctx := context.Background()
  ctx, cancel := context.WithCancel(ctx)
  defer cancel()

  mux := runtime.NewServeMux()
  opts := []grpc.DialOption{grpc.WithInsecure()}
  err := gw.RegisterYourServiceHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
  if err != nil {
    return err
  }

  return http.ListenAndServe(":8080", mux)
}

func main() {
  flag.Parse()
  defer glog.Flush()

  if err := run(); err != nil {
    glog.Fatal(err)
  }
}

測試

curl -X POST -k http://localhost:8080/v1/example/echo -d '{"name": " world"}

{"message":"Hello  world"}

流程以下:curl用post向gateway發送請求,gateway做爲proxy將請求轉化一下經過grpc轉發給greeter_server,greeter_server經過grpc返回結果,gateway收到結果後,轉化成json返回給前端。

相關文章
相關標籤/搜索