gRPC

gRPC

gPRC是一个高性能的开源的通用RPC框架 ,在GRPC中,称被调用方为server,调用方为client

gRPC会屏蔽底层细节,client只需直接调用定义好的方法,拿到预期的结果返回;同样的,对于server来说,还需要实现我们定义的方法,GRPC会帮我们屏蔽底层的实现细节,我们只需实现所定义的方法的具体逻辑即可

相比于HTTP,gRPC性能更高,使用自定义协议和压缩

性能

当我们比较 gRPC 和传统的基于 HTTP/1.1 的通信时,gRPC 的性能更高是相对于 HTTP/1.1 而言的,而不是相对于整个 HTTP 协议。

gRPC 使用的是 HTTP/2 协议,而 HTTP/2 在很多方面相比于 HTTP/1.1 有了很大的改进,这些改进也直接影响了 gRPC 的性能。下面是一些导致 gRPC 比传统的基于 HTTP/1.1 的通信更高效的主要原因:

1. 多路复用(Multiplexing)

  • HTTP/2 多路复用:HTTP/2 允许多个请求和响应在同一个 TCP 连接上交错进行,而不像 HTTP/1.x 需要创建多个连接。这消除了 HTTP/1.x 中的队头阻塞问题,并提高了资源利用率。gRPC 充分利用了这一特性,使得在单个连接上可以同时进行多个请求和响应的处理。

2. 头部压缩(Header Compression)

  • HTTP/2 头部压缩:HTTP/2 使用了 HPACK 算法对请求和响应的头部进行压缩,减少了通信时的数据量。对于 gRPC 这种多次小数据量请求的情况,这种压缩效果特别明显。

3. 二进制传输(Binary Framing)

  • HTTP/2 二进制传输:HTTP/2 使用二进制帧进行数据传输,而不像 HTTP/1.x 那样使用文本格式。这样减少了在序列化和反序列化数据时的开销,并且减少了网络传输的数据量。

4. 流量控制(Flow Control)

  • HTTP/2 流量控制:HTTP/2 中引入了流量控制机制,可以防止过载一个接收方的情况发生。这对于长时间运行的连接或大量数据传输的应用程序非常有用,例如 gRPC。

5. 服务器推送(Server Push)

  • HTTP/2 服务器推送:HTTP/2 允许服务器在客户端请求之前推送资源。这对于减少客户端请求的往返次数和加快加载速度非常有帮助。

综上所述,gRPC 基于 HTTP/2 协议的特性使得它在很多方面都比传统的基于 HTTP/1.1 的通信更高效。因此,当我们说 gRPC 的性能比传统的 HTTP 更好时,我们实际上是在指 gRPC 相对于 HTTP/1.1 在性能上的提升。对于 HTTP/2 而言,它为 gRPC 提供了一个更高效、更快速的通信基础。

微服务中网关的作用

  1. 网关根据不同的请求,将其转发到不同的服务(路由功能),由于入口的一致性,可以在网关上实现公共的一些功能
  2. 可以将公共的功能抽取出来,形成一个新的服务,比如 统一认证中心

ProtoBuf

grpc默认使用protobuf

链接与协议

gRPC 使用了 HTTP/2 作为传输层协议,而 HTTP/2 又是基于 TCP 连接的。当 gRPC 客户端与服务器建立连接时,实际上是在 TCP 连接上运行 HTTP/2 协议。这意味着 gRPC 利用了 TCP 提供的可靠性和连接管理特性,同时又通过 HTTP/2 的多路复用、头部压缩等特性提高了性能。

具体步骤如下:

  1. gRPC 客户端与服务器建立 TCP 连接。
  2. 在这个 TCP 连接上,双方交换 HTTP/2 协议的帧(frames),这些帧包含了 gRPC 的请求和响应数据。
  3. HTTP/2 协议负责将这些帧分割成更小的帧,并在一个连接上复用多个请求和响应,以提高效率。
  4. gRPC 在这些帧中使用 Protocol Buffers 进行序列化和反序列化,从而进行有效的数据交换。

因此,尽管 gRPC 抽象了底层的网络细节,但它的底层连接仍然是基于 TCP 的。这使得 gRPC 具有 TCP 的可靠性和连接管理特性,同时也利用了 HTTP/2 的高效传输机制,从而实现了高性能和可靠的远程过程调用(RPC)。

Protocol Buffers(简称 Protobuf)是 Google 开发的一种用于序列化结构化数据的语言无关、平台无关、可扩展的机制。它不是一种网络传输协议,而是一种数据序列化的格式。

Protobuf 的主要目的是提供一种有效率、简洁的方法来序列化结构化数据,以便于存储或传输。它可以将结构化数据转换为二进制格式,从而节省存储空间和网络传输带宽。

安装

Pb的安装

1
brew install protobuf

gRPC的安装

1
2
3
//go mod拉一下这两个库
go get google.golang.org/grpc
go get google.golang.org/protobuf

安装GRPC的Go代码生成器

1
2
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Go接入gRPC简单实践

1.编写proto文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 指定当前proto语法的版本
syntax = "proto3";

// option go_package = "path;name";
// path表示自动生成的go文件的存放地址,会自动生成目录
// name表示生成的go文件的包名
option go_package="../service";

// 指定文件生成出来的package
package service;

message ProductRequest{
int32 prod_id = 1; //1代表顺序
}

message ProductResponse{
int32 prod_stock = 1; //1代表顺序
}

service ProdService {
rpc GetProductStock(ProductRequest) returns (ProductResponse);
}

2.编译proto文件生成service代码

进入proto文件所在位置,执行编译命令

1
protoc --go_out=../service --go_opt=paths=source_relative --go-grpc_out=../service --go-grpc_opt=paths=source_relative ./product.proto
  1. protoc

这是 Protocol Buffers 的编译器命令行工具,用于将 .proto 文件编译成相应语言的代码。

  1. –go_out=../service

这部分是 protoc 命令的选项,告诉编译器生成的 Go 代码放置在 ../service 目录中。具体来说:

  1. –go_out:表示要生成的是 Go 代码。
    ../service:是生成的 Go 代码的目标目录。
  2. –go_opt=paths=source_relative
    这个选项告诉编译器生成的 Go 代码的导入路径是相对于 .proto 文件所在的目录的。这是为了确保生成的代码可以正确地导入依赖的其他包。
  3. –go-grpc_out=../service
    这部分告诉 protoc 同时生成 gRPC 相关的代码,放置在 ../service 目录中。具体来说:
  4. –go-grpc_out:表示要生成的是 gRPC 相关的 Go 代码。
  5. ../service:是生成的 gRPC 相关的 Go 代码的目标目录。
  6. –go-grpc_opt=paths=source_relative
    与 –go_opt=paths=source_relative 类似,这个选项告诉编译器生成的 gRPC 相关的 Go 代码的导入路径是相对于 .proto 文件所在的目录的。
  7. ./product.proto
    这是要编译的 .proto 文件的路径。在这个例子中,product.proto 文件位于当前目录(.)下。

这个命令的作用是:

  • 编译 product.proto 文件。
  • 生成对应的 Go 代码,并将生成的 Go 代码放置在 ../service 目录中。
  • 生成的 Go 代码的导入路径是相对于 product.proto 文件所在目录的。
  • 同时生成 gRPC 相关的 Go 代码,并将生成的 gRPC 相关的 Go 代码也放置在 ../service 目录中。
  • 生成的 gRPC 相关的 Go 代码的导入路径也是相对于 product.proto 文件所在目录的。

3.编写业务层的service文件来使用生成的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
package service

import (
"context"
)

// 向外暴露的服务
var ProductService = &productService{}

// 实现接口
type productService struct {
UnimplementedProdServiceServer
}

// 接口方法的实现
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductResponse, error) {
// 实现具体的业务逻辑
stock := p.getStockById(request.GetProdId())
return &ProductResponse{ProdStock: stock}, nil
}

func (p *productService) getStockById(id int32) int32 {
return 100 + id
}

4.服务端代码

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
31
package main

import (
"fmt"
"google.golang.org/grpc"
"learn-protobuf/service"
"log"
"net"
)

func main() {
// 创建一个gRPC server
rpcServer := grpc.NewServer()

// 将接口的实现类注册到grpcServer
service.RegisterProdServiceServer(rpcServer, service.ProductService)

// 启动端口监听
listen, err := net.Listen("tcp", ":8003")
if err != nil {
log.Fatalln("启动监听出错")
}

// 启动server
err = rpcServer.Serve(listen)
if err != nil {
log.Fatalln("启动RPC服务出错")
}

fmt.Println("启动GRPC服务端成功")
}

5.客户端代码

将服务端生成的go代码完整拷贝一份到客户端service层

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
31
package main

import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"learn-protobuf/service"
"log"
)

func main() {
// 创建与服务端的grpc连接
conn, err := grpc.Dial(":8003", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal("服务端出错", err)
}
defer conn.Close()

// 创建一个客户端
prodClient := service.NewProdServiceClient(conn)
request := &service.ProductRequest{
ProdId: 12,
}
// 客户端调用本地代码,执行RPC操作,获取结果
stockResponse, err := prodClient.GetProductStock(context.Background(), request)
if err != nil {
log.Fatal("查询库存出错", err)
}
fmt.Println("查询库存成功", stockResponse)
}

服务端流式gRPC

1.定义流式gRPC所用的proto文件

服务端流式rpc,需在响应参数前面加上 stream 关键字

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
// 指定当前proto语法的版本
syntax = "proto3";

// option go_package = "path;name";
// path表示自动生成的go文件的存放地址,会自动生成目录
// name表示生成的go文件的包名
option go_package="../service";

// 指定文件生成出来的package
package service;

// 定义请求信息
message SimpleRequest {
string data = 1;
}

// 定义流式响应信息
message StreamResponse {
string stream_value = 1;
}

// 服务端流式rpc,需在响应参数前面加上 stream 关键字
service StreamServer {
rpc ListValue(SimpleRequest) returns (stream StreamResponse);
}

2.编译proto文件生成service代码

进入proto文件所在位置,执行编译命令

1
protoc --go_out=../service --go_opt=paths=source_relative --go-grpc_out=../service --go-grpc_opt=paths=source_relative ./server_stream.proto

命令含义如之前

3.编写业务层的service文件来使用生成的go代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package service

import "strconv"

// 定义我们的服务,实现接口
type StreamService struct {
UnimplementedStreamServerServer
}

func (s *StreamService) ListValue(req *SimpleRequest, srv StreamServer_ListValueServer) error {
// 实现具体的业务逻辑,即向流中发送消息,默认每次发送消息的最大长度为`math.MaxInt32`bytes
for i := 0; i < 5; i++ {
err := srv.Send(&StreamResponse{
StreamValue: req.Data + strconv.Itoa(i),
})
if err != nil {
return err
}
}
return nil
}

4.服务端代码

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
31
package main

import (
"fmt"
"google.golang.org/grpc"
"learn-protobuf/service"
"log"
"math"
"net"
)

// 服务端流式grpc
func main() {
// 创建grpc服务器,设置单次最大接收消息大小为1024*1024*4 bytes,即4MB;最大单次响应大小为 math.MaxInt32 bytes
rpcServer := grpc.NewServer(grpc.MaxRecvMsgSize(1024*1024*4), grpc.MaxSendMsgSize(math.MaxInt32))
// 将接口的实现注册到grpcServer,注册一个流式Server
service.RegisterStreamServerServer(rpcServer, &service.StreamService{})

listen, err := net.Listen("tcp", ":8003")
if err != nil {
log.Fatalln("启动监听出错")
}

err = rpcServer.Serve(listen)
if err != nil {
log.Fatalln("启动RPC服务出错")
}

fmt.Println("启动GRPC服务端成功")
}

5.客户端代码

将服务端生成的go代码完整拷贝一份到客户端service层

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"io"
"learn-protobuf/stream_client/service"
"log"
)

func main() {
// 创建一个发送结构体
simpleRequest := service.SimpleRequest{
Data: "Stream Server gRPC",
}

// 创建grpc连接
conn, err := grpc.Dial(":8003", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal("服务端出错", err)
}
defer conn.Close()

// 流式grpc客户端
prodStreamClient := service.NewStreamServerClient(conn)

// 调用GRPC方法
stream, err := prodStreamClient.ListValue(context.Background(), &simpleRequest)
if err != nil {
log.Fatalf("Call List Str Error: %v", err)
}

// 阻塞
for {
streamResponse, err := stream.Recv()
// 判断消息流是否已经结束
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("ListStr get stream err: %v", err)
}
// 打印返回值
log.Println(streamResponse.StreamValue)
}
}


gRPC
https://wuwanhao.github.io/2024/11/06/grpc/
作者
Wuuu
发布于
2024年11月6日
许可协议