03.gRPC
# 01.gRPC介绍
# 1、什么是 gRPC?
- gRPC(google Remote Procedure Call)是 Google 开发的一个高性能、开源的远程过程调用(RPC)框架
- 它允许客户端和服务器应用程序在不同的环境中(无论是云端、数据中心还是终端设备)相互通信
- gRPC 支持多种编程语言,并利用了 HTTP/2 协议的特性,提供高效的二进制传输和双向流通信
- 跨语言支持:gRPC 支持多种编程语言(如 Go、C++、Java、Python 等),可以在不同语言和平台之间互操作
- 基于 HTTP/2:gRPC 使用 HTTP/2 协议,支持多路复用、流控制和头部压缩,从而提高了传输效率和速度
- Protocol Buffers(Protobuf):
- gRPC 默认使用 Protobuf 作为其序列化协议
- 它是一种紧凑高效的二进制格式,能够更快速地序列化和反序列化数据
- 双向流:gRPC 支持双向流,可以同时发送和接收数据,适用于实时通信的场景
- 支持负载均衡和认证:gRPC 内置了服务发现、负载均衡和认证机制,适用于微服务架构
# 2、为什么要使用 gRPC?
- gRPC 的使用优势主要体现在高效、灵活和适用于微服务架构的场景
- 高效的二进制通信:
- 通过 Protobuf 进行二进制序列化
- gRPC 相较于传统的基于 JSON 或 XML 的 HTTP API,减少了数据传输的大小,提高了通信效率
- HTTP/2 的优势:
- gRPC 基于 HTTP/2,不仅支持多路复用,还能够通过头部压缩降低网络开销
- 并且支持双向流,适用于复杂的双向通信场景
- 强类型定义:
- Protobuf 定义了 gRPC 的通信接口和数据结构,提供了明确的接口约定
- 减少了服务间通信的错误风险,确保了通信的可靠性和一致性
- 跨语言和跨平台支持:
- gRPC 的多语言支持,使得它可以在异构系统之间使用,适合大型分布式系统的开发和维护
- 自动生成代码:
- 通过 Protobuf 定义接口和消息格式,gRPC 能自动生成客户端和服务端的代码,减少了手动编写通信逻辑的复杂性和错误
# 3、何时使用 gRPC?
微服务架构
- gRPC 的高效通信和跨语言支持使其成为微服务架构中服务间通信的理想选择
- 尤其是在服务之间需要低延迟、高吞吐量的场景下
实时通信
- gRPC 支持双向流,可以在同一连接中同时发送和接收数据,特别适用于需要实时数据传输的场景
- 比如视频流、物联网设备通信和在线游戏等
跨语言交互
- gRPC 的跨语言支持可以使服务端和客户端使用不同的语言,并且通过自动生成的代码
- 无需为通信编写复杂的序列化和反序列化逻辑
性能要求高的分布式系统
- 对于需要处理大量请求且对响应时间敏感的分布式系统
- gRPC 提供了高效的二进制序列化和 HTTP/2 的优势,减少了延迟和资源消耗
不适用的场景:
浏览器客户端通信
- 由于浏览器原生不支持 HTTP/2 中的双向流
- 因此 gRPC 在前端 Web 浏览器中的应用比较有限,不过可以通过 gRPC-Web 提供部分支持
低性能设备或带宽受限的环境
- 虽然 gRPC 高效,但它的复杂性和协议开销可能不适合极端受限的环境
# 02.gRPC基本使用
# 0、安装gRPC
go get -u -v google.golang.org/grpc # u(update) v(显示详情)
go mod tidy # 也可以导入后运行此命令
2
# 1、pb/person.proto
默认,protobuf,编译期间,不编译服,要想使之编译,需要使用 gRPC
使用的编译指令为:
protoc --go_out=plugins=grpc:./ *.proto
syntax = "proto3";
option go_package ="./person";
package pb;
// 消息体 --- 一个package 中,不允许定义同名的消息体
message Teacher {
int32 age = 1;
string name = 2;
}
// 定义 服务(参数和返回都是“消息体”)
service SayName {
rpc SayHello (Teacher) returns (Teacher);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2、grpc-server.go
package main
import (
"context" // 上下文. --- goroutine (go程) 之间用来进行数据传递 API 包
"demo/pb/person"
"fmt"
"google.golang.org/grpc"
"net"
)
// 定义类
type Children struct {
}
// 按接口绑定类方法
func (this *Children) SayHello(ctx context.Context, t *person.Teacher) (*person.Teacher, error) {
t.Name += " is Sleeping"
return t, nil
}
func main() {
//1. 初始一个 grpc 对象
grpcServer := grpc.NewServer()
//2. 注册服务
person.RegisterSayNameServer(grpcServer, new(Children))
//3. 设置监听, 指定 IP、port
listener, err := net.Listen("tcp", "127.0.0.1:8800")
if err != nil {
fmt.Println("Listen err:", err)
return
}
defer listener.Close()
//4. 启动服务---- serve()
grpcServer.Serve(listener)
}
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
31
32
33
34
35
36
37
38
39
# 3、grpc-client.go
package main
import (
"context"
"demo/pb/person"
"fmt"
"google.golang.org/grpc"
)
func main() {
//1. 连接 grpc 服务
grpcConn, err := grpc.Dial("127.0.0.1:8800", grpc.WithInsecure())
if err != nil {
fmt.Println("grpc.Dial err:", err)
return
}
defer grpcConn.Close()
//2. 初始化 grpc 客户端
grpcClient := person.NewSayNameClient(grpcConn)
// 创建并初始化Teacher 对象
var teacher person.Teacher
teacher.Name = "itcast"
teacher.Age = 18
//3. 调用远程服务, context.TODO 表示一个站位的空对象
t, err := grpcClient.SayHello(context.TODO(), &teacher)
fmt.Println(t, err)
}
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
31
# 4、pb/person/person.pb.go
- 运行命令自动生成的文件
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.19.1
// source: person.proto
package person
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 消息体 --- 一个package 中,不允许定义同名的消息体
type Teacher struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Age int32 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *Teacher) Reset() {
*x = Teacher{}
if protoimpl.UnsafeEnabled {
mi := &file_person_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Teacher) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Teacher) ProtoMessage() {}
func (x *Teacher) ProtoReflect() protoreflect.Message {
mi := &file_person_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Teacher.ProtoReflect.Descriptor instead.
func (*Teacher) Descriptor() ([]byte, []int) {
return file_person_proto_rawDescGZIP(), []int{0}
}
func (x *Teacher) GetAge() int32 {
if x != nil {
return x.Age
}
return 0
}
func (x *Teacher) GetName() string {
if x != nil {
return x.Name
}
return ""
}
var File_person_proto protoreflect.FileDescriptor
var file_person_proto_rawDesc = []byte{
0x0a, 0x0c, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02,
0x70, 0x62, 0x22, 0x2f, 0x0a, 0x07, 0x54, 0x65, 0x61, 0x63, 0x68, 0x65, 0x72, 0x12, 0x10, 0x0a,
0x03, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x67, 0x65, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x32, 0x2f, 0x0a, 0x07, 0x53, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24,
0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x62, 0x2e,
0x54, 0x65, 0x61, 0x63, 0x68, 0x65, 0x72, 0x1a, 0x0b, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x65, 0x61,
0x63, 0x68, 0x65, 0x72, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_person_proto_rawDescOnce sync.Once
file_person_proto_rawDescData = file_person_proto_rawDesc
)
func file_person_proto_rawDescGZIP() []byte {
file_person_proto_rawDescOnce.Do(func() {
file_person_proto_rawDescData = protoimpl.X.CompressGZIP(file_person_proto_rawDescData)
})
return file_person_proto_rawDescData
}
var file_person_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_person_proto_goTypes = []interface{}{
(*Teacher)(nil), // 0: pb.Teacher
}
var file_person_proto_depIdxs = []int32{
0, // 0: pb.SayName.SayHello:input_type -> pb.Teacher
0, // 1: pb.SayName.SayHello:output_type -> pb.Teacher
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_person_proto_init() }
func file_person_proto_init() {
if File_person_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_person_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Teacher); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_person_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_person_proto_goTypes,
DependencyIndexes: file_person_proto_depIdxs,
MessageInfos: file_person_proto_msgTypes,
}.Build()
File_person_proto = out.File
file_person_proto_rawDesc = nil
file_person_proto_goTypes = nil
file_person_proto_depIdxs = nil
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// SayNameClient is the client API for SayName service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type SayNameClient interface {
SayHello(ctx context.Context, in *Teacher, opts ...grpc.CallOption) (*Teacher, error)
}
type sayNameClient struct {
cc grpc.ClientConnInterface
}
func NewSayNameClient(cc grpc.ClientConnInterface) SayNameClient {
return &sayNameClient{cc}
}
func (c *sayNameClient) SayHello(ctx context.Context, in *Teacher, opts ...grpc.CallOption) (*Teacher, error) {
out := new(Teacher)
err := c.cc.Invoke(ctx, "/pb.SayName/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SayNameServer is the server API for SayName service.
type SayNameServer interface {
SayHello(context.Context, *Teacher) (*Teacher, error)
}
// UnimplementedSayNameServer can be embedded to have forward compatible implementations.
type UnimplementedSayNameServer struct {
}
func (*UnimplementedSayNameServer) SayHello(context.Context, *Teacher) (*Teacher, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func RegisterSayNameServer(s *grpc.Server, srv SayNameServer) {
s.RegisterService(&_SayName_serviceDesc, srv)
}
func _SayName_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Teacher)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SayNameServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pb.SayName/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SayNameServer).SayHello(ctx, req.(*Teacher))
}
return interceptor(ctx, in, info, handler)
}
var _SayName_serviceDesc = grpc.ServiceDesc{
ServiceName: "pb.SayName",
HandlerType: (*SayNameServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _SayName_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "person.proto",
}
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# 03.gRPC和HTTP区别
# 1、协议层面的区别
gRPC:基于 HTTP/2 协议
- gRPC是基于 HTTP/2 协议开发的,利用了 HTTP/2 的多路复用、头部压缩、流控制等特性
- 多路复用:允许在一个 TCP 连接上并发多个请求,避免了 HTTP/1.1 中“队头阻塞”(Head-of-line blocking)的问题
- 双向流:gRPC 支持双向流(bidirectional streaming),允许客户端和服务端在同一个连接上同时发送和接收消息
- 头部压缩:HTTP/2 引入了头部压缩机制,减少了带宽占用,提升了传输效率
HTTP 服务:基于 HTTP/1.1 或 HTTP/2 协议
- 传统 HTTP 服务大多数仍然基于 HTTP/1.1 协议
- 虽然部分场景中支持 HTTP/2,但 HTTP/1.1 在服务设计上仍然有较大的差异
- 单一请求/响应模型:
- HTTP/1.1 采用严格的请求-响应模型
- 每次请求都需要独立的连接或通过持续连接来保持 TCP 连接,无法并发多个请求
- 没有流控制:
- HTTP/1.1 没有流式传输能力,客户端和服务端只能在每次请求/响应中传递一条消息
- 队头阻塞:多个请求无法在同一个连接中并发,导致性能下降
# 2、数据格式的区别
gRPC:二进制格式(Protocol Buffers)
- gRPC 默认使用 Google 的 Protocol Buffers(Protobuf)进行消息序列化
- 高效的二进制编码:
- Protobuf 序列化后的数据体积更小,能够更快地在网络上传输,并且节省解析时间
- 跨语言:Protobuf 允许在不同的编程语言中生成相同的序列化和反序列化代码,确保跨语言的通信一致性
HTTP 服务:文本格式(JSON、XML、HTML 等)
- 传统 HTTP 服务一般使用 JSON 或 XML作为数据交换格式
- 虽然这些格式可读性好,但由于是文本格式,占用的字节较多
- 可读性好:JSON 和 XML 格式是人类可读的,这对于调试和手动查看数据非常方便
- 效率低:文本格式的消息在网络传输和解析时效率较低,特别是对于大数据量场景,会带来带宽消耗和性能瓶颈
# 3、性能的区别
gRPC:高性能、低延迟
- gRPC 基于 HTTP/2 和 Protobuf,使得它在传输和解析数据方面比传统 HTTP 服务要高效得多
- 其二进制编码和多路复用使得 gRPC 特别适合高并发、低延迟的场景
- 头部压缩和多路复用:HTTP/2 的特性减少了网络请求的开销和延迟,提升了并发处理能力
- 二进制序列化:通过 Protobuf 进行二进制序列化,减少了数据传输的字节数和 CPU 处理时间
HTTP 服务:相对较低的性能
传统 HTTP 服务由于依赖于 HTTP/1.1 协议和文本格式的消息,带来了更多的网络开销和更慢的解析速度
头部较大:HTTP/1.1 的头部较为冗长,特别是对于短小请求,头部会占用大量带宽
请求阻塞:由于缺乏多路复用,HTTP/1.1 容易发生队头阻塞问题,在高并发场景下性能受限
# 4、功能的区别
gRPC:丰富的功能支持
双向流:gRPC 支持双向流式通信,客户端和服务端可以在同一个连接中同时发送和接收消息,非常适合实时通信、视频流和物联网场景
负载均衡和服务发现:gRPC 支持与现代的负载均衡和服务发现系统集成,例如与 Kubernetes 一起使用时,gRPC 可以实现自动的负载均衡
认证和安全:gRPC 内置了强大的认证和安全功能,支持基于 TLS 的加密通信,以及与 OAuth、JWT 等标准身份认证协议的集成
自动代码生成:通过 Protobuf,gRPC 可以自动生成客户端和服务端的代码,大大减少了开发者手动编写通信逻辑的工作
HTTP 服务:基本功能
单向请求/响应:HTTP 服务只能处理单次的请求和响应,无法处理复杂的流式通信
功能扩展性有限:HTTP 服务的负载均衡、认证等通常需要额外的中间件或服务支持,例如使用 Nginx 实现负载均衡,或者集成 OAuth 来实现认证
无自动代码生成:传统 HTTP 服务需要手动编写客户端和服务端的请求、响应处理逻辑
# 5、开发体验的区别
gRPC:强类型与自动化代码生成
使用 gRPC 开发时,开发者只需要编写 Protobuf 文件定义服务和消息格式,gRPC 会自动生成客户端和服务端的代码这种自动化减少了手动编写序列化、反序列化代码的负担
强类型定义:gRPC 服务的接口和消息都是通过 Protobuf 进行强类型定义的,编译期间就能检查出错误,提升了开发的安全性和准确性
HTTP 服务:松散耦合和手动编码
传统 HTTP 服务由于使用 JSON 或 XML 等文本格式进行数据交换,消息体没有强类型约束,客户端和服务端之间可能存在协议不一致的问题,这些问题往往要等到运行时才会发现
需要手动处理 JSON 的序列化和反序列化逻辑,增加了代码复杂度
# 6、适用场景的区别
gRPC:
微服务架构:gRPC 非常适合微服务之间的通信,特别是在需要高效、低延迟的场景下,例如内部服务调用、数据处理管道、机器学习任务调度等
实时通信:gRPC 的双向流和低延迟特性使其非常适合实时通信的场景,例如 IoT 设备、在线游戏和视频会议等
跨语言服务:由于 gRPC 提供跨语言支持,它适合异构系统的服务通信,特别是在多种编程语言共存的环境中,gRPC 的自动化代码生成降低了集成成本
HTTP 服务:
Web 应用前端与后端通信:HTTP 服务主要用于传统的 Web 应用场景,例如前端(如浏览器、移动应用)与后端服务器的通信由于 JSON 的可读性和广泛支持,HTTP 服务在此场景下仍然非常流行
第三方 API:HTTP 服务通常用于提供和消费第三方的 RESTful API,这些 API 对人类可读性要求较高,并且通常通过标准的 HTTP 1.1 协议传输