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 充分利用了这一特性,使得在单个连接上可以同时进行多个请求和响应的处理。
- 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 提供了一个更高效、更快速的通信基础。
微服务中网关的作用
- 网关根据不同的请求,将其转发到不同的服务(路由功能),由于入口的一致性,可以在网关上实现公共的一些功能
- 可以将公共的功能抽取出来,形成一个新的服务,比如 统一认证中心
ProtoBuf
grpc默认使用protobuf
链接与协议
gRPC 使用了 HTTP/2 作为传输层协议,而 HTTP/2 又是基于 TCP 连接的。当 gRPC 客户端与服务器建立连接时,实际上是在 TCP 连接上运行 HTTP/2 协议。这意味着 gRPC 利用了 TCP 提供的可靠性和连接管理特性,同时又通过 HTTP/2 的多路复用、头部压缩等特性提高了性能。
具体步骤如下:
- gRPC 客户端与服务器建立 TCP 连接。
- 在这个 TCP 连接上,双方交换 HTTP/2 协议的帧(frames),这些帧包含了 gRPC 的请求和响应数据。
- HTTP/2 协议负责将这些帧分割成更小的帧,并在一个连接上复用多个请求和响应,以提高效率。
- gRPC 在这些帧中使用 Protocol Buffers 进行序列化和反序列化,从而进行有效的数据交换。
因此,尽管 gRPC 抽象了底层的网络细节,但它的底层连接仍然是基于 TCP 的。这使得 gRPC 具有 TCP 的可靠性和连接管理特性,同时也利用了 HTTP/2 的高效传输机制,从而实现了高性能和可靠的远程过程调用(RPC)。
Protocol Buffers(简称 Protobuf)是 Google 开发的一种用于序列化结构化数据的语言无关、平台无关、可扩展的机制。它不是一种网络传输协议,而是一种数据序列化的格式。
Protobuf 的主要目的是提供一种有效率、简洁的方法来序列化结构化数据,以便于存储或传输。它可以将结构化数据转换为二进制格式,从而节省存储空间和网络传输带宽。
安装
Pb的安装
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
| syntax = "proto3";
option go_package="../service";
package service;
message ProductRequest{ int32 prod_id = 1; }
message ProductResponse{ int32 prod_stock = 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
|
- protoc
这是 Protocol Buffers 的编译器命令行工具,用于将 .proto 文件编译成相应语言的代码。
- –go_out=../service
这部分是 protoc 命令的选项,告诉编译器生成的 Go 代码放置在 ../service 目录中。具体来说:
- –go_out:表示要生成的是 Go 代码。
../service:是生成的 Go 代码的目标目录。
- –go_opt=paths=source_relative
这个选项告诉编译器生成的 Go 代码的导入路径是相对于 .proto 文件所在的目录的。这是为了确保生成的代码可以正确地导入依赖的其他包。
- –go-grpc_out=../service
这部分告诉 protoc 同时生成 gRPC 相关的代码,放置在 ../service 目录中。具体来说:
- –go-grpc_out:表示要生成的是 gRPC 相关的 Go 代码。
- ../service:是生成的 gRPC 相关的 Go 代码的目标目录。
- –go-grpc_opt=paths=source_relative
与 –go_opt=paths=source_relative 类似,这个选项告诉编译器生成的 gRPC 相关的 Go 代码的导入路径是相对于 .proto 文件所在的目录的。
- ./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() { rpcServer := grpc.NewServer() service.RegisterProdServiceServer(rpcServer, service.ProductService)
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
| package main
import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "learn-protobuf/service" "log" )
func main() { 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, } 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
| syntax = "proto3";
option go_package="../service";
package service;
message SimpleRequest { string data = 1; }
message StreamResponse { string stream_value = 1; }
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 { 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" )
func main() { rpcServer := grpc.NewServer(grpc.MaxRecvMsgSize(1024*1024*4), grpc.MaxSendMsgSize(math.MaxInt32)) 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", }
conn, err := grpc.Dial(":8003", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal("服务端出错", err) } defer conn.Close()
prodStreamClient := service.NewStreamServerClient(conn)
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) } }
|