protobuffer、gRPC、restful gRPC的相互轉化


轉自:https://studygolang.com/articles/12510

文檔

protobuf

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

優點

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

安裝

參考 golang使用protobuf

https://github.com/google/protobuf/releases // 下載並編譯、安裝
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.proto

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:是枚舉類型結構體
  • 數字:字段的標識符,不可重復
  • 數據類型: int32、int64、uint32、uint64、sint32、sint64、double、float、 string、bool、bytes、enum、message等等

在golang使用

protobuf采用以上的book.proto文件

並使用以下命令生成go文件

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編碼。這對於大型項目來說,可以有效的提高數據的解編碼效率和數據傳輸率。

proto service定義

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

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代碼:

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返回給前端。

 

Grpc+Grpc Gateway實踐

https://segmentfault.com/a/1190000013339403

假定我們有一個項目需求,希望用Rpc作為內部API的通訊,同時也想對外提供Restful Api,寫兩套又太繁瑣不符合

 

於是我們想到了Grpc以及Grpc Gateway,這就是我們所需要的

 

介紹與環境安裝

准備環節

在正式開始我們的Grpc+Grpc Gateway實踐前,我們需要先配置好我們的開發環境

  • Grpc
  • Protoc Plugin
  • Protocol Buffers
  • Grpc-gateway

Grpc

是什么

Google對Grpc的定義:

A high performance, open-source universal RPC framework

也就是Grpc是一個高性能、開源的通用RPC框架,具有以下特性:

  • 強大的IDL,使用Protocol Buffers作為數據交換的格式,支持v2v3(推薦v3
  • 跨語言、跨平台,也就是Grpc支持多種平台和語言
  • 支持HTTP2,雙向傳輸、多路復用、認證等

安裝

1、官方推薦(需科學上網)

go get -u google.golang.org/grpc

2、通過github.com

進入到第一個$GOTPATH目錄(因為go get 會默認安裝在第一個下)下,新建google.golang.org目錄,拉取golanggithub上的鏡像庫:

cd /usr/local/go/path/src   

mkdir google.golang.org

cd google.golang.org/

git clone https://github.com/grpc/grpc-go

mv grpc-go/ grpc/

目錄結構:

google.golang.org/
└── grpc
    ...

而在grpc下有許多常用的包,例如:

  • metadata:定義了grpc所支持的元數據結構,包中方法可以對MD進行獲取和處理
  • credentials:實現了grpc所支持的各種認證憑據,封裝了客戶端對服務端進行身份驗證所需要的所有狀態,並做出各種斷言
  • codes:定義了grpc使用的標准錯誤碼,可通用

Protoc Plugin

是什么

編譯器插件

安裝

go get -u github.com/golang/protobuf/protoc-gen-go

Protoc Plugin的可執行文件從$GOPATH中移動到$GOBIN下

mv /usr/local/go/path/bin/protoc-gen-go /usr/local/go/bin/

Protocol Buffers v3

是什么

Protocol buffers are a flexible, efficient, automated mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages. You can even update your data structure without breaking deployed programs that are compiled against the "old" format.

Protocol BuffersGoogle推出的一種數據描述語言,支持多語言、多平台,它是一種二進制的格式,總得來說就是更小、更快、更簡單、更靈活,目前分別有v2v3的版本,我們推薦使用v3

建議可以閱讀下官方文檔的介紹,本系列會在使用時簡單介紹所涉及的內容

安裝

wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.zip
unzip protobuf-all-3.5.1.zip
cd protobuf-3.5.1/
./configure
make
make install

檢查是否安裝成功

protoc --version

如果出現報錯

protoc: error while loading shared libraries: libprotobuf.so.15: cannot open shared object file: No such fileor directory

則執行ldconfig后,再次運行即可成功

為什么要執行ldconfig

我們通過控制台輸出的信息可以知道,Protocol Buffers Libraries的默認安裝路徑在/usr/local/lib

Libraries have been installed in:
   /usr/local/lib

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the `LD_LIBRARY_PATH' environment variable
     during execution
   - add LIBDIR to the `LD_RUN_PATH' environment variable
     during linking
   - use the `-Wl,-rpath -Wl,LIBDIR' linker flag
   - have your system administrator add LIBDIR to `/etc/ld.so.conf'

See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.

而我們安裝了一個新的動態鏈接庫,ldconfig一般在系統啟動時運行,所以現在會找不到這個lib,因此我們要手動執行ldconfig讓動態鏈接庫為系統所共享,它是一個動態鏈接庫管理命令,這就是ldconfig命令的作用

protoc使用

我們按照慣例執行protoc --help(查看幫助文檔),我們抽出幾個常用的命令進行講解

1、-IPATH, --proto_path=PATH:指定import搜索的目錄,可指定多個,如果不指定則默認當前工作目錄

2、--go_out:生成golang源文件

參數

若要將額外的參數傳遞給插件,可使用從輸出目錄中分離出來的逗號分隔的參數列表:

protoc --go_out=plugins=grpc,import_path=mypackage:. *.proto
  • import_prefix=xxx:將指定前綴添加到所有import路徑的開頭
  • import_path=foo/bar:如果文件沒有聲明go_package,則用作包。如果它包含斜杠,那么最右邊的斜杠將被忽略。
  • plugins=plugin1+plugin2:指定要加載的子插件列表(我們所下載的repo中唯一的插件是grpc)
  • Mfoo/bar.proto=quux/shme: M參數,指定.proto文件編譯后的包名(foo/bar.proto編譯后為包名為quux/shme

Grpc支持

如果proto文件指定了RPC服務,protoc-gen-go可以生成與grpc相兼容的代碼,我們僅需要將plugins=grpc參數傳遞給--go_out,就可以達到這個目的

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

Grpc-gateway

是什么

grpc-gateway is a plugin of protoc. It reads gRPC service definition, and generates a reverse-proxy server which translates a RESTful JSON API into gRPC. This server is generated according to custom options in your gRPC definition.

grpc-gateway是protoc的一個插件。它讀取gRPC服務定義,並生成一個反向代理服務器,將RESTful JSON API轉換為gRPC。此服務器是根據gRPC定義中的自定義選項生成的。

安裝

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

如果出現以下報錯,我們分析錯誤提示可得知是連接超時(大概是被牆了)

package google.golang.org/genproto/googleapis/api/annotations: unrecognized import path "google.golang.org/genproto/googleapis/api/annotations" (https fetch: Get https://google.golang.org/genproto/googleapis/api/annotations?go-get=1: dial tcp 216.239.37.1:443: getsockopt: connection timed out)

有兩種解決方法,

1、科學上網

2、通過github.com

進入到第一個$GOPATH目錄的google.golang.org目錄下,拉取genprotogithub上的go-genproto鏡像庫:

cd /usr/local/go/path/src/google.golang.org

git clone https://github.com/google/go-genproto.git

mv go-genproto/ genproto/

在安裝完畢后,我們將grpc-gateway的可執行文件從$GOPATH中移動到$GOBIN

mv /usr/local/go/path/bin/protoc-gen-grpc-gateway /usr/local/go/bin/

到這里我們這節就基本完成了,建議多反復看幾遍加深對各個組件的理解!

參考

示例代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM