acecase 发表于 2022-11-14 08:08

【golang微服务】gRPC教程

gRPC教程

RPC算是近些年比较火热的概念了,随着微服务架构的兴起,RPC的应用越来越广泛。本文介绍了RPC和gRPC的相关概念,并且通过详细的代码示例介绍了gRPC的基本使用。
gRPC是什么


gRPC是一种现代化开源的高性能RPC框架,能够运行于任意环境之中。最初由谷歌进行开发。它使用HTTP/2作为传输协议。

快速了解HTTP/2就戳HTTP/2相比HTTP/1.x有哪些重大改进?

在gRPC里,客户端可以像调用本地方法一样直接调用其他机器上的服务端应用程序的方法,帮助你更容易创建分布式应用程序和服务。与许多RPC系统一样,gRPC是基于定义一个服务,指定一个可以远程调用的带有参数和返回类型的的方法。在服务端程序中实现这个接口并且运行gRPC服务处理客户端调用。在客户端,有一个stub提供和服务端相同的方法。[图片上传失败...(image-24c80c-1668139452947)]
为什么要用gRPC


使用gRPC, 我们可以一次性的在一个.proto文件中定义服务并使用任何支持它的语言去实现客户端和服务端,反过来,它们可以应用在各种场景中,从Google的服务器到你自己的平板电脑—— gRPC帮你解决了不同语言及环境间通信的复杂性。使用protocol buffers还能获得其他好处,包括高效的序列化,简单的IDL以及容易进行接口更新。总之一句话,使用gRPC能让我们更容易编写跨语言的分布式代码。

IDL(Interface description language)是指接口描述语言,是用来描述软件组件接口的一种计算机语言,是跨平台开发的基础。IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流;比如,一个组件用C++写成,另一个组件用Go写成。
安装gRPC

安装gRPC


在你的项目目录下执行以下命令,获取 gRPC 作为项目依赖。
go get google.golang.org/grpc@latest安装Protocol Buffers v3


安装用于生成gRPC服务代码的协议编译器,最简单的方法是从下面的链接:https://github.com/google/protobuf/releases下载适合你平台的预编译好的二进制文件(protoc-<version>-<platform>.zip)。
适用Windows 64位protoc-3.20.1-win64.zip适用于Mac Intel 64位protoc-3.20.1-osx-x86_64.zip适用于Mac ARM 64位protoc-3.20.1-osx-aarch_64.zip适用于Linux 64位protoc-3.20.1-linux-x86_64.zip

例如,我使用 Intel 芯片的 Mac 系统则下载 protoc-3.20.1-osx-x86_64.zip 文件,解压之后得到如下内容。

[图片上传失败...(image-197c11-1668139452947)]

其中:
bin 目录下的 protoc 是可执行文件。include 目录下的是 google 定义的.proto文件,我们import "google/protobuf/timestamp.proto"就是从此处导入。

我们需要将下载得到的可执行文件protoc所在的 bin 目录加到我们电脑的环境变量中。
安装插件


因为本文我们是使用Go语言做开发,接下来执行下面的命令安装protoc的Go插件:

安装go语言插件:
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
该插件会根据.proto文件生成一个后缀为.pb.go的文件,包含所有.proto文件中定义的类型及其序列化方法。

安装grpc插件:
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
该插件会生成一个后缀为_grpc.pb.go的文件,其中包含:
一种接口类型(或存根) ,供客户端调用的服务方法。服务器要实现的接口类型。

上述命令会默认将插件安装到$GOPATH/bin,为了protoc编译器能找到这些插件,请确保你的$GOPATH/bin在环境变量中。

protocol-buffers 官方Go教程
检查


依次执行以下命令检查一下是否开发环境都准备完毕。

[*]
确认 protoc 安装完成。
protoc --versionlibprotoc 3.20.1
[*]
确认 protoc-gen-go 安装完成。
protoc-gen-go --versionprotoc-gen-go v1.28.0
如果这里提示protoc-gen-go不是可执行的程序,请确保你的 GOPATH 下的 bin 目录在你电脑的环境变量中。
[*]
确认 protoc-gen-go-grpc 安装完成。
protoc-gen-go-grpc --versionprotoc-gen-go-grpc 1.2.0
如果这里提示protoc-gen-go-grpc不是可执行的程序,请确保你的 GOPATH 下的 bin 目录在你电脑的环境变量中。
gRPC的开发方式


把大象放进冰箱分几步?
把冰箱门打开。把大象放进去。把冰箱门带上。

gRPC开发同样分三步:
编写.proto文件定义服务


像许多 RPC 系统一样,gRPC 基于定义服务的思想,指定可以通过参数和返回类型远程调用的方法。默认情况下,gRPC 使用 protocol buffers作为接口定义语言(IDL)来描述服务接口和有效负载消息的结构。可以根据需要使用其他的IDL代替。

例如,下面使用 protocol buffers 定义了一个HelloService服务。
service HelloService {rpc SayHello (HelloRequest) returns (HelloResponse);}message HelloRequest {string greeting = 1;}message HelloResponse {string reply = 1;}
在gRPC中你可以定义四种类型的服务方法。
普通 rpc,客户端向服务器发送一个请求,然后得到一个响应,就像普通的函数调用一样。
rpc SayHello(HelloRequest) returns (HelloResponse);
服务器流式 rpc,其中客户端向服务器发送请求,并获得一个流来读取一系列消息。客户端从返回的流中读取,直到没有更多的消息。gRPC 保证在单个 RPC 调用中的消息是有序的。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
客户端流式 rpc,其中客户端写入一系列消息并将其发送到服务器,同样使用提供的流。一旦客户端完成了消息的写入,它就等待服务器读取消息并返回响应。同样,gRPC 保证在单个 RPC 调用中对消息进行排序。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
双向流式 rpc,其中双方使用读写流发送一系列消息。这两个流独立运行,因此客户端和服务器可以按照自己喜欢的顺序读写: 例如,服务器可以等待接收所有客户端消息后再写响应,或者可以交替读取消息然后写入消息,或者其他读写组合。每个流中的消息是有序的。
生成指定语言的代码


在 .proto 文件中的定义好服务之后,gRPC 提供了生成客户端和服务器端代码的 protocol buffers 编译器插件。

我们使用这些插件可以根据需要生成Java、Go、C++、Python等语言的代码。我们通常会在客户端调用这些 API,并在服务器端实现相应的 API。
在服务器端,服务器实现服务声明的方法,并运行一个 gRPC 服务器来处理客户端发来的调用请求。gRPC 底层会对传入的请求进行解码,执行被调用的服务方法,并对服务响应进行编码。在客户端,客户端有一个称为存根(stub)的本地对象,它实现了与服务相同的方法。然后,客户端可以在本地对象上调用这些方法,将调用的参数包装在适当的 protocol buffers 消息类型中—— gRPC 在向服务器发送请求并返回服务器的 protocol buffers 响应之后进行处理。
编写业务逻辑代码


gRPC 帮我们解决了 RPC 中的服务调用、数据传输以及消息编解码,我们剩下的工作就是要编写业务逻辑代码。

在服务端编写业务代码实现具体的服务方法,在客户端按需调用这些方法。
gRPC入门示例

编写proto代码


Protocol Buffers是一种与语言无关,平台无关的可扩展机制,用于序列化结构化数据。使用Protocol Buffers可以一次定义结构化的数据,然后可以使用特殊生成的源代码轻松地在各种数据流中使用各种语言编写和读取结构化数据。

关于Protocol Buffers的教程可以查看Protocol Buffers V3中文指南,本文后续内容默认读者熟悉Protocol Buffers。
syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本option go_package = "xx";// 指定生成的Go代码在你项目中的导入路径package pb; // 包名// 定义服务service Greeter {    // SayHello 方法    rpc SayHello (HelloRequest) returns (HelloResponse) {}}// 请求消息message HelloRequest {    string name = 1;}// 响应消息message HelloResponse {    string reply = 1;}编写Server端Go代码


我们新建一个hello_server项目,在项目根目录下执行go mod init hello_server。

再新建一个pb文件夹,将上面的 proto 文件保存为hello.proto,将go_package按如下方式修改。
// ...option go_package = "hello_server/pb";// ...
此时,项目的目录结构为:
hello_server├── go.mod├── go.sum├── main.go└── pb    └── hello.proto
在项目根目录下执行以下命令,根据hello.proto生成 go 源码文件。
protoc --go_out=. --go_opt=paths=source_relative \--go-grpc_out=. --go-grpc_opt=paths=source_relative \pb/hello.proto
注意 如果你的终端不支持\符(例如某些同学的Windows),那么你就复制粘贴下面不带\的命令执行。
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative pb/hello.proto
生成后的go源码文件会保存在pb文件夹下。
hello_server├── go.mod├── go.sum├── main.go└── pb    ├── hello.pb.go    ├── hello.proto    └── hello_grpc.pb.go
将下面的内容添加到hello_server/main.go中。
package mainimport (    "context"    "fmt"    "hello_server/pb"    "net"    "google.golang.org/grpc")// hello servertype server struct {    pb.UnimplementedGreeterServer}func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {    return &pb.HelloResponse{Reply: "Hello " + in.Name}, nil}func main() {    // 监听本地的8972端口    lis, err := net.Listen("tcp", ":8972")    if err != nil {      fmt.Printf("failed to listen: %v", err)      return    }    s := grpc.NewServer()                  // 创建gRPC服务器    pb.RegisterGreeterServer(s, &server{}) // 在gRPC服务端注册服务    // 启动服务    err = s.Serve(lis)    if err != nil {      fmt.Printf("failed to serve: %v", err)      return    }}
编译并执行 http_server:
go build./server编写Client端Go代码


我们新建一个hello_client项目,在项目根目录下执行go mod init hello_client。

再新建一个pb文件夹,将上面的 proto 文件保存为hello.proto,将go_package按如下方式修改。
// ...option go_package = "hello_client/pb";// ...
在项目根目录下执行以下命令,根据hello.proto在http_client项目下生成 go 源码文件。
protoc --go_out=. --go_opt=paths=source_relative \--go-grpc_out=. --go-grpc_opt=paths=source_relative \pb/hello.proto
注意 如果你的终端不支持\符(例如某些同学的Windows),那么你就复制粘贴下面不带\的命令执行。
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative pb/hello.proto
此时,项目的目录结构为:
http_client├── go.mod├── go.sum├── main.go└── pb    ├── hello.pb.go    ├── hello.proto    └── hello_grpc.pb.go
在http_client/main.go文件中按下面的代码调用http_server提供的 SayHello RPC服务。
package mainimport (    "context"    "flag"    "log"    "time"    "hello_client/pb"    "google.golang.org/grpc"    "google.golang.org/grpc/credentials/insecure")// hello_clientconst (    defaultName = "world")var (    addr = flag.String("addr", "127.0.0.1:8972", "the address to connect to")    name = flag.String("name", defaultName, "Name to greet"))func main() {    flag.Parse()    // 连接到server端,此处禁用安全传输    conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))    if err != nil {      log.Fatalf("did not connect: %v", err)    }    defer conn.Close()    c := pb.NewGreeterClient(conn)    // 执行RPC调用并打印收到的响应数据    ctx, cancel := context.WithTimeout(context.Background(), time.Second)    defer cancel()    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})    if err != nil {      log.Fatalf("could not greet: %v", err)    }    log.Printf("Greeting: %s", r.GetReply())}
保存后将http_client编译并执行:
go build./hello_client -name=七米
得到以下输出结果,说明RPC调用成功。
2022/05/15 00:31:52 Greeting: Hello 七米gRPC跨语言调用


接下来,我们演示一下如何使用gRPC实现跨语言的RPC调用。

我们使用Python语言编写Client,然后向上面使用go语言编写的server发送RPC请求。

python下安装 grpc:

闲鱼技术01 发表于 2022-11-14 08:09

用自己的思维逻辑推理,创造出奇迹!真棒
页: [1]
查看完整版本: 【golang微服务】gRPC教程