grpc是基于http/2协议的高性能的rpc框架
师傅领进门,修行在个人,跟着官方脚手架demo了grpc后,之后就需要扩展前后知识边界,下面总结grpc的前世今生和最佳实践。
https://www.cnblogs.com/JulianHuang/p/14441952.html
- grpc是基于http/2协议的高性能的rpc框架
- 为什么已经有http? 还需要grpc?
- 八股文都说grpc是基于http2的rpc框架,到底利用了http2的什么特性 ?
- 一个grpc内存泄漏的例子
grpc是基于http/2协议的高性能的rpc框架。

提取句式中关键信息:rpc框架、 http2、 高性能
1.落地宾语-rpc框架
远程过程调用 remote process call;
程序可以像调用本地函数和本地对象一样, 达成调用远程服务的效果(不用意识到是远程服务),rpc屏蔽了底层的通信细节和打解包细节。
跟许多rpc协议一样, grpc也是基于IDL(interface define lauguage)来定义服务协议。
rpc的开发模式, 必然强调契约优先, client和server端首先约定service的结构(包括一系列方法的组合、每个方法具体签名)。
对这个结构的描述,gRPC 默认是用Protocol Buffer去实现的。
syntax = "proto3";
option csharp_namespace = "GrpcAuthor";
package greet;
// The greeting 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;
}
rpc框架两个关键通用部件:
- channel: 通信信道
- stub: 是client的抽象,中文名叫存根。
① 建立与grcp服务端的通信信道;
② 基于步骤①的信道使用服务名Greet建立grpc服务在客户端的存根, 就像服务Greet是本地服务一样;
③ 用步骤②的grpc服务存根发起grpc调用GreeterClient(),就像GreeterClient是本地方法一样。
var channel = GrpcChannel.ForAddress(
"http:///my-example-host",
new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure });
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "world" });
rpc 是一种久远的通信框架, http是通信协议。
2. 目前市面流行http, 为什么还需要grpc?
其实rpc框架比 http协议更早出来。
tcp协议于70年代诞生,tcp是一种可靠的、面向连接的、基于字节流的传输层协议。
tcp粘包?
关于tcp的定语和修饰词也很关键,正因为是字节流,就如同躺在水流中的0/1串, 这些0/1串是没有任何边界的,应用层传到tcp层的数据不是以消息报为单位向对面主机发送,而是以字节流的形式流淌在tcp层, 在tcp传输层由tcp层的协议进行切割和组包, 对端接收的时候没能正确还原发送端的消息报。
tcp粘包问题并不是tcp协议的弊端,而是我们在应用层发送和接收数据需要对数据分段,而传输层是无边界的0/1串之间的矛盾。
在70到90年代之后,互联网并不发达,很多都是client、server点对点的传输,所以在那个年代rpc很活跃,client、server双方约定服务结构,以[类本地调用]的形态通信。
90年代,随着it产业的蓬勃发展,计算机走进了千家万户,90年代初期诞生了浏览器, 浏览器与上面的C/S结构最大的不同是访问的服务端对象是千万家不同的服务提供方。
再像C/S那样一对一提前沟通契约就不合适了, 故浏览器和web服务器作为一种特殊的C/S需要约定一种【固定的、能自表述的传输格式】, 于是诞生了适用于B/S端的http协议。 http协议不再有rpc双方已知的本地服务名和服务方法, 服务和服务方法被金演化成对远程主机的资源请求。
所以,我们该问的不是“既然有HTTP协议,为什么要有RPC”,而是应该问“为什么有rpc,还需要有http”。
从上面的前世今生可以知道, rpc是通信框架,http是通信协议,rpc可以基于tcp,udp,http/2协议来实现。
grpc在众多rpc框架中脱颖而出,取决于底层的http2基础设施。
3. grpc到底利用了http/2的什么特性?
回过头来看grpc的连两个定语 ① http/2 ② 高性能。
grpc底层传输使用http/2,http/2兼容http1.1语义,还有如下优势
| http2 | http1.1 |
|---|---|
| 用于数据传输的二进制分帧 | http1.1是基于文本协议 |
| 同一tcp连接支持流式传输,故支持发送多个并行请求、调用 | 应答模型:http1.1在一个tcp连接上完成[请求/响应]是串行的 |
| 减少网络使用率的标头压缩 | 头部带有大量信息,每次都要重复发送 |
http2的二进制分帧、流式传输 能力支撑了grpc框架近乎本地的实时服务互调;
http2的多路复用(单tcp连接上并发多个请求,不多占文件描述符)、二进制编码协议、头部压缩支撑了grpc本地互调的高性能。
这里要指出:
- HTTP/2 上的通信只需要在一个TCP连接完成,在这个连接上可见的形态是帧frame和流stream。

- 而消息(或者说业务调用,业务上的逻辑发送单位)由一或多个帧组成,这些帧可以乱序发送,然后根据每个帧首部的流标识符重新组装。

- "gRPC双向流通信streaming"与"http2的流式stream传输"是一个东西吗?
http2流式传输stream是一种底层的传输方式,其作用是支撑单连接多路复用 。
grpc流式通信streaming,更接近业务通信级别的通信方式,grpc流式通信可用于替代高性能场景下的一元gRPC调用。
我们假设HTTP/2协议中1次RPC请求使用1个并发Stream,每个RPC消息又可 通过帧体中 Length-Prefixed Message 头部确立了边界,这样,在 Stream 中连续地发送多个 DATA 帧,就可以实现流模式 RPC。
https://juejin.cn/post/7249522846211801147

tcp连接现在是性能瓶颈
底层的http2协议给予了grpc很大的性能表现,但同时也带来了新的性能瓶颈, 现在现在压力给到了tcp连接。
通常情况下,一个HTTP/2 tcp连接中流的数量是有限制的,一般服务器默认为100,不同的语言有不同的应对策略。
.net
.net tcp连接上默认流数量为100,当该连接中的grpc调用导致到达"连接流的限制",新的grpc调用会进行排队,这个时候会出现因Concurrent stream=100引起的性能问题。
.NET引入了
EnableMultipleHttp2Connections可在此时产生新的连接对象。
var channel = GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions
{
HttpHandler = new SocketsHttpHandler
{
EnableMultipleHttp2Connections = true,
// ...configure other handler settings
}
});
golang
[golang tcp连接上流数量的默认限制也是100](https://github.com/grpc/grpc-go/blob/master/internal/transport/http2_client.go)defaultMaxStreamsClient =100.
可通过ServerOption: MaxConcurrentStreams修改, 从实现上默认的并发流的限制,被设计成池的概念,
类似与C# 也有利用tcp连接池来规避该问题的方案, 按下不表。
https://segmentfault.com/a/1190000041716350?utm_source=sf-similar-article
https://github.com/shimingyah/pool
我们先来分析grpc基于http2 stream概念发起调用的源码。
创建一个gRPC 客户端连接,会创建的几个协程:
1)transport.loopyWriter.run 往服务端发送数据协程
2)transport.http2Client.reader 读取服务端数据协程
http2Client的基础结构
// 源码所在文件:google.golang.org/grpc/http2_client.go
// http2Client 实现了接口 ClientTransport
// http2Client implements the ClientTransport interface with HTTP2.
type http2Client struct {
conn net.Conn // underlying communication channel
loopy *loopyWriter // 生产和消费关联的队列在这里面,所在文件:controlbuf.go
// controlBuf delivers all the control related tasks (e.g., window
// updates, reset streams, and various settings) to the controller.
controlBuf *controlBuffer // 所在文件:controlbuf.go
maxConcurrentStreams uint32
streamQuota int64
streamsQuotaAvailable chan struct{}
waitingStreams uint32
initialWindowSize int32
}
type controlBuffer struct {
list *itemList // 队列
}
type loopyWriter struct {
// 关联上 controlBuffer,
// 消费 controlBuffer 中的队列 list,
// 生产 由http2Client通过controlBuffer 进行。
cbuf *controlBuffer
}
创建grpc客户端的行为
func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts ConnectOptions, onPrefaceReceipt func(), onGoAway func(GoAwayReason), onClose func()) (_ *http2Client, err error) {
conn, err := dial(connectCtx, opts.Dialer, addr.Addr)
t.controlBuf = newControlBuffer(t.ctxDone) // 含发送队列的初始化
if t.keepaliveEnabled {
t.kpDormancyCond = sync.NewCond(&t.mu)
go t.keepalive() // 保活协程
}
// Start the reader goroutine for incoming message. Each transport has
// a dedicated goroutine which reads HTTP2 frame from network. Then it
// dispatches the frame to the corresponding stream entity.
go t.reader()
// Send connection preface to server.
n, err := t.conn.Write(clientPreface)
go func() {
t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst)
err := t.loopy.run()
}
}
特别说明:
每一次 gRPC 调用,客户端均会创建一个新的 Stream,
该特性使得同一 gRPC 连接可以同时处理多个调用。请求的发送并不是同步的,而是基于队列的异步发送。
// 源码所在文件:internal/transport/controlbuf.go
func (l *loopyWriter) run() (err error) {
// 通过 get 间接调用 dequeue 和 dequeueAll
for {
it, err := l.cbuf.get(true)
if err != nil {
return err
}
if err = l.handle(it); err != nil {
return err
}
if _, err = l.processData(); err != nil {
return err
}
}
}
每一个gRPC客户端连接均有一个自己的队列,gRPC 并没有直接限定队列大小,所以如果不加任何限制则会内存暴涨,直到OOM发生。
这里我们结合内存泄漏的案例加深tcp连接在grpc调用中的独特作用。
go func() {
for {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
resp, err := c.election.Leader(ctx)
if err != nil {
log.WithError(err).Errorf("get leader error. test")
} else {
log.Infof("get test leader success : %s", string(resp.Kvs[0].Value))
}
// cancel()
}
}()
pprof 显示这个election.Leader函数导致的内存持续增长。

bug表象
etcd v3 发起的请求为grpc请求,
因为从closed信道能持续读取零值,形成死循环。
死循环中,用于上下文释放资源的defer cancel() 无法得到执行,因为是在函数级别。
每次grpc调用均形成缓慢内存泄漏。
grpc是基于http/2协议的高性能的rpc框架的更多相关文章
- 基于netty轻量的高性能分布式RPC服务框架forest<下篇>
基于netty轻量的高性能分布式RPC服务框架forest<上篇> 文章已经简单介绍了forest的快速入门,本文旨在介绍forest用户指南. 基本介绍 Forest是一套基于java开 ...
- 基于netty轻量的高性能分布式RPC服务框架forest<上篇>
工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...
- gRPC:Google开源的基于HTTP/2和ProtoBuf的通用RPC框架
gRPC:Google开源的基于HTTP/2和ProtoBuf的通用RPC框架 gRPC:Google开源的基于HTTP/2和ProtoBuf的通用RPC框架 Google Guava官方教程(中文版 ...
- nightwatchjs 基于nodejs&& webdriver 协议的自动化测试&&持续集成框架
nightwatchjs 是基于nodejs&& webdriver 协议的自动化测试&&持续集成框架 参考架构 参考资料 http://nightwatchjs.or ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇
基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇 前提 最近对网络编程方面比较有兴趣,在微服务实践上也用到了相对主流的RPC框架如Spring Cloud Gateway底层也切换 ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇
前提 前置文章: Github Page:<基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> Coding Page:<基于Netty和SpringBoot实现 ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇
前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> 前 ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理
前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> & ...
- MediatRPC - 基于MediatR和Quic通讯实现的RPC框架,比GRPC更简洁更低耦合,开源发布第一版
大家好,我是失业在家,正在找工作的博主Jerry.作为一个.Net架构师,就要研究编程艺术,例如SOLID原则和各种设计模式.根据这些原则和实践,实现了一个更简洁更低耦合的RPC(Remote Pro ...
- 高性能Java RPC框架Dubbo与zookeeper的使用
https://blog.csdn.net/qq_38982845/article/details/83795295
随机推荐
- DEDEBIZ禁止发布重复标题文章的方法
修改文件位置 /admin/article_add.php 找到 if (empty($click)) $click = ($cfg_arc_click == '-1' ? mt_rand(1000, ...
- Solution Set -「CF 1485」
「CF 1485A」Add and Divide Link. 贪心.枚举 \([b,b+\log_{2}\text{range}]\) 然后取个 \(\min\). #include<cstdi ...
- 兴达易控modbus转profinet网关三菱变频器通讯
兴达易控modbus转profinet网关与三菱变频器通讯 本案例分享兴达易控modbus转profinet网关(MDPN100)连接西门子1200plc,实现三菱变频器485通讯兼容转modbusT ...
- C#开源且免费的Windows桌面快速预览神器 - QuickLook
前言 今天给大家推荐一款由C#开源且免费的Windows桌面快速预览神器:QuickLook. 工具介绍 QuickLook是一款在Windows操作系统上的实用工具,它提供了一种快速预览文件内容的方 ...
- destoon根据目录下的html文件生成地图索引
因为项目需要,destoon根据目录下的html文件生成地图索引,操作方法,代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...
- PPT图片处理教程5大抠图方法:堪比PS!
PPT图片处理教程5大抠图方法:堪比PS! .如何在制作PPT的过程中提取图片.比如,当你准备给妹子做一份PPT的时候,妹子想要把N多图片放在里面,但又不想做的太LOW,这要怎么解(gai)咯?如果你 ...
- 一次考试的简单T3
我的第一个想法其实是毫无头绪 根本就想不到dp,直接就写了爆搜后来讲了才知道... 这种dp的状态好像是一类dp的模型,他们的状态都有这样的一维:以第i个数结尾.这样的dp有什么样的标志呢?以第i个数 ...
- CF755C
题目简化和分析: 这题不用说怎么分析了吧,这一看就是个并查集求连通分量个数的经典模板. 我们需要将 \(i\) 和 \(p_i\) 进行合并. 遍历每个 \(i\) 与 \(i+1\) 是否属于同一个 ...
- Epic资源转到unity的方法
众所周知,unity中的素材主要是通过unity资源商店获取的.但是unity资源商店的白嫖机会太少了,而隔壁UE的Epic资源商店就有每月免费的资源,不白嫖成何体统?但是UE咱也不会用啊,白嫖的资源 ...
- 14. 从零开始编写一个wmproxy(代理,内网穿透等), HTTP文件服务器的实现过程及参数
用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP中的压缩gzip,deflate,brotli算法 项目 ++wmproxy++ gite: https://gitee.com/ ...