grpc初识和实现
# grpc 和 rpc的区别
gRPC 是一款高性能、开源的 RPC 框架,产自 Google,基于 ProtoBuf 序列化协议进行开发,支持多种语言(Golang、Python、Java等)。gRPC 提供了一种简单的方法来定义服务,同时客户端可以充分利用 HTTP/2 stream 的特性,从而有助于节省带宽、降低 TCP 的连接次数、节省CPU的使用等。
rpc和grpc之间的关系是什么?
- rpc是一种协议,grpc是基于rpc协议实现的一种框架。
grpc的解决rpc三大问题
协议约定。gRPC 的协议是 Protocol Buffers,是一种压缩率极高的序列化协议,Google 在 2008 年开源了 Protocol Buffers,支持多种编程语言,所以 gRPC 支持客户端与服务端可以用不同语言实现。
传输协议。gRPC 的数据传输用的是 Netty Channel, Netty 是一个高效的基于异步 IO 的网络传输架构。Netty Channel 中,每个 gRPC 请求封装成 HTTP 2.0 的 Stream。
服务发现。gRPC 本身没有提供服务发现的机制,需要通过其他组件。
总结
grpc是一种实现了rpc协议的框架,并且分别通过protocol buffer、netty channel 以及服务发现组件解决rpc的协议约定、传输协议、服务发现三大问题。
# grpc和protobuf的区别
# 1. grpc
- 多语言:语言中立,支持多种语言。
- 轻量级、高性能:序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架。
- 可插拔
- IDL:基于文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub。
- 移动端:基于标准的 HTTP2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量。
- gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc (opens new window), grpc-java (opens new window), grpc-go (opens new window). 其中 C 版本支持 C (opens new window), C++ (opens new window), Node.js (opens new window), Python (opens new window), Ruby (opens new window), Objective-C (opens new window), PHP (opens new window) 和 C# (opens new window) 支持.
# 2. protobuf
习惯用
Json、XML
数据存储格式的你们,相信大多都没听过Protocol Buffer
Protocol Buffer
其实 是Google
出品的一种轻量 & 高效的结构化数据存储格式,性能比Json、XML
真的强!太!多!protobuf经历了protobuf2和protobuf3,pb3比pb2简化了很多,目前主流的版本是pb3
# python 开发 grpc和probuf
# 1. protobuf体验
- 安装
python -m pip install grpcio #安装grpc
python -m pip install grpcio-tools #安装grpc tools
2
- 先体验protobuf3
protobuf3 是有自己专门的定义格式的 - 门槛
syntax = "proto3";
message HelloRequest {
string name = 1; // name 表示名称,name 的编号是1;
}
2
3
4
5
- 生成proto的python文件
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
- 查看protobuf生成的代码
from grpc_test import helloworld_pb2
request = helloworld_pb2.HelloRequest()
request.name = "bobby"
req_str = request.SerializeToString()
print(req_str)
request2 = helloworld_pb2.HelloRequest()
request2.ParseFromString(req_str)
print(request2.name)
2
3
4
5
6
7
8
9
10
11
12
- 对比一下protobuf和json生成的效果
from grpc_test import helloworld_pb2
request = helloworld_pb2.HelloRequest()
request.name = "bobby"
req_str = request.SerializeToString()
print(req_str)
import json
req_json = {
"name":"bobby"
}
print(len(json.dumps(req_json)))
print(len(req_str))
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2. grpc打印hello word
https://github.com/grpc/grpc/blob/master/examples/python/helloworld
客户端代码
from __future__ import print_function import logging import grpc from protobuf import protobuf_pb2 from protobuf import protobuf_pb2_grpc if __name__ == '__main__': with grpc.insecure_channel("127.0.0.1:50051") as channel: stub = protobuf_pb2_grpc.GreeterStub(channel) response = stub.SayHello(protobuf_pb2.HelloRequest(name="word!!!")) print(response.message)
1
2
3
4
5
6
7
8
9
10
11
12
13
14服务端代码
#!/usr/bin/env python # -*- coding: utf-8 -*- # @time : 2021/10/26 # @desc : ... from concurrent import futures import logging import grpc from protobuf import protobuf_pb2 from protobuf import protobuf_pb2_grpc class Greeter(protobuf_pb2_grpc.GreeterServicer): def SayHello(self, request, context): return protobuf_pb2.HelloReply(message="hello "+request.name) if __name__ == '__main__': server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) protobuf_pb2_grpc.add_GreeterServicer_to_server(Greeter(),server) server.add_insecure_port("0.0.0.0:50051") server.start() server.wait_for_termination()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# go 开发 grpc
# 1. 下载编译工具
- 地址 https://github.com/protocolbuffers/protobuf/releases
注意:protoc的版本需要和golang/protobuf保持一致 (尽量自己去下载最新的版本);下载完成后解压后记得将路径添加到环境变量中
# 2. 下载go的依赖包
go get -u google.golang.org/protobuf
# 3. proto文件
syntax = "proto3";
option go_package = ".;proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
2
3
4
5
6
7
8
9
10
11
12
13
# 4. 生成go文件
protoc -I . helloword.proto --go_out=plugins=grpc:.
# 5. 服务端代码
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"grpc_demo/hello"
"net"
)
type Server struct {
}
func (s *Server) SayHello(ctx context.Context,request *hello.HelloRequest)(*hello.HelloReply,error){
return &hello.HelloReply{Message:"Hello "+request.Name},nil
}
func main() {
g := grpc.NewServer()
s := Server{}
hello.RegisterGreeterServer(g,&s)
lis, err := net.Listen("tcp", fmt.Sprintf(":8080"))
if err != nil {
panic("failed to listen: "+err.Error())
}
g.Serve(lis)
}
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
# 6. 客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"grpc_demo/proto"
)
func main() {
conn,err := grpc.Dial("127.0.0.1:8080",grpc.WithInsecure())
if err!=nil{
panic(err)
}
defer conn.Close()
c := hello.NewGreeterClient(conn)
r,err := c.SayHello(context.Background(),&hello.HelloRequest{Name:"bobby"})
if err!=nil{
panic(err)
}
fmt.Println(r.Message)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# python和golang 联调
- 要点
- 使用同一个
*.proto
文件 - python和go的 *.proto文件中尽量不要使用package参数,除非大量的包名重复
- 使用同一个
# grpc 模式
gRPC主要有4种请求/响应模式
简单模式(Simple RPC)
服务端数据流模式(Server-side streaming RPC)
客户端数据流模式(Client-side streaming RPC)
双向数据流模式(Bidirectional streaming RPC)
proto文件
syntax = "proto3"; option go_package = ".;proto"; service Greeter{ rpc GetStream(StreamReqData) returns(stream StreamResData) ; // 服务端流模式 rpc PutStream(stream StreamReqData) returns(StreamResData) ; // 客户端流模式 rpc AllStream(stream StreamReqData) returns(stream StreamResData); // 双向数据流模式 } message StreamReqData{ string data = 1; } message StreamResData{ string data = 1; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 简单模式
- 简单请求响应
# 2. 服务端数据流模式
- 这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。
- 代码见双向数据流
# 3. 客户端数据流模式
与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。
代码见双向数据流
# 4. 双向数据流模式
proto文件
syntax = "proto3"; option go_package = ".;proto"; service Greeter{ rpc GetStream(StreamReqData) returns(stream StreamResData) ; // 服务端流模式 rpc PutStream(stream StreamReqData) returns(StreamResData) ; // 客户端流模式 rpc AllStream(stream StreamReqData) returns(stream StreamResData); // 双向数据流模式 } message StreamReqData{ string data = 1; } message StreamResData{ string data = 1; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15server
/* * @date: 2021/10/27 * @desc: ... */ package main import ( "fmt" "google.golang.org/grpc" "net" "picturePro/grpcStreamTest/proto" "sync" "time" ) const ADDR = ":50052" type server struct { } // GetStream 服务端数据流模式 func (s *server) GetStream(req *proto.StreamReqData, res proto.Greeter_GetStreamServer) error { i := 0 for { i++ _ = res.Send(&proto.StreamResData{ Data: fmt.Sprintf("date: %v", time.Now().Unix()), }) time.Sleep(time.Second) if i > 10 { break } } return nil } // PutStream 客户端数据流模式 func (s *server) PutStream(clientStr proto.Greeter_PutStreamServer) error { for { if a, err := clientStr.Recv(); err != nil { fmt.Println("er:", err) break } else { fmt.Println(a.Data) } } return nil } func (s *server) AllStream(allStr proto.Greeter_AllStreamServer) error { wg := sync.WaitGroup{} wg.Add(2) go func() { defer wg.Done() for { data, _ := allStr.Recv() fmt.Println("收到客户端消息:" + data.Data) } }() go func() { defer wg.Done() for { _ = allStr.Send(&proto.StreamResData{Data: "我是服务器"}) time.Sleep(time.Second) } }() wg.Wait() return nil } func main() { listen, err := net.Listen("tcp", ADDR) if err != nil { panic(err.Error()) } s := grpc.NewServer() proto.RegisterGreeterServer(s, &server{}) err = s.Serve(listen) if err != nil { return } }
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
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
86client
/* * @date: 2021/10/27 * @desc: ... */ package main import ( "context" "fmt" "google.golang.org/grpc" "picturePro/grpcStreamTest/proto" "sync" "time" ) func main() { conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure()) if err != nil { return } defer conn.Close() c := proto.NewGreeterClient(conn) // 服务端流模式 res, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "hello"}) for { a, err := res.Recv() if err != nil { fmt.Println(err.Error()) break } fmt.Println(a.Data) } // 客户端流模式 puts, _ := c.PutStream(context.Background()) for i := 0; i < 10; i++ { err = puts.Send(&proto.StreamReqData{ Data: fmt.Sprintf("test %v", i), }) if err != nil { fmt.Println(err.Error()) } time.Sleep(time.Second) } //双向流模式 allStr, _ := c.AllStream(context.Background()) wg := sync.WaitGroup{} wg.Add(2) go func() { defer wg.Done() for { data, _ := allStr.Recv() fmt.Println("收到客户端消息:" + data.Data) } }() go func() { defer wg.Done() for { _ = allStr.Send(&proto.StreamReqData{Data: "慕课网"}) time.Sleep(time.Second) } }() wg.Wait() }
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# gRPC的优势
与HTTP(Restful API)对比,gRPC的优势
- gRPC和restful API都提供了一套通信机制,用于server/client模型通信,而且它们都使用http作为底层的传输协议(严格地说, gRPC使用的http2.0,而restful api则不一定)。不过gRPC还是有些特有的优势,如下:
- gRPC可以通过protobuf来定义接口,可以有更加严格的接口约束条件,支持多种语言。
- protobuf可以将数据序列化为二进制编码,这会大幅减少需要传输的数据量,从而大幅提高传输速度。
- gRPC可以支持streaming流式通信(http2.0),提高传输速度。