随便聊聊gRPC

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据同步 1个月
简介: 在介绍gRPC之前,首先先来聊一下gRPC当中用到的一个编码Protobuf。

gRPC


%N12C6MDC_{)KP9(Z4K8[6A.jpg

gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

在介绍gRPC之前,首先先来聊一下gRPC当中用到的一个编码Protobuf。


ProtoBuf

image.gifJ7$5K}47ZKKVRCTFY})SD33.png

什么是ProtoBuf

「Protocol Buffers」(简称:「ProtoBuf」)是一种开源跨平台的序列化数据结构的协议。其对于存储资料或在网络上进行通信的程序是很有用的。这个方法包含一个接口描述语言,描述一些数据结构,并提供程序工具根据这些描述产生代码,这些代码将用来生成或解析代表这些数据结构的字节流。

前言

Protobuf有特殊的编码规则。在正式介绍Protobuf的编码规则之前,我们先来回顾一下,之前我们利用网络传输数据常用采取的格式。

JSON

{
    "name": "LittleQ"
}

相信各位读者对于JSON格式应该是相当的熟悉,无论是写过客户端,服务端,爬虫等等吧,涉及到网络的数据传输,大家大概率首选的格式应该就是JSON格式了。

XML

<info>
   <name>LittleQ</name>
<info>

对于XML来说,作为另一个数据传输的格式,相信各位读者至少应该是见过,我们可以看到上面这个例子,里面有个info的对象,里面有一条属性,name他的值为LittleQ

我们来观察上面的两种格式,我们可以非常容易的知道我们所要传输的数据是什么,但是呢,我们来思考这么一个问题,对于网络传输来说,一般都是两个实体进行的数据交换(不考虑哪种多个实体交换的情况,只考虑一个客户端和一个服务端进行通信)。那么双方所能发送或者接受的数据内容应该是提前协商好的,那么我们来看JSON当中的name和xml当中的<info> <name>这些,似乎是可以省略一下,如果说通信的双方协商好了,我可以接收那些字段这样就能节省一大笔空间的开销。

考虑到上面所说的这一点,我们来看一下protobuf的表示格式:

protobuf

0A 07 4C 69 74 74 6C 65 51

protobuf采用了二进制直接传输数据,因此在可读性上面不及JSON或者说XML但是它对于空间和时间开销是相对来说比较低的。

编码规则

FD~S6F54E3ME4T`YF6F8U9J.png

对于Protobuf的第一部分,也就是Tag,前半部分是一个序号,范围是从1到 然后后面三个是类型位,目前只有6种,因此需要3个比特就够了,具体类型如下:

Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimited string, bytes, embedded messages, packed repeated fields
3 Start group groups (deprecated)
4 End group groups (deprecated)
5 32-bit fixed32, sfixed32, float

上面的表格来自于Google的官方文档,我们可以看到3和4是已经废弃了的,因此呢我们只需要看剩下的4中类型就可以了,对于0、1和5这三种类型,是不需要长度的,因为他们的长度可以通过类型本身长度或者某种方式计算得来,后文在给读者来描述Varint类型长度是如何计算的,而对于2这种类型,存的是一个变长类型,因此需要存储长度。


Varint编码规则

Varint是一种紧凑的数字表示方法,它通过一个或者多个字节来表示一个数字,数字越小所使用的字节数越少,这样在某些情况下,比如说数字可能是非常小,也可能是非常大的情况,采用这种方式在数字小的时候就可以少占用字节数,Varint是一种变长编码。下面我们来看几个具体的varint的例子。

0x8(<128数字代表)

对于小于128的数字来说,Varint编码只占用一个字节

[W_H{P]ON6JLNGJO%DGQF)C.png

0x80(>127数字代表)

这里如果说数字大小占用超过7bit,那么会在最高位补1然后继续往下去取,直到剩余bit全为0,最终组合得到最终的编码。

B$MD)~F]9)MS6OXMRKZ}XCT.png

然后简单的来聊一下Varint的解码过程,咱们按照小端来做,从前往后去读,如果最高位是1,那么最低位就取剩余的8bit,然后继续往下去找,如果最高位依然是1继续重复之前的操作,直到最高位为0,最终拼接所有bit得到原始的数字。

-1(负数)

对于负数,一般我们采用补码表示,这导致了,如果我们保存一个负数的话,采用Varint编码之后,会占用5个字节,这对于网络流量寸土寸金的时代,这肯定是不能忍受的,因此对于负数,protobuf定义了sint32和sint64来表示负数,对于负数采用Zigzag编码之后,在执行Varint编码。

%LV}QW%[FU@HLY5][VCBABV.png

image.gif

从上面的例子我们可以看出来,采用Varint编码之后,每个字段所占用的长度是可以计算出来的,因此呢就不需要存长度了。

字符串编码

字符串属于变长类型,因此对于Tag当中的wire_type为2,我们来看一下在最上面提到过的一个例子。

message Info {
  required string name = 1;
}

下面的例子是name的值为LittleQ的时候对应的编码的值。

0A 07 4C 69 74 74 6C 65 51

同样的,我们用图来手动解析一下上面的值。

430@{0_P{N]CQ~~WS}DG4_K.png

image.gif

数组编码

对于数组来说,存在两种形式。一种是添加packed,另一种是不添加packed,对于不添加packed的来说,因为对于Tag的值是冗余的,因此添加之后可以节省一些空间,同样的,我们来看一个具体的例子。

message Info {
   repeated int32 a = 1;
   repeated int32 b = 2 [packed=true];
}

下面的例子是对于

{
  "a": [
    1,
    2,
    3
  ],
  "b": [
    1,
    2,
    3
  ]
}

的protobuf的编码表示

08 01 08 02 08 03 12 03 01 02 03

image.gifT]8RVTR_V[Z3{(4F_[12~F1.png

使用方法

因为protobuf在数据传输过程当中是没有保存具体的字段信息的,因此需要通信双方预先定义好所需要通信的数据格式,一般在.proto当中定义一个或者多个消息类型,从上面讲解的编码类型来看,实际上对应的字段只要类型和序号对了,对于具体的格式可能有多种情况,所以交互双方要去约定具体的.proto文件。


Http2

因为gRPC是基于http2的,在这里不展开http2的解释了,有兴趣的读者可以自行查阅一下相关的rfc。


gRPC

前面啰嗦了这么多,现在终于回到了本篇文章的主题,正式开聊有关gRPC的相关知识。

image.gifAEY76UV4LZS60I(ED](VN$5.png

下面我们来通过一个例子来实现一下gRPC的服务调用,这里服务端我们选择用go来写吧,采用官网给出的一个.proto格式。

// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}
// The response message containing the greetings
message HelloReply {
  string message = 1;
}

GoLang实现gRPC的服务端/客户端

首先来安装一下相关的依赖

go get -u github.com/golang/protobuf/proto
go get -u google.golang.org/grpc

然后我们就可以愉快的编写代码了,最终的代码结构如下

.
├── client
│   └── main.go
├── go.mod
├── go.sum
├── protos
│   ├── greeter.pb.go
│   ├── greeter.proto
│   └── greeter_grpc.pb.go
└── server
    └── main.go

在创建完成.proto文件之后,需要利用protoc工具来生成对应语言的文件,对于Go来说,具体的命令如下:

protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    protos/greeter.proto

执行完成之后,这里会生成greeter.pb.gogreeter_grpc.pb.go两个文件。生成之后,我们就可以来先来编写服务端代码了。

package main
import (
  "context"
  "flag"
  "fmt"
  "log"
  "net"
  pb "gRPCServer/protos"
  "google.golang.org/grpc"
)
var (
  port = flag.Int("port", 10088, "The server port")
)
type server struct {
  pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  log.Printf("Received: %v", in.GetName())
  return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
  flag.Parse()
  lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }
  s := grpc.NewServer()
  pb.RegisterGreeterServer(s, &server{})
  log.Printf("server listening at %v", lis.Addr())
  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}

之后,我们来编写客户端代码,简单调用一下我们上面刚才所写的服务。

package main
import (
  "context"
  "flag"
  "log"
  "time"
  pb "gRPCServer/protos"
  "google.golang.org/grpc"
)
var (
  addr = flag.String("addr", "localhost:10088", "the address to connect to")
)
func main() {
  flag.Parse()
  conn, err := grpc.Dial(*addr, grpc.WithInsecure())
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close()
  c := pb.NewGreeterClient(conn)
  ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  defer cancel()
  r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "LittleQ"})
  if err != nil {
    log.Fatalf("could not greet: %v", err)
  }
  log.Printf("Greeting: %s", r.GetMessage())
}

之后,运行一下就可以成功的使用gRPC了。突然发现好像也没什么能说的了,所以呢,本文到这里就结束了。


总结

本文呢主要是聊了一下有关gRPC的使用和其数据传输主要才用的protobuf的编码规则,总体来说,protobuf对于省空间这件事情应该是做的还是相当不错的,虽然说编码之后的内容咱们开发者可能一眼无法看穿,但是对于计算机来说还是有好的。

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
打赏
0
0
0
0
19
分享
相关文章
社区供稿 | 一张照片跳舞的AnimateAnyone社区开发者复刻版,开源!
日前,兵马俑跳科目三、奶牛猫跳洗澡舞等趣味和魔性的短视频在社交媒体上出圈,背后“一张照片来跳舞”的技术来自阿里通义实验室在可控动画生成领域的一项研究工作——AnimateAnyone。
CAD2023网盘下载直接安装即可使用无需激活AutoCAD
AutoCAD是目前计算机辅助设计领域最流行的CAD软件,此软件功能强大、使用方便,在国内外广泛应用于机械、建筑、家居、纺织等诸多行业。CAD制图软件具有良好的用户界面,通过交互菜单或命令行方式便可以进行各种操作。它的多文档设计环境,让非计算机专业人员也能很快地学会使用。在不断实践的过程中更好地掌握它的各种应用和开发技巧,从而不断提高工作效率。
6821 0
iOS微信分享配置universal links步骤
iOS微信分享配置universal links步骤
1792 58
slb后端服务器故障
slb后端服务器故障
140 13
【Go语言专栏】Go语言中的加密与安全通信
【4月更文挑战第30天】本文介绍了Go语言中的加密与安全通信。通过使用golang.org/x/crypto/ssh/terminal库实现终端加密,以及golang.org/x/net/websocket库实现WebSocket安全通信。文章展示了安装库的命令、加密操作及WebSocket通信的示例代码。此外,还列举了安全通信在数据传输加密、用户认证、密码保护和文件加密等场景的应用。掌握这些知识对开发安全的Web应用至关重要。
171 0
小白带你学习linux的keepalived+lvs和keepalived双机热备(三十七)
小白带你学习linux的keepalived+lvs和keepalived双机热备(三十七)
254 0
TS 自定义结构Long与number类型相互转换
TS 自定义结构Long与number类型相互转换
272 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问