Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——高性能的 gRPC(实践案例)
远程过程调用 RPC——高性能的 gRPC(实践案例)
gRPC
,这一由Google
推出的高性能、开源、通用RPC
框架,凭借其众多引人注目的特性,已成为业界瞩目的焦点。它基于HTTP/2
协议标准设计开发,并采用Protocol Buffers
作为默认的数据序列化协议,广泛支持多种编程语言。gRPC
不仅简化了服务的精确定义,而且还能为客户端和服务端自动生成可靠的代码库,从而极大地提升了开发效率。
现在,让我们一起来深入了解gRPC
的卓越特性:
-
-
-
语言无关性:
gRPC
展示了出色的多语言兼容性,它不受特定编程语言的限制。无论是C++
,Java
,Python
,Go
,Ruby
,C#
,Node.js
,还是Objective-C
,gRPC
都能轻松集成,为跨语言开发提供了极大的便利。 -
HTTP/2
的优势:通过采用HTTP/2
作为其核心传输协议,gRPC
得以充分利用该协议的多路复用、流量控制和服务器推送等高级功能,从而确保了数据传输的高效性和稳定性。 -
双向流式通信:
gRPC
支持双向流式传输模式,这意味着客户端和服务端能够建立持久连接,实现数据的实时读写,为需要持续数据交换的应用提供了强大的支持。 -
Protocol Buffers的集成:
gRPC
与Google的Protocol Buffers无缝集成,后者作为一种高效的数据序列化协议,不仅确保了数据的轻量化和快速传输,还为服务的精确定义提供了强大的工具。通过Protocol Buffers编译器,开发者可以轻松生成客户端和服务端的代码,极大地简化了开发流程。 -
插件化的扩展性:
gRPC
支持各种插件,以实现身份验证、调用跟踪、健康检查以及负载均衡等关键功能,为构建健壮、可伸缩的服务提供了坚实的基础。 -
跨平台与跨网络兼容性:
gRPC
的设计考虑了跨平台和跨网络的需求,确保了在不同的系统和网络环境中都能保持出色的性能和稳定性。
-
-
gRPC
凭借其高性能、灵活性和易用性,已成为微服务架构、云原生应用和分布式系统等领域的首选RPC框架。
一、gRPC 的安装
首先使用 go get 命令安装 gRPC-go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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/
)语言指南。在该文件中,我们定义了两个参数结果,分别是
StringRequest
和StringResponse
,同时还有一个服务结构StringService
,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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
)以及两个消息类型(StringRequest
和StringResponse
)。下面是对这段代码的详细分析:
2.1.1、语法声明
1 | syntax = "proto3" ; |
这行代码指定了使用
Buffers
的proto3
语法。Proto3
是Protobuf
的一个版本,它简化了字段规则和默认值,并且移除了proto2
中的一些复杂性和冗余。
2.1.2、包声明
1 | package pb; |
这里定义了一个包名pb
,它用于防止命名冲突,并允许Protobuf
消息在不同的项目中重用。
2.1.3、服务定义
1 2 3 4 | service StringService { rpc Concat(StringRequest) returns (StringResponse) {} rpc Diff(StringRequest) returns (StringResponse) {} } |
定义了一个名为StringService
的服务,该服务包含两个RPC方法:Concat
和Diff
。这两个方法都接受StringRequest
作为输入,并返回StringResponse
。RPC(远程过程调用)允许客户端调用服务器上的方法,就像调用本地函数一样。
2.1.4、消息定义
2.1.4.1、StringRequest
1 | protobuf`message StringRequest { string A = 1; string B = 2; }` |
这个消息类型包含两个字符串字段:A
和B
,分别用数字1和2进行标识。这些数字是字段的标识符,用于在序列化和反序列化过程中识别字段。它们也确保了向后兼容性,即使在未来添加或删除字段时也是如此。
2.1.4.2、StringResponse
1 | protobuf`message StringResponse { string Ret1 = 1; string Ret2 = 2; }` |
这个消息类型包含两个字符串字段:Ret1
和Ret2
,分别用数字1和2进行标识。如注释所述,Ret1
可能用于表示Concat
方法的结果,而Ret2
可能包含Diff
方法的结果或附加信息。重要的是要注意,字段名在消息内部必须是唯一的,以避免混淆和错误。
gRPC
支持四种不同类型的服务接口,每种都有其特定的使用场景。下面我将解释每种类型,并提供相应的代码示例:
2.1.5、一元RPC(是指客户端向服务器发送请求并获得响应,就像正常的函数调用一样。)
在一元RPC中,客户端发送一个请求给服务器,然后服务器返回一个响应。这是最简单的RPC类型。在上面的proto
文件中,Concat
和Diff
方法就是一元RPC
的例子:
1 2 3 4 5 | // StringService 服务定义,提供字符串操作服务 service StringService { rpc Concat(StringRequest) returns (StringResponse) {} // 一元RPC示例:连接字符串 // ... 其他方法 ... } |
2.1.6、服务器RPC(Server-streaming RPC,是指客户端发送一个对象,服务器端返回一个 Stream(流式消息)。)
在服务器流RPC中,客户端发送一个请求给服务器,然后服务器可以返回多个响应。这种类型适用于服务器需要连续发送多个数据块给客户端的场景,如实时数据传输或日志流:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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中,客户端可以连续写入多个请求,而服务器在读取完所有请求后返回一个响应。这适用于客户端需要上传大量数据的场景,如文件上传:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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中,客户端和服务器都可以连续读写多个消息。这种类型适用于需要实时交互的场景,如实时聊天或多人在线游戏:
1 2 3 4 5 6 7 8 9 | 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
语言的代码:
1 | protoc -I . helloworld.proto --go_out=plugins=gRPC:. |
2.3、服务端建立 RPC 服务
在服务端,你需要实现.proto
文件中定义的服务接口。以下是一个简单的Go语言服务端实现示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 语言客户端实现示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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) } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」