What is gRPC: gRPC,顾名思义, Google远程过程调用。这是Google创建的一种远程通信协议,可让不同的服务轻松高效地相互通信。它提供与服务之间的同步和异步通信。要了解有关gRPC的更多信息,请访问 gRPC.io
gRPC最适合内部通信。它使客户调用变得更加简洁,我们无需担心序列化,类型安全以及所有这些事情,因为gRPC为我们做到了这一点。
gPRC使用protobuf,一种类型安全的二进制传输格式,旨在实现有效的网络通信。要了解有关protobuf的更多信息,请访问此链接。
性能基准测试结果表明,如果开发人员需要性能和本地调用体验,则gRPC比http/http2更好。具体测评细节查看该文章。
我们选择Golang(也称为Go)作为此服务的编程语言,选择gRPC作为其他服务的通信协议,以与我们的服务进行对话,并使用经过验证的OAuth 2.0协议上的OpenId身份层来保护我们的服务。
为此,首先我们需要在gRPC中创建一个简单的实体表示形式,称为message。用gRPC术语表示的消息可以用作从另一个服务到一个服务的消息(使用protobuf语法定义的消息)。您可以想象一只鸽子从一个承载“冬天来了”消息的服务到另一个服务,而该服务正在消费该消息来执行上下文操作。
现在,在上面的示例中,发送鸽子的服务是gRPC客户端,“冬天来了”是我们的消息,而使用该消息的服务是gRPC服务器在侦听该消息。关于消息的好处是它可以来回传送。
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服务器/客户端传递消息。
我们上面的实现是同步的。我们尚未解决服务器的异步响应和流式处理。