Google Protocol Buffer(簡稱 Protobuf)是一種輕便高效的結構化數據存儲格式,平臺無關、語言無關、可擴展,可用於通信協議和數據存儲等領域。前端
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 }
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是由Google主導開發的RPC框架,使用HTTP/2協議並用ProtoBuf做爲序列化工具。其客戶端提供Objective-C、Java接口,服務器側則有Java、Golang、C++等接口。使用grpc能夠方便的調用其餘進程的方法,調用須要傳輸的數據使用的是proto編碼。這對於大型項目來講,能夠有效的提升數據的解編碼效率和數據傳輸率。json
一個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.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文件的包
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) }
使用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
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返回給前端。