1.goè¯è¨grpcçstream 使ç¨
2.Go语言 grpc
3.gRPC 介绍及 Go gRPC 入门教程
4.gRPC 流量控制详解
goè¯è¨grpcçstream 使ç¨
ä¹åæä»¬è®²äº grpc æä¹ç®åçä½¿ç¨ ï¼è¿æ¬¡è®²è®² grpc ä¸ç streamï¼srteam 顾åæä¹ å°±æ¯ ä¸ç§ æµï¼å¯ä»¥æºæºä¸æç æ¨é æ°æ®ï¼å¾éå ä¼ è¾ä¸äºå¤§æ°æ®ï¼æè æå¡ç«¯ å 客æ·ç«¯ é¿æ¶é´ æ°æ®äº¤äºï¼æ¯å¦ 客æ·ç«¯ å¯ä»¥å æå¡ç«¯ 订é ä¸ä¸ªæ°æ®ï¼æå¡ç«¯ å°± å¯ä»¥å©ç¨ stream ï¼æºæºä¸æå° æ¨éæ°æ®ã
å ¶å®è¿ä¸ªæµ å·²ç» åºæ¬éåæ tcpäºï¼grpc åºå±ä¸ºæ们 åå äºï¼æ以ççå¾æ¹ä¾¿ã
æä»¬å¨ protobuf éé¢ å®ä¹ è¦æä¾çæå¡ï¼å¦æ ä½ æ³æåªä¸ªæ°æ® æºæºä¸æç æ¨é å°±å¨åé¢å 个stream 就好äº,修改å®ä¹å¥½è®°å¾ç¼è¯ã
ç¥è¯ç¹ï¼
client è°ç¨ æµçå½æ°ï¼ å°±ä¼ è¿åä¸ä¸ª æµå¯¹è±¡ï¼åªè¦ ä¸æå° å¯¹å®è¿è¡è¯»åæè åå ¥ï¼å¯¹åºæ¹å°±è½æ¶å°ã
grpc ç stream å goçåç¨ é å ç®ç´å®ç¾ãéè¿æµ æ们 å¯ä»¥æ´å çµæ´»ç å®ç°èªå·±çä¸å¡ãå¦ è®¢é ï¼å¤§æ°æ®ä¼ è¾çã
Go语言 grpc
gRPC 是一个高性能、跨平台、源码源码开源的分析远程过程调用(RPC)框架,它面向移动和 HTTP/2 设计,修改提供 C/C++、源码源码Java、分析完整小程序源码下载Python、修改Ruby、源码源码C#、分析PHP、修改Node.js 和 Go 等语言版本,源码源码几乎支持所有编程语言。分析 gRPC 基于 HTTP/2 标准,修改具备诸如双向流、源码源码流控、分析威联通源码头部压缩和单 TCP 连接上的多复用请求等特性,使其在移动设备上表现出色,节省电量和空间。 接下来,我们将介绍 gRPC 的核心概念。 什么是 gRPC? 在 gRPC 中,客户端应用能够像调用本地方法一样直接调用另一台机器上服务端应用的方法,简化了分布式应用和服务的创建。它基于定义服务、方法及其参数和返回类型的基础理念。在服务端实现这些方法并运行 gRPC 服务器来处理客户端调用。客户端则拥有一个与服务端方法相似但未实现的存根,通过该存根调用服务端方法。 gRPC 使用的宪法离我们源码协议 默认使用 protocol buffers,这是由 Google 开发的结构化数据序列化机制,也可使用 JSON 等其他数据格式。不过,通常推荐使用 protocol buffers,因其灵活性和高效性。 服务定义 使用 gRPC 需先定义服务,包含可被远程调用的方法及其参数和返回类型。服务可以被理解为服务端 API 接口的集合,提供功能。 以下是使用 gRPC 定义服务的示例。 单向 RPC 客户端发送请求至服务端,服务端返回应答,类似于普通函数调用。 服务端流式 RPC 客户端发送请求至服务端,开发源码笔记接收一系列消息流,直至无更多消息为止。客户端可以持续读取直到消息流结束。 客户端流式 RPC 客户端通过消息流向服务端发送一系列消息,待服务端读取后返回应答。 双向流式 RPC 客户端和服务端可独立通过读写数据流发送消息,双方可按任意顺序读写消息,如服务端在写应答前等待所有客户端消息,或先读消息再写消息。 关于 Go 语言 gRPC 教程,包括安装 go 语言 gRPC 包、安装 protobuf 编译器、定义服务、编译 proto 协议文件、矩形优排源码实现服务端和客户端代码等步骤。具体流程如下: 安装 Go 语言 gRPC 包 确保已安装 Go 编译器。 安装 protobuf 编译器 下载并安装 protoc 编译器,需将其路径添加至系统环境变量。 安装 gRPC 编译器插件 因当前版本的 protoc 缺少 Go 语言代码生成器,需单独安装 gRPC 编译器插件。 例子目录结构 创建目录结构,用于存放示例代码。 定义服务 通过 protobuf 语法定义服务接口,包含方法及其参数和返回类型。 编译 proto 协议文件 使用 protoc 编译器将 proto 文件转换为 Go 语言代码。 实现服务端代码 编写并运行服务端代码,实现服务接口。 实现客户端代码 编写并运行客户端代码,调用服务接口。 完成上述步骤后,即可实现基于 Go 语言的 gRPC 应用。gRPC 介绍及 Go gRPC 入门教程
gRPC 是一个开源的远程过程调用(RPC)框架,旨在简化分布式系统的开发。它支持跨环境的高效连接,提供负载均衡、追踪、健康检查和身份验证功能。适用于连接数据中心内部和跨数据中心服务,以及连接设备、移动应用、浏览器与后端服务。gRPC 的核心基于 Protocol Buffers(IDL),支持多种编程语言。
在 gRPC 中,服务通过接口定义语言(IDL)描述,使用 Protocol Buffers 定义服务接口和负载消息的结构。客户端和服务器通过生成的代码进行交互,支持同步和异步调用。gRPC 还支持四种服务方法类型,包括一元 RPC、服务端流、客户端流和双向流。
元数据是用于特定 RPC 调用的键-值对列表,包含如身份验证等信息。通道提供连接至指定主机和端口上的 gRPC 服务,用于创建客户端存根。gRPC 支持通道状态和关闭逻辑,以及超时、取消和错误处理机制。
为了开始使用 gRPC,需要安装 Protocol Buffer 编译器插件和 Go 语言的 gRPC 插件。然后创建测试项目,定义服务和方法,生成客户端和服务端代码。服务端实现服务方法,客户端调用服务。
在 Go 中,通过定义 service 和方法,使用 protoc 编译器生成 gRPC 客户端和服务端接口。实现服务方法,如 GetFeature、ListFeatures、RecordRoute 和 RouteChat,然后启动服务端并创建客户端存根。调用服务方法,如 GetFeature、ListFeatures、RecordRoute 和 RouteChat,执行 RPC 调用。
通过使用 gRPC,可以在多种环境中实现服务间的高效通信,同时享受 Protocol Buffers 的序列化优势。通过快速入门和基础教程,可以逐步了解如何定义服务、生成代码、实现服务端和客户端,以及调用服务方法。gRPC 的强大功能和跨语言支持使其成为构建分布式系统的理想选择。
gRPC 流量控制详解
gRPC 流量控制详解
流量控制, 一般来说指的是在网络传输中, 发送者主动限制自身发送数据的速率或发送的数据量, 以适应接收者处理数据的速度. 当接收者的处理速度较慢时, 来不及处理的数据会被存放在内存中, 而当内存中的数据缓存区被填满之后, 新收到的数据就会被扔掉, 导致发送者不得不重新发送, 就会造成网络带宽的浪费.
流量控制是一个网络组件的基本功能, 我们熟知的 TCP 协议就规定了流量控制算法. gRPC 建立在 TCP 之上, 也依赖于 HTTP/2 WindowUpdate Frame 实现了自己在应用层的流量控制.
在 gRPC 中, 流量控制体现在三个维度:
采样流量控制: gRCP 接收者检测一段时间内收到的数据量, 从而推测出 on-wire 的数据量, 并指导发送者调整流量控制窗口.
Connection level 流量控制: 发送者在初始化时被分配一个 quota (额度), quota 随数据发送减少, 并在收到接收者的反馈之后增加. 发送者在耗尽 quota 之后不能再发送数据.
Stream level 流量控制: 和 connection level 的流量控制类似, 只不过 connection level 管理的是一个发送者和一个接收者之间的全部流量, 而 stream level 管理的是 connection 中诸多 stream 中的一个.
在本篇剩余的部分中, 我们将结合代码一起来看看这三种流量控制的实现原理和实现细节.
本篇中的源代码均来自 /grpc/grpc-go, 并且为了方便展示, 在不影响表述的前提下截断了部分代码.
流量控制是双向的, 为了减少冗余的叙述, 在本篇中我们只讲述 gRPC 是如何控制 server 所发送的流量的.
gRPC 中的流量控制仅针对 HTTP/2 data frame.
采样流量控制原理采样流量控制, 准确来说应该叫做 BDP 估算和动态流量控制窗口, 是一种通过在接收端收集数据, 以决定发送端流量控制窗口大小的流量控制方法. 以下内容翻译自 gRPC 的一篇官方博客, 介绍了采样流量控制的意义和原理.
BDP 估算和动态流量控制这个 feature 缩小了 gRPC 和 HTTP/1.1 在高延迟网络环境下的性能差距.
Bandwidth Delay Product (BDP), 即带宽延迟积, 是网络连接的带宽和数据往返延迟的乘积. BDP 能够有效地告诉我们, 如果充分利用了网络连接, 那么在某一刻在网络连接上可以存在多少字节的数据.
计算 BDP 并进行相应调整的算法最开始是由 @ejona 提出的, 后来由 gRPC-C Core 和 gRPC-Java 实现. BDP 的想法简单而实用: 每次接收者得到一个 data frame, 它就会发出一个 BDP ping frame (一个只有 BDP 估算器使用的 ping frame). 之后, 接收者会统计指导收到 ACK 之前收到的字节数. 这个大约在 1.5RTT (往返时间) 中收到的所有字节的总和是有效 BDP1.5 的近似值. 如果该值接近当前流量窗口的大小 (例如超过 2/3), 接收者就需要增加窗口的大小. 窗口的大小被设定为 BDP (所有采样期间接受到的字节总和) 的两倍.
BDP 采样目前在 gRPC-go 的 server 端是默认开启的.
结合代码, 一起来看看具体的实现方式.
代码分析我们以 client 发送 BDP ping 给 server, 并决定 server 端的流量控制窗口为例.
在 gRPC-go 中定义了一个bdpEstimator , 是用来计算 BDP 的核心:
type?bdpEstimator?struct?{ //?sentAt?is?the?time?when?the?ping?was?sent.sentAt?time.Timemu?sync.Mutex//?bdp?is?the?current?bdp?estimate.bdp?uint//?sample?is?the?number?of?bytes?received?in?one?measurement?cycle.sample?uint//?bwMax?is?the?maximum?bandwidth?noted?so?far?(bytes/sec).bwMax?float//?bool?to?keep?track?of?the?beginning?of?a?new?measurement?cycle.isSent?bool//?Callback?to?update?the?window?sizes.updateFlowControl?func(n?uint)//?sampleCount?is?the?number?of?samples?taken?so?far.sampleCount?uint//?round?trip?time?(seconds)rtt?float}bdpEstimator 有两个主要的方法 add 和 calculate :
//?add?的返回值指示?是否发送?BDP?ping?frame?给?serverfunc?(b?*bdpEstimator)?add(n?uint)?bool?{ b.mu.Lock()defer?b.mu.Unlock()//?如果?bdp?已经达到上限,?就不再发送?BDP?ping?进行采样if?b.bdp?==?bdpLimit?{ return?false}//?如果在当前时间点没有?BDP?ping?frame?发送出去,?就应该发送,?来进行采样if?!b.isSent?{ b.isSent?=?trueb.sample?=?nb.sentAt?=?time.Time{ }b.sampleCount++return?true}//?已经有?BDP?ping?frame?发送出去了,?但是还没有收到?ACKb.sample?+=?nreturn?false}add 函数有两个作用:
决定 client 在接收到数据时是否开始采样.
记录采样开始的时间和初始数据量.
func?(t?*ing?flow?control?windows//?for?the?transport?and?the?stream?based?on?the?current?bdp//?estimation.func?(t?*ingWindowUpdateHandler?负责处理来自?client?的?window?update?framefunc?(l?*loopyWriter)?incomingWindowUpdateHandler(w?*incomingWindowUpdate)?error?{ if?w.streamID?==?0?{ //?增加?quotal.sendQuota?+=?w.incrementreturn?nil}......}sendQuota 在接收到来自 client 的 window update 后增加.
//?processData?负责发送?data?frame?给?clientfunc?(l?*loopyWriter)?processData()?(bool,?error)?{ ......//?根据发送的数据量减少?sendQuotal.sendQuota?-=?uint(size)......}并且 server 在发送数据时会减少 sendQuota .
Client 端//?add?的返回值指示?是否发送?BDP?ping?frame?给?serverfunc?(b?*bdpEstimator)?add(n?uint)?bool?{ b.mu.Lock()defer?b.mu.Unlock()//?如果?bdp?已经达到上限,?就不再发送?BDP?ping?进行采样if?b.bdp?==?bdpLimit?{ return?false}//?如果在当前时间点没有?BDP?ping?frame?发送出去,?就应该发送,?来进行采样if?!b.isSent?{ b.isSent?=?trueb.sample?=?nb.sentAt?=?time.Time{ }b.sampleCount++return?true}//?已经有?BDP?ping?frame?发送出去了,?但是还没有收到?ACKb.sample?+=?nreturn?false}0trInFlow 是 client 端控制是否发送 window update 的核心. 值得注意的是 client 端是否发送 window update 只取决于已经接收到的数据量, 而管这些数据是否被某些 stream 读取. 这一点是 gRPC 在流量控制中的优化, 即因为多个 stream 共享同一个 connection, 不应该因为某个 stream 读取数据较慢而影响到 connection level 的流量控制, 影响到其他 stream.
//?add?的返回值指示?是否发送?BDP?ping?frame?给?serverfunc?(b?*bdpEstimator)?add(n?uint)?bool?{ b.mu.Lock()defer?b.mu.Unlock()//?如果?bdp?已经达到上限,?就不再发送?BDP?ping?进行采样if?b.bdp?==?bdpLimit?{ return?false}//?如果在当前时间点没有?BDP?ping?frame?发送出去,?就应该发送,?来进行采样if?!b.isSent?{ b.isSent?=?trueb.sample?=?nb.sentAt?=?time.Time{ }b.sampleCount++return?true}//?已经有?BDP?ping?frame?发送出去了,?但是还没有收到?ACKb.sample?+=?nreturn?false}1这里 limit * 1/4 的限制其实是可以浮动的, 因为 limit 的数值会随着 server 端发来的 window update 而改变.
Stream level 流量控制原理Stream level 的流量控制和 connection level 的流量控制原理基本上一致的, 主要的区别有两点:
Stream level 的流量控制中的 quota 只针对单个 stream. 每个 stream 即受限于 stream level 流量控制, 又受限于 connection level 流量控制.
Client 端决定反馈给 server window update frame 的时机更复杂一点.
Stream level 的流量控制不光要记录已经收到的数据量, 还需要记录被 stream 消费掉的数据量, 以达到更加精准的流量控制. 实际上, client 会记录:
pendingData: stream 收到但还未被应用消费 (未被读取) 的数据量.
pendingUpdate: stream 收到且已经被应用消费 (已被读取) 的数据量.
limit: stream 能接受的数据上限, 被初始为 字节, 受到采样流量控制的影响.
delta: delta 是在 limit 基础上额外增加的数据量, 当应用试着去读取超过 limit 大小的数据是, 会临时在 limit 上增加 delta, 来允许应用读取数据.
Client 端的逻辑是这样的:
每当 client 接收到来自 server 的 data frame 的时候, pendingData += 接收到的数据量 .
每当 application 在从 stream 中读取数据之前 (即 pendingData 将被消费的时候),