随便聊聊gRPC

本文涉及的产品
数据传输服务 DTS,同步至DuckDB 3个月
简介: 在介绍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对于省空间这件事情应该是做的还是相当不错的,虽然说编码之后的内容咱们开发者可能一眼无法看穿,但是对于计算机来说还是有好的。

相关实践学习
自建数据库迁移到云数据库
本场景将引导您将网站的自建数据库平滑迁移至云数据库RDS。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业务,无需过多担心数据库的管理和维护。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
相关文章
|
存储 Rust 并行计算
【密码学】一文读懂XTS模式
这篇文章的灵感来源于我偶然翻到的一个某U盘有关磁盘加密的一个介绍(这一篇不是广告蛤), 然后发现这个模式我之前还真没遇到过,因此呢,就学习了一下,就出来了这一篇文章。
8427 0
【密码学】一文读懂XTS模式
|
并行计算 算法 搜索推荐
简单学习一下AES算法:GCM、ECB、CFB、OFB等
简单学习一下AES算法:GCM、ECB、CFB、OFB等
3092 0
|
4月前
|
JSON 搜索推荐 API
淘宝商品评论 API 返回数据参考(附解析与实战示例)
淘宝商品评论 API(核心接口如 taobao.item.review.get)是获取商品用户评价数据的官方通道,返回数据以 JSON 格式为主,结构规范且字段丰富,涵盖评论基础信息、用户画像、内容详情、多媒体信息等维度。本文将拆解通用返回结构、核心字段含义、多场景示例及解析注意事项,为开发者提供完整的数据参考指南。
|
算法 安全 Go
【密码学】一文读懂HKDF
我这又来水一篇文章,来聊一下HKDF(基于HMAC的密钥导出函数)。密钥派生函数是密钥管理的组成部分,他的目标是通过一些初始的数据派生出来密码学安全的随机密钥。
4256 1
【密码学】一文读懂HKDF
|
数据采集 数据可视化 机器人
FastGPT 社区版快速部署指南
FastGPT 是一款基于大语言模型的智能知识库系统,具备开箱即用、可视化编排和多场景适配(客服机器人、知识检索等)的核心能力。通过阿里云计算巢,用户可在 2-3 分钟内完成快速部署,实现复杂问答逻辑设计与高效数据处理。
|
数据采集 存储 JSON
【专栏】网络爬虫与数据抓取的基础知识,包括爬虫的工作原理、关键技术和不同类型
【4月更文挑战第27天】本文介绍了网络爬虫与数据抓取的基础知识,包括爬虫的工作原理、关键技术和不同类型。通过实例展示了如何构建简单爬虫,强调实战中的环境搭建、目标分析及异常处理。同时,文章探讨了法律、伦理考量,如尊重版权、隐私保护和合法用途,并分享了应对反爬策略。最后,倡导遵守数据抓取道德规范,以负责任的态度使用这项技术,促进数据科学的健康发展。
1728 2
|
JSON API 开发者
1688店铺所有商品API接口(1688API系列)
1688店铺所有商品API接口允许开发者通过输入店铺ID,获取指定店铺内的全部商品信息,包括名称、价格、库存、图片和销售数据等。该接口支持排序和分页参数,返回JSON格式数据,便于解析和应用。Python示例展示了如何使用requests库发送GET请求并处理响应,助力电商数据分析与业务拓展。
|
Web App开发 安全 算法
真实世界的密码学(二)(4)
真实世界的密码学(二)
405 2
|
Rust 算法 JavaScript
【密码学】密码学相关资料整理
感觉我也写了不少的文章了,这里整理一下,之后这个整理会佛系更新,手动狗头,具体的链接查看原文获取吧,因为这个链接好像加不进去。
【密码学】密码学相关资料整理

热门文章

最新文章

下一篇
开通oss服务