gRPC实战--用Golang编写经过gRPC进行通讯的服务

What is gRPC: gRPC,顾名思义, Google远程过程调用。这是Google建立的一种远程通讯协议,可以让不一样的服务轻松高效地相互通讯。它提供与服务之间的同步和异步通讯。要了解有关gRPC的更多信息,请访问 gRPC.iolinux

gRPC最适合内部通讯。它使客户调用变得更加简洁,咱们无需担忧序列化,类型安全以及全部这些事情,由于gRPC为咱们作到了这一点。git

gPRC使用protobuf,一种类型安全的二进制传输格式,旨在实现有效的网络通讯。要了解有关protobuf的更多信息,请访问此连接github

性能基准测试结果代表,若是开发人员须要性能和本地调用体验,则gRPC比http/http2更好。具体测评细节查看该文章。golang

使用Golang构建微服务

咱们选择Golang(也称为Go)做为此服务的编程语言,选择gRPC做为其余服务的通讯协议,以与咱们的服务进行对话,并使用通过验证的OAuth 2.0协议上的OpenId身份层来保护咱们的服务。编程

建立Message

为此,首先咱们须要在gRPC中建立一个简单的实体表示形式,称为message。用gRPC术语表示的消息能够用做从另外一个服务到一个服务的消息(使用protobuf语法定义的消息)。您能够想象一只鸽子从一个承载“冬天来了”消息的服务到另外一个服务,而该服务正在消费该消息来执行上下文操做。小程序

如今,在上面的示例中,发送鸽子的服务是gRPC客户端,“冬天来了”是咱们的消息,而使用该消息的服务是gRPC服务器在侦听该消息。关于消息的好处是它能够来回传送。windows

message Repository {
    int64 id  = 1;
    string name = 2;
    int64 userId = 3;
    bool isPrivate = 4;
}

定义 gRPC 服务

如今咱们已经建立了一个名为存储库的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条消息,告诉鸽子在执行操做后返回成功响应或错误响应。网络

为Golang服务建立文件夹结构

我假设您已经安装了Go运行时。若是不这样作,请按照其官方文档中的步骤进行操做,网址为 https://golang.org/doc/instal...

咱们还将使用dep做为咱们项目的依赖管理工具。 Dep是用于管理golang项目中外部依赖关系的成熟解决方案。咱们使用dep是由于还没有正式发布Go模块支持。

若是您是Windows用户,则将dep安装的路径放在环境的PATH变量中。这使您更容易使用,而无需指定可执行文件的完整路径便可使用它。

安装Go运行时后,请执行如下步骤。

$GOPATH/src 中建立名为 bitbucket-repository-management-service”的目录。
而后在目录中设置标准子包,整个项目架构具体以下:

g01.jpg

  • 导航到项目的根目录并执行如下命令

    • 若是是windows系统, "dep.exe init"
    • 若是是linux系统, "dep init"
  • 上面的命令将建立一个名为“vendor”的文件夹以及“Gopkg.lock”和“ Gopkg.toml”文件。这两个文件对于管理咱们项目的不一样依赖关系很重要。
  • 咱们的下一步是将原型文件放入“internal”文件夹,由于这些文件严格绑定到咱们的应用程序。之后,若是咱们想使用不一样的编程语言将相同的文件用于不一样的服务,则将为此建立一个单独的存储库。可是为了简单起见,咱们如今将它们放在同一目录中。
  • 以下图所示,在“内部”包中建立名为“proto-files”的文件夹。
  • 在“proto-files”文件夹中,建立两个子文件夹:

    • domain
    • service

所以最终项目的程序架构布局将以下所示。

g02.png

接下来,咱们将如下代码粘贴到名为“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编译器

若是在咱们的系统中未安装gRPC编译器,咱们将没法生成存根。

要安装协议编译器,

  • 导航到此连接
  • 选择最新版本的标签,请确保选择一个稳定的版本。
  • 下载适合您的操做系统的二进制文件。
  • 下载后,将其解压缩到操做系统的path变量正在扫描的位置。

安装Go绑定并生成存根

没有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!")
}

g03.png

如今,咱们均可觉得原型缓冲区安装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

上面的命令将在如下标记的子目录中生成存根。

g04.png

实现 gRPC Service Stub

接下来,编写咱们本身的服务实现,

  • 咱们将在“internal”目录中建立一个名为“impl”的软件包。
  • 咱们将建立一个名为RepositoryServiceGrpcImpl的结构,
  • 确保咱们的结构实现了全部gRPC存根方法。

所以,咱们知道咱们的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服务器开始。

配置 gRPC Server

咱们将建立一个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
}

g05.png

配置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("=============================")
        }
    }
}

g06.png

测试

要运行gRPC服务器,请从项目的根目录执行如下命令。

go run .\cmd\gRPC\server\main.go

运行客户端:

go run .\cmd\gRPC\client\main.go

您应该在客户端的标准输出流上看到相似的内容。

g07.png

在服务端应该能够看到以下的内容:

g08.png

总结

咱们建立了一个最小程序,并考虑了gRPC请求响应的最佳实践。一方面,咱们的gRPC服务器正在侦听和处理请求,另外一方面,客户端正在向服务器发送请求。咱们正在使用自定义消息来往/从gRPC服务器/客户端传递消息。

咱们上面的实现是同步的。咱们还没有解决服务器的异步响应和流式处理。

相关文章
相关标签/搜索