What is gRPC: gRPC,顧名思義, Google遠程過程調用。這是Google建立的一種遠程通訊協議,可以讓不一樣的服務輕鬆高效地相互通訊。它提供與服務之間的同步和異步通訊。要了解有關gRPC的更多信息,請訪問 gRPC.iolinux
gRPC最適合內部通訊。它使客戶調用變得更加簡潔,咱們無需擔憂序列化,類型安全以及全部這些事情,由於gRPC爲咱們作到了這一點。git
gPRC使用protobuf,一種類型安全的二進制傳輸格式,旨在實現有效的網絡通訊。要了解有關protobuf的更多信息,請訪問此連接。github
性能基準測試結果代表,若是開發人員須要性能和本地調用體驗,則gRPC比http/http2更好。具體測評細節查看該文章。golang
咱們選擇Golang(也稱爲Go)做爲此服務的編程語言,選擇gRPC做爲其餘服務的通訊協議,以與咱們的服務進行對話,並使用通過驗證的OAuth 2.0協議上的OpenId身份層來保護咱們的服務。編程
爲此,首先咱們須要在gRPC中建立一個簡單的實體表示形式,稱爲message。用gRPC術語表示的消息能夠用做從另外一個服務到一個服務的消息(使用protobuf語法定義的消息)。您能夠想象一隻鴿子從一個承載「冬天來了」消息的服務到另外一個服務,而該服務正在消費該消息來執行上下文操做。小程序
如今,在上面的示例中,發送鴿子的服務是gRPC客戶端,「冬天來了」是咱們的消息,而使用該消息的服務是gRPC服務器在偵聽該消息。關於消息的好處是它能夠來回傳送。windows
message Repository { int64 id = 1; string name = 2; int64 userId = 3; bool isPrivate = 4; }
如今咱們已經建立了一個名爲存儲庫的message以用於通訊,下一步是定義gRPC服務。安全
service RepositoryService { //For now we'll try to implement "insert" operation. rpc add (Repository) returns (AddRepositoryResponse); } message AddRepositoryResponse { Repository addedRepository = 1; Error error = 2; } message Error { string code = 1; string message = 2; }
在這裏,咱們告訴gRPC編譯器,以「service」關鍵字開頭的代碼段應被視爲gRPC服務。帶有「rpc」關鍵字的方法表示它是一個遠程過程調用,而且編譯器應爲客戶端和服務器運行時生成適當的存根。服務器
咱們還定義了2條消息,告訴鴿子在執行操做後返回成功響應或錯誤響應。網絡
我假設您已經安裝了Go運行時。若是不這樣作,請按照其官方文檔中的步驟進行操做,網址爲 https://golang.org/doc/instal...
咱們還將使用dep做爲咱們項目的依賴管理工具。 Dep是用於管理golang項目中外部依賴關係的成熟解決方案。咱們使用dep是由於還沒有正式發佈Go模塊支持。
若是您是Windows用戶,則將dep安裝的路徑放在環境的PATH變量中。這使您更容易使用,而無需指定可執行文件的完整路徑便可使用它。
安裝Go運行時後,請執行如下步驟。
在 $GOPATH/src 中建立名爲 bitbucket-repository-management-service」的目錄。
而後在目錄中設置標準子包,整個項目架構具體以下:
導航到項目的根目錄並執行如下命令
在「proto-files」文件夾中,建立兩個子文件夾:
所以最終項目的程序架構佈局將以下所示。
接下來,咱們將如下代碼粘貼到名爲「repository.proto」的文件中。此代碼定義了用protobuf語法編寫的框架消息,該消息將在grpc客戶端和服務器之間交換。
syntax = "proto3"; package domain; option go_package = "bitbucket-repository-management-service/internal/gRPC/domain"; message Repository { int64 id = 1; string name = 2; int64 userId = 3; bool isPrivate = 4; }
以後,咱們將下面的代碼粘貼到名爲「repository-service.proto」的文件中。該代碼定義了grpc服務定義。它定義了grpc服務器將支持的操做以及可能的輸入和返回類型。
syntax = "proto3"; package service; option go_package = "bitbucket-repository-management-service/internal/gRPC/service"; import "bitbucket-repository-management-service/internal/proto-files/domain/repository.proto"; //RepositoryService Definition service RepositoryService { rpc add (domain.Repository) returns (AddRepositoryResponse); } message AddRepositoryResponse { domain.Repository addedRepository = 1; Error error = 2; } message Error { string code = 1; string message = 2; }
若是在咱們的系統中未安裝gRPC編譯器,咱們將沒法生成存根。
要安裝協議編譯器,
沒有Go綁定,咱們的存根就沒有用了。 Go綁定提供了輔助結構,接口和函數,可用於註冊gRPC服務,封送和解封二進制消息等。
爲此,咱們首先須要將很是簡單的Go代碼添加到咱們的server.go文件中,由於默認狀況下,若是項目中沒有go代碼,則dep(咱們的依賴性管理工具)不會下載任何庫。
爲了知足dep的要求,咱們將一些很是基本的go代碼放入cmd/gRPC/main.go文件。
package main import "fmt" func main() { fmt.Println("gRPC In Action!") }
如今,咱們均可覺得原型緩衝區安裝go綁定了。咱們將執行如下命令進行安裝。
Linux
dep ensure --add google.golang.org/gRPC/github.com/golang/protobuf/protoc-gen-go
Windows
dep.exe ensure -add google.golang.org/gRPC github.com/golang/protobuf/protoc-gen-go
上面的命令會將go綁定下載到「vendor」文件夾中。
如今該生成存根了。
若是您在Windows上,請執行此命令。
protoc.exe -I $env:GOPATH\src --go_out=$env:GOPATH\src $env:GOPATH\src\bitbucket-repository-management-service\internal\proto-files\domain\repository.proto protoc.exe -I $env:GOPATH\src --go_out=plugins=gRPC:$env:GOPATH\src $env:GOPATH\src\bitbucket-repository-management-service\internal\proto-files\service\repository-service.proto
若是您在Linux上,請執行此命令。
protoc -I $GOPATH/src --go_out=$GOPATH/src $GOPATH/src/bitbucket-repository-management-service/internal/proto-files/domain/repository.proto protoc -I $GOPATH/src --go_out=plugins=gRPC:$GOPATH/src $GOPATH/src/bitbucket-repository-management-service/internal/proto-files/service/repository-service.proto
上面的命令將在如下標記的子目錄中生成存根。
接下來,編寫咱們本身的服務實現,
所以,咱們知道咱們的gRPC服務有一個稱爲add的方法。在此過程的早期,咱們將其定義寫入了原始文件中。
rpc add (domain.Repository) returns (AddRepositoryResponse);
爲了實現它的服務契約,咱們將首先聲明一個負責RepositoryService實現的結構。
package impl import ( "bitbucket-repository-management-service/internal/gRPC/domain" "bitbucket-repository-management-service/internal/gRPC/service" "context" "log" ) //RepositoryServiceGrpcImpl is a implementation of RepositoryService Grpc Service. type RepositoryServiceGrpcImpl struct { } //NewRepositoryServiceGrpcImpl returns the pointer to the implementation. func NewRepositoryServiceGrpcImpl() *RepositoryServiceGrpcImpl { return &RepositoryServiceGrpcImpl{} } //Add function implementation of gRPC Service. func (serviceImpl *RepositoryServiceGrpcImpl) Add(ctx context.Context, in *domain.Repository) (*service.AddRepositoryResponse, error) { log.Println("Received request for adding repository with id " + strconv.FormatInt(in.Id, 10)) //Logic to persist to database or storage. log.Println("Repository persisted to the storage") return &service.AddRepositoryResponse{ AddedRepository: in, Error: nil, }, nil }
如今是時候編寫服務器配置,端口配置和最小的測試客戶端了,咱們能夠執行這些操做來驗證整個流程。
讓咱們先從gRPC服務器開始。
咱們將建立一個RepositoryServiceGrpcImpl的實例。repositoryServiceImpl:= impl.NewRepositoryServiceGrpcImpl()
咱們將建立net.Listener:
func getNetListener(port uint) net.Listener { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { log.Fatalf("failed to listen: %v", err) panic(fmt.Sprintf("failed to listen: %v", err)) } return lis }
建立gRPC server:
gRPCServer := gRPC.NewServer()
咱們將服務實現註冊到gRPC服務器。
service.RegisterRepositoryServiceServer(gRPCServer, repositoryServiceImpl)
咱們將綁定net.Listener和gRPC服務器,以使其從指定端口進行通訊。
// start the server if err := gRPCServer.Serve(netListener); err != nil { log.Fatalf("failed to serve: %s", err) }
若是咱們把全部東西都鏈接起來,咱們將獲得如下內容:
package main import ( "bitbucket-repository-management-service/internal/gRPC/impl" "bitbucket-repository-management-service/internal/gRPC/service" "fmt" "log" "net" "google.golang.org/gRPC" ) func main() { netListener := getNetListener(7000) gRPCServer := gRPC.NewServer() repositoryServiceImpl := impl.NewRepositoryServiceGrpcImpl() service.RegisterRepositoryServiceServer(gRPCServer, repositoryServiceImpl) // start the server if err := gRPCServer.Serve(netListener); err != nil { log.Fatalf("failed to serve: %s", err) } } func getNetListener(port uint) net.Listener { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { log.Fatalf("failed to listen: %v", err) panic(fmt.Sprintf("failed to listen: %v", err)) } return lis }
配置gRPC Client
要配置客戶端:
咱們將建立與gRPC服務器的鏈接。
serverAddress := "localhost:7000" conn, e := gRPC.Dial(serverAddress, gRPC.WithInsecure())
咱們將把該鏈接傳遞給gRPC客戶端。
client := service.NewRepositoryServiceClient(conn)
調用gRPC方法:
client.Add(context.Background(), &repositoryModel);
若是咱們在這裏也鏈接起來,它將像:
package main import ( "bitbucket-repository-management-service/internal/gRPC/domain" "bitbucket-repository-management-service/internal/gRPC/service" "context" "fmt" "google.golang.org/gRPC" ) func main() { serverAddress := "localhost:7000" conn, e := gRPC.Dial(serverAddress, gRPC.WithInsecure()) if e != nil { panic(e) } defer conn.Close() client := service.NewRepositoryServiceClient(conn) for i := range [10]int{} { repositoryModel := domain.Repository{ Id: int64(i), IsPrivate: true, Name: string("Grpc-Demo"), UserId: 1245, } if responseMessage, e := client.Add(context.Background(), &repositoryModel); e != nil { panic(fmt.Sprintf("Was not able to insert Record %v", e)) } else { fmt.Println("Record Inserted..") fmt.Println(responseMessage) fmt.Println("=============================") } } }
測試
要運行gRPC服務器,請從項目的根目錄執行如下命令。
go run .\cmd\gRPC\server\main.go
運行客戶端:
go run .\cmd\gRPC\client\main.go
您應該在客戶端的標準輸出流上看到相似的內容。
在服務端應該能夠看到以下的內容:
咱們建立了一個最小程序,並考慮了gRPC請求響應的最佳實踐。一方面,咱們的gRPC服務器正在偵聽和處理請求,另外一方面,客戶端正在向服務器發送請求。咱們正在使用自定義消息來往/從gRPC服務器/客戶端傳遞消息。
咱們上面的實現是同步的。咱們還沒有解決服務器的異步響應和流式處理。