gRPC协议简介
gRPC 是谷歌开源的一套 RPC 协议框架。主要做两件事情:一是数据编码,二是请求映射。
数据编码
数据编码顾名思义就是在将请求的内存对像转化成可以传输的字节流发给服务端,并将收到的字节流再转化成内存对像。方法有很多,常见的有 XML、JSON、Protobuf。XML 已经日薄西山,JSON 风头正盛,Protobuf 则方兴未艾。gRPC 默认选用 Protobuf,早期貌似只支持 Protobuf,现在号称也支持 JSON 了,但不知道有多少人在用。
为什么选 Protobuf 呢? Protobuf 在某些场景下的效率要比 JSON 高一些。请大家牢记,天下没有免费的午餐,所有的优化都是有代价的。我们在考虑问题的时候一定要思考选择什么和放弃什么。
JSON
要理解 Protobuf 的优化,我们就需要回过头来看 JSON 有什么缺点。这是一段典型的 JSON
{ "int":12345, "str": "hello", "bool": true }
{ "int":67890, "str": "hello", "bool": false }
头一个缺点是非字符串的编码低效。比如 int 字段的值是 12345,内存表示只占两个字节,转成 JSON 却要五个字节。 bool 字段则占了四或五个字节。
再一个缺点就是信息冗余。同一个接口同一个对像,只是 int 字段的值不同,每次都还要传输"int"这个字段名。
等等,这是缺点吗?是!可 JSON 为什么会有这些毛病呢?因为 JSON 在可读性和编码效率之间选择了可读性,所以效率方面做了一定的牺牲。
Protobuf
好了,现在人们觉得效率是主要矛盾了,那就必然会牺牲可读性。为此,Protobuf 一方面选用了 VarInts 对数字进行编码,解决了效率问题;另一方面给每个字段指定一个整数编号,传输的时候只传字段编号,解决了冗余问题。更多细节可参考文章
在传输的时候只传了字段编号固然可以提高传输效率,但接收方如何知道各个编号对应哪个字段呢?只能事先约定了。就像当年地下工作者一样,一人拿一个密码本。Protobuf 使用 .proto 文件当密码本,记录字段和编号的对应关系
message Demo {int32 i = 1;string s = 2;bool b = 3;
}
Protobuf 提供了一系列工具,为 proto 描述的 message 生成各种语言的代码。传输效率上去了,工具链也更加复杂了。如果你给 gRPC 通信抓过包,你一定会怀念 JSON 的。
好了,数据编码问题到此告一段落,我们继续讨论请求映射问题。
因为有 .proto 作为 IDL,Protobuf 确实可以做很多 JSON 不方便做的事情。其中最重的就是 RPC 描述!
package demo.hello;service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}
}message HelloRequest {string name = 1;
}message HelloReply {string message = 1;
}
上面的 .proto 文件定义了一个 Greeter 服务,其中有一个 SayHello 的方法,接受 HelloRequest 消息并返回 HelloReply 消息。如何实现这个 Greeter 则是语言无关的,所以叫 IDL。gRPC 就是用了 Protobuf 的 service 来描述 RPC 接口的。
请求映射
接口路径
那问题来了,gRPC 如何映射请求呢?要回答这个问题,首先要回答 gRPC 在底层使用什么传输协议。答案是 HTTP 协议,准确的说,gRPC 使用的是 HTTP/2 协议。不过就我们现在讨论的内容而言,我们暂时可以忽略 HTTP/2 和 HTTP/1 区别。
现在你可以简单认为一个 gRPC 请求就是一个 HTTP 请求(不严格)。这个 HTTP 请求用的是 POST 方法,对应的资源路径则是根据 .proto 定义确定的。我们前文提到的 Greeter 服务对应的路径是/demo.hello.Greeter/SayHello
。
一个 gRPC 定义包含三个部分,包名、服务名和接口名,连接规则如下
/${包名}.${服务名}/${接口名}
SayHello的包名是demo.hello,服务名是Greeter,接口名是SayHello,所以对应的路径就是 /demo.hello.Greeter/SayHello。如此的朴实无华!
gRPC 协议规定Content-Type header 的取值为application/grpc,当然也可以写成application/grpc+proto。如果你想使JSON 编码,也可以设成application/grpc+json,只要服务支持都行。
消息格式
最后就要确定请求 body 的定义了。如果用的 Protobuf 编码,那 body 肯定是编码后的字节流。那 gRPC 的 HTTP 请求是不是这样呢?
POST /demo.hello.Greeter/SayHello HTTP/1
Host: grpc.demo.com
Content-Type: application/grpc
Content-Length: 1234<protobuf bytes>
答案是否定的!简单来说,gRPC 要求在 Protobuf 字节流前面加一个五字节的前缀,第一个字节表示字节流是否被压缩,后四个字节存储数据长度,并取名叫作 Length-Prefixed Message。
熟悉 HTTP 协议的同学都清楚,HTTP 协议本身可以通过 Content-Encoding 表示压缩算法,使用 Content-Length 指定数据长度。gRPC 为什么要重新定义一套机制呢?
流式接口
答案在于 gRPC 支持的另一特性 stream rpc!为方便行文,我们称之为流式接口。所谓流式,就是可以源源不断收发消息。这个跟 HTTP 的一收一发有着显著的差别。
service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}rpc SayHello (stream HelloRequest) returns (HelloReply) {}rpc SayHello (HelloRequest) returns (stream HelloReply) {}rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
}
gRPC 持三种流式接口,定义的办法就是在参数前加上 stream 关键字,分别是:请求流、响应流和双向流。
第一种叫请求流,可以在 RPC 发起之后不断发送新的请求消息。此类接口最典型的使用场景是发推送或者短信。
第二种叫响应流,可以在 RPC 发起之后不断接收新的响应消息。此类接口最典型的使用场景是订阅消息通知。
最后一种是双向流。可以在 RPC 发起之后同时收发消息。此类接口最典型的使用场景是实时语音转字幕。
为了实现流式传输,gRPC 不得不引入所谓的 Length-Prefixed Message。同一个 gRPC 请求的不同消息共用 HTTP 头信息,所以只能给每个消息单独加一个五字节的前缀来表示压缩和长度信息了。
就是因为这五个字节,不管你是 Protobuf 还是 JSON,都注定了 gRPC 只能是二进制协议,UNIX 下常用的文本工具都无法很好地处理 gRPC 的通信内容。
返回状态
gRPC 还定义了自己的返回状态和消息,分别用 grpc-status 和 grpc-message 头传输。所以最简单的 gRPC 通信(非流式调用,unary)内容长成这个样子
请求内容
POST /demo.hello.Greeter/SayHello HTTP/1.1
Host: grpc.demo.com
Content-Type: application/grpc
Content-Length: 1234<Length-Prefixed Message>
响应内容
HTTP/1.1 200 OK
Content-Length: 5678
Content-Type: application/grpc<Length-Prefixed Message>
如果你真的理解前文所讲的内容,那么你现在可以写一个非流式 gRPC 的客户端了。 sniper 框架就自带了一个,源码在这里。
gRPC vs HTTP
最后讲一下 gRPC 跟 HTTP 协议的关系。如果不熟悉HTTP协议可以阅读另一篇文章。
如果单看非流式调用,也就是 unary call,gRPC 并不复杂,跟普通的 HTTP 请求也没有太大区别。我们甚至可以使用 HTTP/1.1 来承载 gRPC 流量。但是 gRPC 支持流式接口,这就有点难办了。
我们知道,HTTP/1.1 也是支持复用 TCP 连接的。但这种复用有一个明显的缺陷,所有请求必须排队。也就是说一定要按照请求、等待、响应、请求、等待、响应这样的顺序进行。先到先服务。而在实际的业务场景中肯定会有一些请求响应时间很长,客户端在收到响应之前会一直霸占着TCP连接。在这段时间里别的请求要么等待,要么发起新的 TCP 连接。在效率上确实有优化的余地。一言以蔽之,HTTP/1.1 不能充分地复用 TCP 连接。
后来,HTTP/2 横空出世!通过引入 stream 的概念,解决了 TCP 连接复用的问题(注意,这里同样有取舍问题,不展开了)。你可以把 HTTP/2 的 stream 简单理解为逻辑上的 TCP 连接,可以在一条 TCP 连接上并行收发 HTTP 消息,而无需像 HTTP/1.1 那样等待。
所以 gRPC 为了实现流式特性,选择使用 HTTP/2 进行通信。所以,前文的 Greeter 调用的实际通信内容长这个样子。
请求内容
HEADERS (flags = END_HEADERS) # header frame
:method = POST
:scheme = http
:path = /demo.hello.Greeter/SayHello
:authority = grpc.demo.com
content-type = application/grpc+protoDATA (flags = END_STREAM) # data frame
<Length-Prefixed Message>
响应内容
HEADERS (flags = END_HEADERS) # header frame
:status = 200
content-type = application/grpc+protoDATA # data frame
<Length-Prefixed Message>HEADERS (flags = END_STREAM, END_HEADERS) # header frame
grpc-status = 0
HTTP/2 的 header 和 data 使用独立的 frame(中文译作帧,简单来说也是一种 Length-Prefixed 消息,是 HTTP/2 通信的基本单位) 发送,可以多次发送。HTTP/1.1 只能先发 header 再发 data(不完全准确。自己查,提示 http trunk),HTTP/2 可以交替发送。比如上文中的 gRPC 响应,先发一个 header frame,告知 http 状态;再发一个 data frame,传输 gRPC 消息;最后又发了一个 header frame,告知 grpc-status 状态,这是 gRPC 自定义的状态码。
慢着!一般不是先发 header 再发 data 的吗?为什么 gRPC 需要在发完 data 之后才发 grpc-status 头呢?
还是流式接口导致的问题。你想呀,在所有的流式消息没有传输完成之前,服务端也不知道要传什么 grpc-status 呀。
总结
好了,到这里请求映射的问题也分析完了。让我们回到最开始的问题。
如果 gRPC 好,它好在哪里?为了这些好,它又牺牲了哪些方面?我们的业务面临什么问题?gRPC 的优点能否为我所用?gRPC 的缺点会不会给我们带来不便?
这些问题在这篇文章中有过全面的分析,有兴趣的同学可以移步阅读
相关文章:
gRPC协议简介
gRPC 是谷歌开源的一套 RPC 协议框架。主要做两件事情:一是数据编码,二是请求映射。 数据编码 数据编码顾名思义就是在将请求的内存对像转化成可以传输的字节流发给服务端,并将收到的字节流再转化成内存对像。方法有很多,常见的…...
[dp+dfs]砝码称重
题目描述 现有 n n n 个砝码,重量分别为 a 1 , a 2 , … , a n a_1, a_2, \ldots,a_n a1,a2,…,an ,在去掉 m m m 个砝码后,问最多能称量出多少不同的重量(不包括 0 0 0 )。 输入格式 第一行为有两个整数…...
MYSQL-查看表中字段属性语法(三)
查看表中字段全部信息 show full columns from database_name.table_name; show full columns from table_name;示例 mysql> show full columns from world.city; ----------------------------------------------------------------------------------------------------…...
第三讲 part 3:前端处理LINK3D - 代码解析 - 从main出发看总体流程(ROS1改为ROS2)
目录 1. ROS1 ->ROS21.1 包含头文件1.2 全局变量定义1.3 结构体定义1.4 点云容器定义1.5 图像处理相关变量1.6 ROS2发布者和订阅者定义1.7 全局变量,被不断更新1.8 点云处理相关变量1.9 图像描述符1.10 主函数1.10.1. 初始化ROS21.10.2. 创建节点1.10.3. 声明参数1.10.4. 设…...

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——15.红黑树
1.红黑树的概念 红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,…...
【C++】Eclipse技巧汇总
Eclipse C/C调试无法输入 在debug C/C程序时,Eclipse自带的窗口,无法读取cin等输入 解决办法: 参考:https://blog.csdn.net/sagjhdj/article/details/123271383 思路是调用外部console: 依次点击Debug>Debug Conf…...

Golang | Leetcode Golang题解之第430题扁平化多级双向链表
题目: 题解: func dfs(node *Node) (last *Node) {cur : nodefor cur ! nil {next : cur.Next// 如果有子节点,那么首先处理子节点if cur.Child ! nil {childLast : dfs(cur.Child)next cur.Next// 将 node 与 child 相连cur.Next cur.Chi…...

Java实现找色和找图功能
某天,张三接到一个任务需求,将一个Excel表格里面的员工信息,录入到员工系统里面,由于数据量非常大,操作起来巨慢。经过一段时间的操作和观察,他发现这种操作,非常有规律,基本就是一些…...
linux脚本工具
目录 shell工具查看Nvidia GPU状态查看某个监听端口是否存在设置局部代理查找关键字相关进程根据日常所需,持续更新 shell工具 减少重复性工作,简化工作流程,提高工作效率 将所编写的shell脚本赋予可执行权限 chmod x <脚本文件> 在…...

MySQL之基础篇
数据库操作 1.查看当前的数据库版本 select version(); 2.显示所有数据库 show databases; 3.创建数据库 create [if not exists] database 数据库名 character set 字符编码集 collate 排序规则; 我们这里提前说一下 被方括号括起来的代码 表示可写可不写 示例…...

13年408计算机考研-计算机网络
第一题: 解析:OSI体系结构 OSI参考模型,由下至上依次是:物理层-数据链路层-网络层-运输层-会话层-表示层-应用层。 A.对话管理显然属于会话层, B.数据格式转换,是表示层要解决的问题,很显然答案…...
camera2 + MediaRecorder 实现的分段循环录像功能
硬件设备Android系统 8.1; 硬件设备上开发过程中的问题记录: 问题1. 长时间录像后发现保存的录像文件始终只有4G。 原因及解决:Android 11之前的系统有对保存的文件大小有限制,所以只能修改成分段保存,即录像文件3.…...
LeetCode 每日一题 2024/9/23-2024/9/29
记录了初步解题思路 以及本地实现代码;并不一定为最优 也希望大家能一起探讨 一起进步 目录 9/23 2414. 最长的字母序连续子字符串的长度9/24 2207. 字符串中最多数目的子序列9/25 2306. 公司命名9/26 2535. 数组元素和与数字和的绝对差9/27 2516. 每种字符至少取 K…...

知识付费APP开发指南:基于在线教育系统源码的技术详解
本篇文章,我们将探讨基于在线教育系统源码的知识付费APP开发的技术细节,帮助开发者和企业快速入门。 一、选择合适的在线教育系统源码 选择合适的在线教育系统源码是开发的关键一步。市场上有许多开源和商业化的在线教育系统源码,开发者需要…...

物联网智能项目全面解析
目录 引言 一、物联网概述 1.1 什么是物联网 1.2 物联网的历史与发展 二、物联网智能项目分类 三、关键组件与技术 3.1 传感器和执行器 3.2 连接技术 3.3 数据处理与分析 3.4 用户界面 四、物联网智能项目案例分析 4.1 智能家居 4.2 智慧城市 4.3 工业物联网 4.4…...

【07】纯血鸿蒙HarmonyOS NEXT星河版开发0基础学习笔记-Swiper轮播组件与样式结构重用
序言: 本文详细讲解了关于我们在页面上经常看到的轮播图在鸿蒙开发中如何用Swiper实现,介绍了Swiper的基本用法与属性,及如何面对大段的重复代码进行封装和重用(Extend、Styles、Builder),使代码更加简洁易…...
Springboot3保存日志到数据库
保存日志到数据库 请求日志几乎是所有大型企业级项目的必要的模块,请求日志对于我们来说后期在项目运行上线一段时间用于排除异常、请求分流处理、限制流量等。请求日志一般都会记录请求参数、请求地址、请求状态(Status Code)、SessionId、…...

叉车高位显示器无线摄影,安装更加便捷!
叉车叉货,基本功能,但货叉升降高度确不一定,普通的3米左右,高的十几米,特别是仓储车,仓库叉货空间小,环境昏暗,视线受阻严重,司机叉货升的那么高怎么准确无误的插到货呢&…...
模板的特化
模板的特化 1.概念2.函数模板特化3.类模板的特化3.1 全特化3.2 偏特化3.2.1 部分特化3.2.2 参数更进一步的限制 4.总结 1.概念 在原模板类的基础上,针对特殊类型所进行特殊化的实现方式 2.函数模板特化 步骤 1.必须要先有一个基础的函数模板 2.关键字 template后面接…...
PCIE总线架构
1 概述 PCIe总线(Peripheral Component Interconnect Express)是一种高速串行计算机扩展总线标准,它是基于PCI总线的一种升级版,现在已经被广泛应用于各种高性能的计算机和服务器系统中。 PCIe总线提供更高的数据传输速度和更先进的特性,它主要特点如下: 高带宽:提供比…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...

如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...