Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——高性能的 gRPC(实践案例)

远程过程调用 RPC——高性能的 gRPC(实践案例)

  gRPC,这一由Google推出的高性能、开源、通用RPC框架,凭借其众多引人注目的特性,已成为业界瞩目的焦点。它基于HTTP/2协议标准设计开发,并采用Protocol Buffers作为默认的数据序列化协议,广泛支持多种编程语言。gRPC不仅简化了服务的精确定义,而且还能为客户端和服务端自动生成可靠的代码库,从而极大地提升了开发效率。

现在,让我们一起来深入了解gRPC的卓越特性:

      • 语言无关性:gRPC展示了出色的多语言兼容性,它不受特定编程语言的限制。无论是C++, Java, Python, Go, Ruby, C#, Node.js,还是Objective-CgRPC都能轻松集成,为跨语言开发提供了极大的便利。

      • HTTP/2的优势:通过采用HTTP/2作为其核心传输协议,gRPC得以充分利用该协议的多路复用、流量控制和服务器推送等高级功能,从而确保了数据传输的高效性和稳定性。

      • 双向流式通信:gRPC支持双向流式传输模式,这意味着客户端和服务端能够建立持久连接,实现数据的实时读写,为需要持续数据交换的应用提供了强大的支持。

      • Protocol Buffers的集成:gRPC与Google的Protocol Buffers无缝集成,后者作为一种高效的数据序列化协议,不仅确保了数据的轻量化和快速传输,还为服务的精确定义提供了强大的工具。通过Protocol Buffers编译器,开发者可以轻松生成客户端和服务端的代码,极大地简化了开发流程。

      • 插件化的扩展性:gRPC支持各种插件,以实现身份验证、调用跟踪、健康检查以及负载均衡等关键功能,为构建健壮、可伸缩的服务提供了坚实的基础。

      • 跨平台与跨网络兼容性:gRPC的设计考虑了跨平台和跨网络的需求,确保了在不同的系统和网络环境中都能保持出色的性能和稳定性。

  gRPC凭借其高性能、灵活性和易用性,已成为微服务架构、云原生应用和分布式系统等领域的首选RPC框架。

一、gRPC 的安装

首先使用 go get 命令安装 gRPC-go:

PS D:\worker-go\gRPC> go get -u google.golang.org/gRPC
go: downloading google.golang.org/gRPC v1.64.0
go: downloading golang.org/x/net v0.22.0
go: downloading golang.org/x/sys v0.18.0
go: downloading google.golang.org/protobuf v1.34.1
go: downloading golang.org/x/net v0.25.0
go: downloading golang.org/x/sys v0.20.0
go: downloading google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237
go: downloading google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8
go: downloading google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8
go: downloading golang.org/x/text v0.15.0
go: added golang.org/x/net v0.25.0
go: added golang.org/x/sys v0.20.0
go: added golang.org/x/text v0.15.0
go: added google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8
go: added google.golang.org/gRPC v1.64.0
go: added google.golang.org/protobuf v1.34.1
PS D:\worker-go\gRPC> 

二、实践案例:gRPC过程调用实践

gRPC过程调用时,服务端和客户端需要依赖共同的proto文件。proto文件可以定义远程调用的接口、方法名、参数和返回值等。通过proto文件可以自动生成客户端和客户端的相应RPC代码。借助这些代码,客户端可以十分方便地发送RPC请求,并且服务端也可以很简单地建立RPC服务器,处理RPC请求并且将返回值作为响应发送给客户端。

2.1、定义和编译 proto 文件

  首先,我们要定义一个proto文件,其具体语法查看Protobuf3(https://protobuf.dev/programming-guides/proto3/)语言指南。在该文件中,我们定义了两个参数结果,分别是 StringRequestStringResponse,同时还有一个服务结构StringService,代码如下:

syntax = "proto3"; // 使用Protocol Buffers的proto3语法
  
package pb; // 定义包名为pb
  
// StringService 服务定义,提供字符串操作服务
service StringService {
  rpc Concat(StringRequest) returns (StringResponse) {} // 定义一个连接字符串的RPC方法
  rpc Diff(StringRequest) returns (StringResponse) {}   // 定义一个比较字符串差异的RPC方法
}  
  
// StringRequest 消息包含用户输入的两个字符串值  
message StringRequest {  
  string A = 1; // 第一个字符串  
  string B = 2; // 第二个字符串  
}  
  
// StringResponse 消息包含操作后的响应字符串  
// 注意:字段名不能重复,下面的Ret字段需要修改  
message StringResponse {  
  string Ret1 = 1; // 第一个响应字符串,例如Concat的结果  
  string Ret2 = 2; // 第二个响应字符串,例如Diff的结果或附加信息  
}

  一个Protocol Buffers (Protobuf)的.proto文件,它定义了一个服务(StringService)以及两个消息类型(StringRequestStringResponse)。下面是对这段代码的详细分析:

2.1.1、语法声明

syntax = "proto3";

这行代码指定了使用 Buffersproto3语法。Proto3Protobuf的一个版本,它简化了字段规则和默认值,并且移除了proto2中的一些复杂性和冗余。

2.1.2、包声明

package pb;

这里定义了一个包名pb,它用于防止命名冲突,并允许Protobuf消息在不同的项目中重用。

2.1.3、服务定义

service StringService {  
  rpc Concat(StringRequest) returns (StringResponse) {}  
  rpc Diff(StringRequest) returns (StringResponse) {}  
}

  定义了一个名为StringService的服务,该服务包含两个RPC方法:ConcatDiff。这两个方法都接受StringRequest作为输入,并返回StringResponse。RPC(远程过程调用)允许客户端调用服务器上的方法,就像调用本地函数一样。

2.1.4、消息定义

2.1.4.1、StringRequest 
protobuf`message StringRequest { string A = 1; string B = 2; }`

  这个消息类型包含两个字符串字段:AB,分别用数字1和2进行标识。这些数字是字段的标识符,用于在序列化和反序列化过程中识别字段。它们也确保了向后兼容性,即使在未来添加或删除字段时也是如此。

2.1.4.2、StringResponse
protobuf`message StringResponse { string Ret1 = 1; string Ret2 = 2; }`

  这个消息类型包含两个字符串字段:Ret1Ret2,分别用数字1和2进行标识。如注释所述,Ret1可能用于表示Concat方法的结果,而Ret2可能包含Diff方法的结果或附加信息。重要的是要注意,字段名在消息内部必须是唯一的,以避免混淆和错误。

gRPC支持四种不同类型的服务接口,每种都有其特定的使用场景。下面我将解释每种类型,并提供相应的代码示例:

2.1.5、一元RPC(是指客户端向服务器发送请求并获得响应,就像正常的函数调用一样。)

在一元RPC中,客户端发送一个请求给服务器,然后服务器返回一个响应。这是最简单的RPC类型。在上面的proto文件中,ConcatDiff方法就是一元RPC的例子:

// StringService 服务定义,提供字符串操作服务  
service StringService {  
  rpc Concat(StringRequest) returns (StringResponse) {} // 一元RPC示例:连接字符串  
  // ... 其他方法 ...  
}

2.1.6、服务器RPC(Server-streaming RPC,是指客户端发送一个对象,服务器端返回一个 Stream(流式消息)。)

在服务器流RPC中,客户端发送一个请求给服务器,然后服务器可以返回多个响应。这种类型适用于服务器需要连续发送多个数据块给客户端的场景,如实时数据传输或日志流:

service ServerStreamingService {  
  rpc ListFeatures(Rectangle) returns (stream Feature) {} // 服务器流RPC示例:列出区域内的特征  
}  
  
message Rectangle {  
  int32 lo = 1;  
  int32 hi = 2;  
}  
  
message Feature {  
  string name = 1;  
  // ... 其他字段 ...  
}

在这个例子中,ListFeatures方法接收一个Rectangle请求,并返回一个Feature流。 

2.1.7、客户端RPC(Client-streaming RPC,是指客户端发送一个 Stream(流式消息),服务端返回一个对象。)

在客户端流RPC中,客户端可以连续写入多个请求,而服务器在读取完所有请求后返回一个响应。这适用于客户端需要上传大量数据的场景,如文件上传:

service ClientStreamingService {  
  rpc UploadData(stream DataChunk) returns (UploadResponse) {} // 客户端流RPC示例:上传数据块流  
}  
  
message DataChunk {  
  bytes data = 1;  
  // ... 其他字段 ...  
}  
  
message UploadResponse {  
  bool success = 1;  
  // ... 其他字段 ...  
}

在这个例子中,UploadData方法接收一个DataChunk流,并返回一个UploadResponse

2.1.8、双向流式RPC(Bidirectional streaming RPC,两个独立运行,客户端和服务器可以按照它们喜欢的顺序进行读取和写入,类似 webSocket。) 

在双向流式RPC中,客户端和服务器都可以连续读写多个消息。这种类型适用于需要实时交互的场景,如实时聊天或多人在线游戏:

service BidirectionalStreamingService {  
  rpc Chat(stream ChatMessage) returns (stream ChatMessage) {} // 双向流式RPC示例:实时聊天  
}  
  
message ChatMessage {  
  string sender = 1;  
  string content = 2;  
  // ... 其他字段 ...  
}

在这个例子中,Chat方法接收和返回的都是ChatMessage流,允许客户端和服务器之间进行实时消息交换。

2.2、生成客户端和服务端代码

使用Protobuf编译器和gRPC插件,你可以从.proto文件生成客户端和服务端的代码。这些代码将包含用于 RPC 通信的所有必要结构和功能。

例如,使用以下命令生成Go语言的代码:

protoc -I . helloworld.proto --go_out=plugins=gRPC:.

2.3、服务端建立 RPC 服务

在服务端,你需要实现.proto文件中定义的服务接口。以下是一个简单的Go语言服务端实现示例:

package main  
  
import (  
 "context"  
 "log"  
 "net"  
  
 pb "path/to/your/generated/protobuf/package"  
 "google.golang.org/grpc"  
)  
  
type server struct {  
 pb.UnimplementedGreeterServer  
}  
  
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {  
 return &pb.HelloReply{Message: "Hello, " + req.Name}, nil  
}  
  
func main() {  
 lis, err := net.Listen("tcp", ":50051")  
 if err != nil {  
 log.Fatalf("failed to listen: %v", err)  
 }  
 s := grpc.NewServer()  
 pb.RegisterGreeterServer(s, &server{})  
 if err := s.Serve(lis); err != nil {  
 log.Fatalf("failed to serve: %v", err)  
 }  
}

2.4、客户端发送 RPC 请求

在客户端,你可以使用生成的代码来调用 RPC 方法。以下是一个简单的 Go 语言客户端实现示例:

package main  
  
import (  
 "context"  
 "log"  
 "os"  
  
 pb "path/to/your/generated/protobuf/package"  
 "google.golang.org/grpc"  
)  
  
func main() {  
 conn, err := grpc.Dial(":50051", grpc.WithInsecure())  
 if err != nil {  
 log.Fatalf("did not connect: %v", err)  
 }  
 defer conn.Close()  
 c := pb.NewGreeterClient(conn)  
 name := "world" // 或者从命令行参数、环境变量等获取  
 r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})  
 if err != nil {  
 log.Fatalf("could not greet: %v", err)  
 }  
 log.Printf("Greeting: %s", r.Message)  
}
posted @ 2024-05-15 19:05  左扬  阅读(65)  评论(0编辑  收藏  举报
levels of contents