0%

grpc

​ 学习grpc文件

1、rpc

1、wikipedia概念

分布式计算,远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。

如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用远程方法调用,例:Java RMI

RPC是一种进程间通信的模式,程序分布在不同的地址空间里。如果在同一主机里,RPC可以通过不同的虚拟地址空间(即便使用相同的物理地址)进行通讯,而在不同的主机间,则通过不同的物理地址进行交互。许多技术(常常是不兼容)都是基于这种概念而实现的。

image

2、http和rpc的区别

1、http指的是一个应用层协议,它提供的只是一个传输协议

2、rpc讲的是一个远程过程调用,它是一个过程,一个rpc架构包含了多个层级,以dubbo的架构来

rpc其实架构很简单,就是下面一图,但是具体实现上差异还是有点,比如我们所了解的 http + json 只能说是dubbo 只能说是dubbo的最下层实现,所以 rpc相对来说偏向于服务治理这一块

dubbo-architucture

/dev-guide/images/dubbo-framework.jpg

3、为什么我们需要rpc框架,http1.1 提供的rest api不行吗

1、不支持长连接,keepalive

2、http1.1 ping-pang client - server http , 建立很多个连接 (http tcp 连接慢,传输/窗口)

3、rpc 多路复用能力 (http2/3) client - server (io) ,server 包 (二进制) -> go 顺序 http2 奇偶

4、并发性

5、rpc tcp 传输 -> http1.1 纯文本 头部, rcp hello 行头体

5、 json rpc

4、比较出名的rpc框架

  • Java: JNI , WebService , Dubbo ,HSF ,spring的Feign那一套,grpc
  • Golang: gorpc ,grpc 等
  • C++:Svrkit ,grpc 等

大厂用的吧,各大厂都有自己的轮子,比如 thriftgprcTarsbrpcmotandubbo 还有很多吧

其实轮生态的话,绝对是开源项目的生态比较好,所以开源项目中生态比较好的就是 grpc,thrift,dubbo ,使用难度上来看 dubbo是最简单的,如果你们全Java的话它是个不错的选择!

2、grpc介绍

​ gRPC 是一个现代的开源高性能远程过程调用(Remote Procedure Call,RPC)框架,可以在任何环境中运行。它可以高效地连接数据中心内部和跨数据中心的服务,并为负载平衡、跟踪、健康检查和身份验证提供可插拔的支持。它也适用于最后一英里的分布式计算连接设备,移动应用程序和浏览器的后端服务。

1、开源的grpc官方,主要提供的能力

  • 服务调用(提供有stream流,请求响应类型的)
  • 负载均衡 (提供xsd协议)
  • 权限(安全方面)

2、grpc主要采用的技术

  • 传输层:http2协议
  • 序列化层:protobuf
  • 负载均衡:xsd

3、推荐学习文章,其实grpc是奔着一个规范去走了,所以在开源项目中所使用grpc的项目有很多,云原生中大量的项目使用grpc

关于序列化和反序列化的思考

关于HTTP2相关知识

XDS标准引入GRPC

关于xsd的学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜  grpc-go git:(master) ✗ tree -L 2
.
├── api ## pb 项目(不同业务组名称是不一样的,像我们组直接叫项目名字直接叫api)
│   ├── Makefile ## 脚本
│   ├── bin ## protoc / protoc-gen-go/ protoc-gen-gofast 脚本用来生成pb文件
│   ├── dto ## 传输层数据
│   ├── go.mod
│   ├── go.sum
│   └── third ## rpc接口,我们这里叫做third
└── service ## 业务项目
├── client.go
├── common ##common
├── go.mods
├── go.sum
├── lbs-service.go
├── service ## 具体业务层
└── user-service.go

3、protobuf 介绍

1、介绍和文档

​ Protocol Buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

2、基础学习

1、基本格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3"; // 默认走的是 proto2,所以需要强制指定,pb3比pb2语法上简单
package dto; //当前文件的package,我们项目不喜欢使用前缀类似于 project-name.dto,因为根本不会存在多个项目互相引用!根据你们需求去决定
import "dto/demo.proto"; // import 其他文件,相对于--proto_path 路径的相对路径

option java_multiple_files = true;
option java_package = "com.grpc.api.third"; //java包的路径
option go_package="api/dto";// go的包路径(切记一定要写全,和go的import有关系,如果写 dto,那么别人引入你就是 dto了,显然是引入不到的,一定要写全)

message SearchRequest {
// 每个字段都有四块组成: 字段规则,类型,名称,编号
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
repeated string hobby = 4;
}

2、类型

  • 字符串:string ,(默认为””)
  • 整型:int32, int64, uint32, uint64 (默认为0)
  • 浮点: double, float (默认为0)
  • 字节流: bytes (默认为nil)
  • 布尔类型:bool ,(默认false)
  • 枚举:enum (默认为0,枚举的第一个值必须是0)
  • map类型: 比如 map<string,string> (默认为nil)
  • oneof 类型,它是一个结构体,但是它定义你结构体中只能赋值一个字段,就算你有多个字段!(默认为nil)

3、编号

​ 消息定义中的每个字段都有一个唯一的编号。这些字段编号用于以消息二进制格式标识字段,在使用消息类型后不应更改。注意,范围1到15中的字段编号需要一个字节进行编码,包括字段编号和字段类型。范围16到2047的字段编号采用两个字节。因此,应该为经常出现的消息元素保留数字1到15。记住为将来可能添加的频繁出现的元素留出一些空间。

  • 大小限制:1-2^29-1 , 19000 through 19999 不能使用

4、字段规则

  • singular,默认类型就是这个,不需要申明
  • repeated, 类似于数组,go里面默认就转换成了数组,是个list所以不会自动去重

5、注释

两种格式,和java/c++/go保持一致

1
2
3
4
5
/**
注释
*/

// 注释

6、删除/保留字段

1
2
3
4
5
6
7
8
9
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
repeated string hobby = 4;

reserved 15, 9 to 11;
reserved "foo", "bar";
}

7、其他

1、支持任意的结构体嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
message Outer {                  // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
2、任意类型

​ 这里代表的不是任意的数据类型,指的是 Message 类型!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax="proto3";
package dto;

option java_multiple_files = true;
option java_package = "com.grpc.api.third";
option go_package="dto";

import "google/protobuf/any.proto";

message City {
uint64 id=1;
string name=2;
// 引用包名称.类型名称
google.protobuf.Any any = 9;
}
3、map类型
1
2
3
4
5
6
7
message City {
uint64 id=1;
string name=2;
// 引用包名称.类型名称
google.protobuf.Any any = 9;
map<string,uint64> demo=10;
}
4、oneof 类型

下面为例子,就是你只能选择 v3 或者 v4 !

1
2
3
4
5
6
7
8
message BenchMarkModel {
string v1=1;
uint64 v2=2;
oneof test_oneof {
string v3 = 3;
uint32 v4 = 4;
}
}

8、import 作用

首先 import 是引用 路径的,那个路径是相对于你的 --proto_path 路径的路径

比如我的项目中,引用就是,项目路径是 /Users/fanhaodong/project/programing/grpc-go/api

1
2
3
4
5
6
bin/protoc \
--proto_path /Users/fanhaodong/project/programing/grpc-go/api \
--proto_path /Users/fanhaodong/go/src/github.com/gogo/protobuf/protobuf \
--plugin=protoc-gen-go=bin/protoc-gen-go \
--go_out=. \
./dto/*.proto

那么我的import 可以来自于两个路径

1
2
import "dto/city.proto";
import "google/protobuf/any.proto";

首先 dto/city.proto ,绝对路径是 /Users/fanhaodong/project/programing/grpc-go/api/dto/city.proto 我们的编译目录是 /Users/fanhaodong/project/programing/grpc-go/api ,所以去除前缀就是 dto/city.proto

然后看google/protobuf/any.proto 也是同理

9、package 作用

主要是区分 package 区分 import 可能引用了相同的 message ,所以就需要 package指定,一般package命令为比如我的目录是 api/dto,所以一般命名为 api.dto ,一般来说跨业务组是不允许相互引用,只能引用一些common的结构

比如下面这种情况,我引用了两个 文件 dto/demo.protogoogle/protobuf/any.proto

demo.proto 定义了 Any 类型

1
2
3
message Any {
string name=1;
}

但是我这时候没有告诉我到底用的哪个 Any,此时编译期无法解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
syntax="proto3";
package dto;

option java_multiple_files = true;
option java_package = "com.grpc.api.dto";
option go_package="dto";

import "google/protobuf/any.proto";
import "dto/demo.proto";

message City {
uint64 id=1;
string name=2;
// 引用包名称.类型名称
Any any = 9;
map<string,uint64> demo=10;
}

可以看到

1
2
3
4
5
6
7
8
9
10
11
type City struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
// 引用包名称.类型名称
Any *Any `protobuf:"bytes,9,opt,name=any,proto3" json:"any,omitempty"`
Demo map[string]uint64 `protobuf:"bytes,10,rep,name=demo,proto3" json:"demo,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
}

所以存在这种问题,此时就需要 package解决引用的问题

1
2
3
4
5
6
7
message City {
uint64 id=1;
string name=2;
// 引用包名称.类型名称
google.protobuf.Any any = 9;
map<string,uint64> demo=10;
}

3、Protobuf的编码算法原理

Encoding

4、编译工具

  • 官方编译工具,比如我是mac os ,可以直接下载 wget https://github.com/protocolbuffers/protobuf/releases/download/v3.9.0/protoc-3.9.0-osx-x86_64.zip
  • gogoprotobuf ,对于需要编码为Golang时,其编码效率贼高而且还省内存, 对立项目是 https://github.com/golang/protobuf
  • 如何编译了 ? --proto_path指定include 目录, --plugin 指定需要用的plugin, 最后一个参数指的是引用的pb文件

使用测试的pb 文件

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
syntax="proto3";
package dto;

option java_multiple_files = true;
option java_package = "com.grpc.api.dto";
option go_package="api/dto";

import "google/protobuf/any.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/wrappers.proto";

message BenchMarkModel {
string v1=1;
uint64 v2=2;
int64 v3=3;
double v4=4;
float v5=5;
bool v6=6;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus v7=7;
map<string,uint32> v8=8;
oneof test_oneof {
string v9 = 9;
uint32 v10 = 10;
}
bytes v11=11;
message Msg {
string content=1;
}
repeated Msg v12=12;
google.protobuf.Any v13 = 13;
google.protobuf.Duration v14=14;
google.protobuf.Empty v15=15;
google.protobuf.UInt64Value v16=16;
google.protobuf.Timestamp v17=17;
}

2、测试代码

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
package dto
import (
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
"math"
"testing"
"time"
)
func newModel(b testing.TB) *BenchMarkModel {
any := &anypb.Any{}
if err := any.MarshalFrom(wrapperspb.UInt64(math.MaxUint64)); err != nil {
b.Fatal(err)
}
return &BenchMarkModel{
V1: "hello 12345424234234",
V2: math.MaxUint64,
V3: math.MaxInt64,
V4: math.MaxFloat64,
V5: math.MaxFloat32,
V6: true,
V7: BenchMarkModel_PRODUCTS,
V8: map[string]uint32{"1": 1, "2": 2, "3": 3},
TestOneof: &BenchMarkModel_V10{
V10: math.MaxUint32,
},
V11: []byte("hello 1234567890"),
V12: []*BenchMarkModel_Msg{
{Content: "1"}, {Content: "2"}, {Content: "3"},
},
V13: any,
V14: durationpb.New(time.Hour * 24),
V15: &emptypb.Empty{},
V16: wrapperspb.UInt64(math.MaxUint64),
V17: timestamppb.Now(),
}
}

func TestGo_Marshal(t *testing.T) {
model := newModel(t)
protoBufBody, err := proto.Marshal(model)
if err != nil {
t.Fatal(err)
}
newModel := &BenchMarkModel{}
if err := proto.UnmarshalMerge(protoBufBody, newModel); err != nil {
t.Fatal(err)
}
assertModel(t, model, newModel)
}

func assertModel(t testing.TB, model *BenchMarkModel, newModel *BenchMarkModel) {
assert.Equal(t, model.V1, newModel.V1)
assert.Equal(t, model.V2, newModel.V2)
assert.Equal(t, model.V3, newModel.V3)
assert.Equal(t, model.V4, newModel.V4)
assert.Equal(t, model.V5, newModel.V5)
assert.Equal(t, model.V6, newModel.V6)
assert.Equal(t, model.V7, newModel.V7)
assert.Equal(t, model.V8, newModel.V8)
assert.Equal(t, model.TestOneof, newModel.TestOneof)
assert.Equal(t, model.V11, newModel.V11)

for index, _ := range model.V12 {
assert.Equal(t, model.V12[index].Content, newModel.V12[index].Content)
}

assert.Equal(t, model.V13.Value, newModel.V13.Value)
assert.Equal(t, model.V13.TypeUrl, newModel.V13.TypeUrl)
assert.Equal(t, model.V14.Nanos, newModel.V14.Nanos)
assert.Equal(t, model.V14.Seconds, newModel.V14.Seconds)
assert.Equal(t, model.V15, newModel.V15)
assert.Equal(t, model.V16.Value, newModel.V16.Value)
assert.Equal(t, model.V17.Seconds, newModel.V17.Seconds)
assert.Equal(t, model.V17.Nanos, newModel.V17.Nanos)
}

1、使用protoc-gen-go

这里需要知道的是 --go_out 需要找到 protoc-gen-go 的位置,如果你的protoc-gen-go放在 PATH目录下就可以直接使用,但是我们一般不会放在PATH目录下,所以需要指定 --plugin=protoc-gen-go=bin/protoc-gen-go,意思就是告诉位置所在

1
2
3
4
5
6
bin/protoc \
--proto_path . \
--proto_path /Users/fanhaodong/go/grpc/include \
--plugin=protoc-gen-go=bin/protoc-gen-go \
--go_out=../ \
./dto/*.proto

帮助

1
2
3
4
5
6
7
8
--plugin=EXECUTABLE         Specifies a plugin executable to use.
Normally, protoc searches the PATH for
plugins, but you may specify additional
executables not in the path using this flag.
Additionally, EXECUTABLE may be of the form
NAME=PATH, in which case the given plugin name
is mapped to the given executable even if
the executable's own name differs.

其实规则就是

--plugin=protoc-gen-go=bin/protoc-gen-go 对应的必须是 --go_out,它走的是拼前缀,比如你执行这个也OK,因为是看前缀说话

1
2
3
4
5
6
bin/protoc \
--proto_path . \
--proto_path /Users/fanhaodong/go/grpc/include \
--plugin=protoc-gen-go1=bin/protoc-gen-go \
--go1_out=../ \
./dto/*.proto

开始进入话题进行benchmark

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func BenchmarkGo_Marshal(b *testing.B) {
model := newModel(b)
for i := 0; i < b.N; i++ {
if _, err := proto.Marshal(model); err != nil {
b.Fatal(err)
}
}
}

func BenchmarkGo_UnMarshal(b *testing.B) {
model := newModel(b)
result, err := proto.Marshal(model)
if err != nil {
b.Fatal(err)
}
newModel := &BenchMarkModel{}
for i := 0; i < b.N; i++ {
if err := proto.UnmarshalMerge(result, newModel); err != nil {
b.Fatal(err)
}
}
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  dto git:(master) ✗ go test -run=none -bench=BenchmarkGo_ -benchmem -count=4 . 
goos: darwin
goarch: amd64
pkg: api/dto
BenchmarkGo_Marshal-12 428138 2624 ns/op 600 B/op 17 allocs/op
BenchmarkGo_Marshal-12 431756 2552 ns/op 600 B/op 17 allocs/op
BenchmarkGo_Marshal-12 418332 2595 ns/op 600 B/op 17 allocs/op
BenchmarkGo_Marshal-12 503637 2520 ns/op 600 B/op 17 allocs/op
BenchmarkGo_UnMarshal-12 537661 2824 ns/op 555 B/op 19 allocs/op
BenchmarkGo_UnMarshal-12 542142 2398 ns/op 554 B/op 19 allocs/op
BenchmarkGo_UnMarshal-12 509076 2420 ns/op 563 B/op 19 allocs/op
BenchmarkGo_UnMarshal-12 544599 2063 ns/op 553 B/op 19 allocs/op
PASS
ok api/dto 11.746s

2、使用 protoc-gen-gofast

1、首先需要安装

1
2
go install github.com/gogo/protobuf/protoc-gen-gofast ## protoc-gen-gofast 工具
go get github.com/gogo/protobuf ## 代码依赖

2、这里使用了Any类型,所以需要我们做gofast的兼容,这点比较坑,官网也写了如何解决:https://github.com/gogo/protobuf , 因此编译命令需要添加一些参数!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bin/protoc \
--proto_path . \
--proto_path /Users/fanhaodong/go/src/github.com/gogo/protobuf/protobuf \
--plugin=protoc-gen-gofast=bin/protoc-gen-gofast \
--gofast_out=\
Mgoogle/protobuf/any.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/field_mask.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/type.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/api.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/descriptor.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/empty.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/source_context.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/wrappers.proto=github.com/gogo/protobuf/types:../ \
./dto/*.proto

3、最坑的来了,也就是它为啥不兼容的问题!

1)原来定义的any现在不能使用了,也就是说api的使用方式上变了!

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
import (
"encoding/json"
"github.com/gogo/protobuf/types"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
"math"
"testing"
"time"
)

func newModel(b testing.TB) *BenchMarkModel {
any, err := types.MarshalAny(&types.UInt64Value{
Value: math.MaxUint64,
})
if err != nil {
b.Fatal(err)
}
return &BenchMarkModel{
V1: "hello 12345424234234",
V2: math.MaxUint64,
V3: math.MaxInt64,
V4: math.MaxFloat64,
V5: math.MaxFloat32,
V6: true,
V7: BenchMarkModel_PRODUCTS,
V8: map[string]uint32{"1": 1, "2": 2, "3": 3},
TestOneof: &BenchMarkModel_V10{
V10: math.MaxUint32,
},
V11: []byte("hello 1234567890"),
V12: []*BenchMarkModel_Msg{{Content: "1"}, {Content: "2"}, {Content: "3"},},
V13: any,
V14: types.DurationProto(time.Hour * 24),
V15: &types.Empty{},
V16: &types.UInt64Value{
Value: math.MaxUint64,
},
V17: types.TimestampNow(),
}
}

3、benchmark

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  dto git:(master) ✗ go test -run=none -bench=BenchmarkGoFast_ -benchmem -count=4 .
goos: darwin
goarch: amd64
pkg: api/dto
BenchmarkGoFast_Marshal-12 1579309 748 ns/op 240 B/op 2 allocs/op
BenchmarkGoFast_Marshal-12 1487350 840 ns/op 240 B/op 2 allocs/op
BenchmarkGoFast_Marshal-12 1389932 765 ns/op 240 B/op 2 allocs/op
BenchmarkGoFast_Marshal-12 1532866 784 ns/op 240 B/op 2 allocs/op
BenchmarkGoFast_UnMarshal-12 1000000 1173 ns/op 382 B/op 7 allocs/op
BenchmarkGoFast_UnMarshal-12 1235286 1001 ns/op 384 B/op 7 allocs/op
BenchmarkGoFast_UnMarshal-12 1083085 1191 ns/op 371 B/op 7 allocs/op
BenchmarkGoFast_UnMarshal-12 1000000 1144 ns/op 382 B/op 7 allocs/op
PASS
ok api/dto 14.907s

3、总结

1、从性能上来看确实提升至少是一倍起步,基本带来了翻倍的收益(官方给的数据是5-10倍的性能提升,它还提供了更快的!1),然后主要是内存分配上可以看到内存优势很大!

2、但从效率上来说其实对于业务开发其实是不关注太多这些的,开发效率和质量决定一切!

3、关于选择protoc-gen-gofast 还是选择 protoc-gen-go ,看你们业务已开始用的什么,如果开始就选择 protoc-gen-gofast 那么可以一直用,但是一开始就选择 protoc-gen-go 那就恶心了,基本上无法切换到 protoc-gen-gofast,可以选择使用 protoc-gen-gogo

4、gRPC

1、介绍和文档

​ gRPC 是一个现代的开源高性能远程过程调用(Remote Procedure Call,RPC)框架,可以在任何环境中运行。它可以高效地连接数据中心内部和跨数据中心的服务,并为负载平衡、跟踪、健康检查和身份验证提供可插拔的支持。它也适用于最后一英里的分布式计算连接设备,移动应用程序和浏览器的后端服务。

2、写rpc接口 (IDL)

第一个lbs接口是:

1
2
3
4
5
6
7
8
9
10
11
12
13
syntax="proto3";
package third;

option java_multiple_files = true;
option java_package = "com.grpc.api.third";
option go_package="api/third";

import "google/protobuf/wrappers.proto";
import "dto/city.proto";

service LbsService {
rpc getCityInfo (google.protobuf.UInt64Value) returns (dto.City);
}

第二个是user服务接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
syntax="proto3";
package third;

option java_multiple_files = true;
option java_package = "com.grpc.api.third";
option go_package="api/third";

import "dto/user.proto";
import "google/protobuf/wrappers.proto";

service UserService {
rpc GetUserInfo (google.protobuf.UInt64Value) returns (dto.User);
}

3、gofast编译rcp接口

如何编译,我们使用的是 gofast进行编译,所以需要修改一些参数,关于参数如何使用的,这些绝对百度不来,主要是看人家这个gofast项目的文档和example

这里就是需要告诉一下编译的时候使用 plugin=grpc, 然后还需要改变一下引用,最后就是指定一下输出目录

格式就是 --{pugin}_out=k1=v1,k2=v2,k3=v3....,kn=vn:{输出目录}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bin/protoc \
--proto_path . \
--proto_path /Users/fanhaodong/go/src/github.com/gogo/protobuf/protobuf \
--plugin=protoc-gen-gofast=bin/protoc-gen-gofast \
--gofast_out=plugins=grpc,\
Mgoogle/protobuf/any.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/field_mask.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/type.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/api.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/descriptor.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/empty.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/source_context.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types,\
Mgoogle/protobuf/wrappers.proto=github.com/gogo/protobuf/types:../ \
./third/*.proto

4、go写接口服务调用

1、服务端代码

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 (
"api/dto"
"api/third"
"context"
"fmt"
"github.com/gogo/protobuf/types"
"google.golang.org/grpc"
"log"
"net"
"service/common"
"time"
)

type lbsServiceServer struct{}

func (lbsServiceServer) GetCityInfo(ctx context.Context, cityId *types.UInt64Value) (*dto.City, error) {
if cityId.Value == 0 {
return nil, fmt.Errorf("not found city: %d", cityId.Value)
}
return &dto.City{
Id: cityId.Value,
Name: fmt.Sprintf("beijing-%d", cityId.Value),
Properties: map[string]string{"time": time.Now().Format(time.RFC3339)},
Msg: &dto.OneofMessage{
TestOneof: &dto.OneofMessage_Name{
Name: "demo",
},
},
}, nil
}

func main() {
// 创建一个tcp listener
lis, err := net.Listen("tcp", common.LbsService)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 创建一个 grpc server
ser := grpc.NewServer()
// 注册信息
third.RegisterLbsServiceServer(ser, lbsServiceServer{})
// 启动服务
if err := ser.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

2、客户端代码

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 main

import (
"api/third"
"context"
"github.com/gogo/protobuf/types"
"google.golang.org/grpc"
"log"
"service/common"
)

func main() {
conn, err := grpc.Dial(common.LbsService, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatal(err)
}
client := third.NewLbsServiceClient(conn)
cityInfo, err := client.GetCityInfo(context.Background(), &types.UInt64Value{Value: 1})
if err != nil {
log.Fatal(err)
}
log.Printf("%s", cityInfo)
}

请求一下可以看到完全可以调通

1
2021/04/16 20:10:48 id:1 name:"beijing-1" properties:<key:"time" value:"2021-04-16T20:10:48+08:00" > msg:<name:"demo" > 

5、如何抓包

1、配置pb位置,比如我的就是在 /Users/fanhaodong/project/programing/grpc-go/api目录下

image-20210416200211693

2、选择抓取网卡

本地的话一般是就是本地回环网络,我的本地网卡就是 lo0

image-20210416200342188

然后选择过滤的端口,比如我刚刚启动的服务端口是 8001, 然后记得客户端调用一次服务端,就可以看到以下的流量包了

image-20210416200918263

此时可以监控到流量,但是是tcp,所以我们很难看懂,需要需要分析一下,因此需要decode一下包

image-20210416201157603

所以大概你就可以看到所有包的情况了!

6、http2

7、grpc conn pool

1、如果发现 [] conn

2、如果发现 conn (http2 http3 )

3、

5、使用go-micro 搭建grpc服务

1、官方文档

https://github.com/Anthony-Dong/go-micro

微解决了在云中构建服务的关键需求。它利用微服务体系结构模式,并提供一组作为平台构建块的服务。微处理分布式系统的复杂性,并提供更简单的可编程抽象。

其实看起来和那个现在比较火的 dapr 很像,抽象的级别很高,更加傻瓜式,但是你要是研究的话往往会增大学习成本

2、快速开始的话,你就根据官方提供的就行了

2、提供的能力

1、基本上人家帮代码给你封装好了,直接用

2、提供api-gateway 方便使用

3、提供有 一些内部提供的服务治理能力,需要细细学习

4、有兴趣可以学习一下

本人坚持原创技术分享,如果你觉得文章对您有用,请随意打赏! 如果有需要咨询的请发送到我的邮箱!