
gRPC 是一款高性能开源通用的 RPC 框架,同时面向服务端跟移动端,基于 HTTP/2 协议设计。gRPC**不是一款服务治理框架,但是提供了服务治理的若干原材料,例如客户端负载均衡、KeepAlive、流控(自动跟手动)等等。下面简单介绍一下何为RPC 框架**,gRPC 作为 RPC 框架的特点和依赖的重要协议 – HTTP/2 协议。
特点:
详细介绍:https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-CN
简单来讲就是原先 HTTP/1.1 中的 Content-Type、Content-Encoding 等 Key 都被赋予一个标准的序号(索引)来减少数据量,Value 则进行相应的编码(哈夫曼)从而提升传输效率,gRPC 默认限制 HeaderList 的大小为8192,即不能超过8192个 K-V
http/2 实现多路复用(多个请求共用一个 TCP 连接)的原理:
http/2 通过 stream 来实现多路复用,每个请求相当于一个 stream,并分配一个 stream ID。http/2 将每个请求分割为多个帧(http/2 中的最小传输单元),每个帧都附带对应请求的 stream ID,多个 stream(请求) 的帧交替发送(流实现的并发),而不必等待前一个请求完全发送,服务端收到后根据 stream ID 合并为请求并进行解析。响应亦复如是。
感觉类似于计算机的 cpu 时间片,进程的交替运行。
http/1.x 发送每个请求都会占用一个 TCP 连接,占用系统资源
http/1.x 的队头阻塞问题:在 HTTP/1.x 中,浏览器通常会为每个域名建立 6-8 个并发连接,但每个连接只能处理一个请求。如果某个请求处理较慢,就会阻塞后续请求。
http/2 通过多路复用,允许多个请求和响应并发传输,消除了 http/1 的队头阻塞问题,然而并不能消除 TCP 的队首阻塞问题
虽然 HTTP/2 的多路复用解决了 HTTP/1.x 的许多问题,但它仍然依赖于 TCP。如果底层 TCP 连接出现丢包,整个连接中的所有流都会受到影响(即 TCP 层的队头阻塞问题)。为了解决这一问题,HTTP/3 引入了基于 UDP 的 QUIC 协议。
常见的帧类型:
为了防止某个流占用过多的带宽,HTTP/2 提供了基于窗口大小的流量控制机制
WINDOW_UPDATE 帧调整窗口大小。protobuf 全名是 ProtocolBuffers,是谷歌推出的二进制序列化协议,提供 IDL 文件来定义各种类型的数据。目前整体协议版本是 proto3,protobuf 提供了从 proto 文件编译生成各个语言文件的功能。与此同时 protobuf 提供了丰富的插件机制,用户可以扩展生成的对应语言的文件,俗称桩代码生成
gRPC 使用 Protocol Buffers(Protobuf)作为序列化协议
在 protobuf 中,gRPC 使用 Protocol Buffers(Protobuf)作为序列化协议
// 表示语法版本声明,有 proto2 和 3,基本上都使用 3
syntax = "proto3";
// 使用 package 关键字定义包名,通常与代码的命名空间相对应。
package greeter;
// 定义请求消息
message HelloRequest {
// 字段类型 字段名 = 字段编号;
string name = 1; // 客户端发送的名字
}
// 定义响应消息
message HelloResponse {
string message = 1; // 服务端返回的问候消息
}
// 定义服务
service Greeter {
rpc SayHello(HelloRequest)returns (HelloResponse);
}
消息(message)是 protobuf 的核心,定义了数据结构。每个字段都有类型、名称和唯一的编号(字段编号必须是正整数)。
服务(service)定义了一组 RPC 方法。每个方法有输入消息和输出消息类型。
gRPC 通过 Protocol Buffers 提供的 Plugin 机制来实现原生桩代码生成
以 go 编写一个简单的“问候服务(Greeter Service)”为例,定义一个服务 Greeter,包含一个方法 SayHello。
.proto 文件greeter.proto 文件如下
syntax = "proto3";
package greeter;
// 定义服务
service Greeter {
rpc SayHello(HelloRequest)returns (HelloResponse);
}
// 定义请求消息
message HelloRequest {
string name = 1; // 客户端发送的名字
}
// 定义响应消息
message HelloResponse {
string message = 1; // 服务端返回的问候消息
}
在 Go 中,我们需要安装以下工具来生成 gRPC 的代码:
1. **安装**** **`**protoc**`** ****编译器**(如果尚未安装):
下载并安装 Protocol Buffers 编译器。
2. 安装 Go 的 gRPC 插件:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
3. **设置环境变量**:
确保 protoc-gen-go 和 protoc-gen-go-grpc 已添加到 PATH 中。
4. 桩代码生成
protoc --go_out=. --go-grpc_out=. greeter.proto
生成的文件:
* ` greeter.pb.go `:包含消息类型定义。
* ` greeter_grpc.pb.go `:包含服务端和客户端的接口定义。
greeter.pb.go:含了 HelloRequest 和 HelloResponse 消息的结构体
// greeter.pb.go
package greeter
import(
"google.golang.org/protobuf/proto"
"google.golang.org/grpc"
"io"
)
// HelloRequest 消息
type HelloRequest struct {
Name string ` protobuf:"bytes,1,opt,name=name,proto3"json:"name,omitempty"`
}
// HelloResponse 消息
type HelloResponse struct {
Message string ` protobuf:"bytes,1,opt,name=message,proto3"json:"message,omitempty"`
}
// Reset 重置
func(x *HelloRequest)Reset() {
*x = HelloRequest{}
}
// String 返回字符串表示
func(x *HelloRequest)String()string {
return proto.CompactTextString(x)
}
// ProtoMessage 方法
func(*HelloRequest)ProtoMessage() {}
// Reset 重置
func(x *HelloResponse)Reset() {
*x = HelloResponse{}
}
// String 返回字符串表示
func(x *HelloResponse)String()string {
return proto.CompactTextString(x)
}
// ProtoMessage 方法
func(*HelloResponse)ProtoMessage() {}
greeter_grpc.pb.go:包含了 gRPC 服务端和客户端的代码桩,定义了 Greeter 服务的 SayHello 方法
// greeter_grpc.pb.go
package greeter
import(
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// GreeterClient 是客户端的接口
type GreeterClient interface {
SayHello(ctx context.Context,in *HelloRequest,opts ...grpc.CallOption) (*HelloResponse,error)
}
// GreeterServiceClient 是客户端的结构体
type greeterClient struct {
cc grpc.ClientConnInterface
}
// SayHello 调用 SayHello RPC 方法
func(c *greeterClient)SayHello(ctx context.Context,in *HelloRequest,opts ...grpc.CallOption) (*HelloResponse,error) {
out:= new(HelloResponse)
err:= c.cc.Invoke(ctx, "/greeter.Greeter/SayHello",in,out,opts...)
if err!= nil {
return nil,err
}
return out,nil
}
// NewGreeterClient 创建新的 GreeterClient
func NewGreeterClient(cc grpc.ClientConnInterface)GreeterClient {
return &greeterClient{cc}
}
// GreeterServer 是服务端接口
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloResponse,error)
}
// UnimplementedGreeterServer 是一个空的服务端实现
type UnimplementedGreeterServer struct{}
// SayHello 是服务端实现
func(UnimplementedGreeterServer)SayHello(ctx context.Context,req *HelloRequest) (*HelloResponse,error) {
return &HelloResponse{Message: "Hello, " + req.Name},nil
}
// RegisterGreeterServer 注册服务
func RegisterGreeterServer(s grpc.ServiceRegistrar,srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc,srv)
}
var _Greeter_serviceDesc = grpc.ServiceDesc{
ServiceName: "greeter.Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "greeter.proto",
}
// _Greeter_SayHello_Handler 处理 SayHello RPC
func _Greeter_SayHello_Handler(srv interface{},ctx context.Context,codec grpc.Codec,buf []byte) (interface{},error) {
in:= new(HelloRequest)
if err:= codec.Unmarshal(buf,in);err != nil {
return nil,err
}
return srv.(GreeterServer).SayHello(ctx,in)
}
服务端需要实现 Greeter 服务的接口,并启动 gRPC 服务。
server.go:
package main
import(
"context"
"log"
"net"
pb"path/to/your/generated/code" // 替换为生成代码的实际路径
"google.golang.org/grpc"
)
// 定义服务端结构体
type server struct {
pb.UnimplementedGreeterServer
}
// 实现 SayHello 方法
func(s *server)SayHello(ctx context.Context,req *pb.HelloRequest) (*pb.HelloResponse,error) {
log.Printf("Received: %s",req.GetName())
return &pb.HelloResponse{Message: "Hello, " + req.GetName() + "!"},nil
}
func main() {
// 监听端口
listener,err := net.Listen("tcp", ":50051")
if err!= nil {
log.Fatalf("Failed to listen: %v",err)
}
// 创建 gRPC 服务器
grpcServer:= grpc.NewServer()
pb.RegisterGreeterServer(grpcServer, &server{})
log.Println("Server is running on port 50051...")
// 启动服务
if err:= grpcServer.Serve(listener);err != nil {
log.Fatalf("Failed to serve: %v",err)
}
}
1. ` server ` 结构体嵌套了 ` UnimplementedGreeterServer `,这是生成代码中提供的默认实现。
2. 实现了 ` SayHello ` 方法,处理客户端请求并返回响应。
3. 使用 ` grpc.NewServer()` 创建 gRPC 服务,并注册服务实现。
客户端通过生成的客户端接口调用服务端的方法。
client.go:
package main
import(
"context"
"log"
"time"
pb"path/to/your/generated/code" // 替换为生成代码的实际路径
"google.golang.org/grpc"
)
func main() {
// 连接到服务端
conn,err := grpc.Dial("localhost:50051",grpc.WithInsecure())
if err!= nil {
log.Fatalf("Failed to connect: %v",err)
}
defer conn.Close()
// 创建客户端
client:= pb.NewGreeterClient(conn)
// 调用 SayHello 方法
name:= "Alice"
ctx,cancel := context.WithTimeout(context.Background(),time.Second)
defer cancel()
response,err := client.SayHello(ctx, &pb.HelloRequest{Name:name})
if err!= nil {
log.Fatalf("Could not greet: %v",err)
}
log.Printf("Server response: %s",response.GetMessage())
}
1. 使用 ` grpc.Dial ` 连接到服务端。
2. 创建 ` GreeterClient `,这是生成代码中定义的客户端接口。
3. 调用 ` SayHello ` 方法时,构造 ` HelloRequest ` 请求对象,并接收 ` HelloResponse ` 响应对象。
1. 启动服务端:
go run server.go
服务端会在 50051 端口监听请求,输出类似:
Server is running on port 50051...
2. 启动客户端
go run client.go
客户端会调用服务端的 SayHello 方法,输出类似:
Server response:Hello,Alice!
服务端的日志中会显示收到的请求:
Received:Alice
gRPC 内部在 Channel 构建到销毁的生命周期内,维护了该链接的整个状态的运转,了解内部具体的运转流程有助于我们更好的定位问题。gRPC 内部链接主要分成如下几个状态:
下图描述了五种状态之间的转换:

所有的状态码参考:https://github.com/grpc/grpc/blob/master/doc/statuscodes.md