http2 goway frame 与 grpc graceful restart
1、http/2
HTTP/2新增特性
- 二进制分帧(HTTP Frames)
- 多路复用
- 头部压缩
- 服务端推送(server push)
- 等等新特性
二进制分帧(HTTP Frames)是实现一流多用的基础
HTTP/2最革命性的原因就在于这个二进制分帧了,要了解二进制分帧在客户端和服务端传输的过程,需要了解三个概念:
- Frame,帧,HTTP/2协议里通信的最小单位,每个帧有自己的格式,不同类型的帧负责传输不同的消息
- Message, 消息,类似Request/Response消息,每个消息包含一个或多个帧
- Stream,流,建立链接后的一个双向字节流,用来传输消息,每次传输的是一个或多个帧
HTTP/2里边,这些概念的关系是这样的:
- 所有的通信都在一个tcp链接上完成,会建立一个或多个stream来传递数据
- 每个stream都有唯一的id标识和一些优先级信息,客户端发起的stream的id为单数,服务端发起的stream id为偶数
- 每个message就是一次Request或Response消息,包含一个或多个帧,比如只返回header帧,相当于HTTP里HEAD method请求的返回;或者同时返回header和Data帧,就是正常的Response响应。
- Frame是最小的通信单位,承载着特定类型的数据,例如 Headers, Data, Ping, Setting等等。 来自不同stream的frame可以交错发送,然后再根据每个Frame的header中的数据流标识符重新组装。
http/2的请求和响应多路复用
HTTP/2 中新的二进制框架层消除了这些限制,并通过允许客户端和服务器将 HTTP 消息分解为独立的帧、交织它们,然后在另一端重新组合它们,实现了完整的请求和响应多路复用。
例如:快照捕获同一连接内正在运行的多个流。客户端正在向服务器传输一个DATA帧(流 5),而服务器正在为流 1 和 3 向客户端传输一个交错的帧序列。因此,存在三个并行流。
将 HTTP 消息分解为独立帧、交织它们,然后在另一端重新组合它们的能力是 HTTP/2 最重要的增强功能。事实上,它在所有 Web 技术的整个堆栈中引入了众多性能优势的连锁反应,使我们能够:
- 并行交错多个请求而不阻塞任何一个。
- 并行交错多个响应而不阻塞任何一个。
- 使用单个连接并行传送多个请求和响应。
- 通过消除不必要的延迟和提高可用网络容量的利用率来缩短页面加载时间。
流优先级
一旦 HTTP 消息可以拆分为许多单独的帧,并且我们允许来自多个流的帧被多路复用,客户端和服务器交错和交付帧的顺序就成为一个关键的性能考虑因素。为了促进这一点,HTTP/2 标准允许每个流具有关联的权重和依赖性:
- 每个流都可以分配一个介于 1 和 256 之间的整数权重。
- 每个流都可以明确依赖于另一个流。
流依赖性和权重的组合允许客户端构建和传达一个“优先级树”,该树表示它希望如何接收响应。反过来,服务器可以使用此信息通过控制 CPU、内存和其他资源的分配来确定流处理的优先级,并且一旦响应数据可用,分配带宽以确保将高优先级响应以最佳方式传递给客户端。
2、http/2 goway frame
goway frame作用:
当服务端需要维护时,发送一个GOAWAY的Frame给客户端,那么发送之前的Stream都正常处理了,发送GOAWAY后,客户端会新启用一个链接,继续刚才未完成的Stream发送。这样就可以做到完全不影响运行中的业务而进行服务端维护。
它是如何做到这一点的呢,来看下GOAWAY的帧结构:
+-+-------------------------------------------------------------+
|R| Last-Stream-ID (31) |
+-+-------------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------------+
| Additional Debug Data (*) |
+---------------------------------------------------------------+
最明显的就是这个Last-Stream-ID,GOAWAY包含在此连接中已经或可能在发送端点上处理的最后一个对等启动Stream的ID标识符.例如,如果服务器发送GOAWAY帧,则识别的流是客户端发起的编号最高的流。
通过这个标识,双方就知道上次传输成功的一个Stream Id是多少,再重新发送数据的时候,就知道从哪个数据开始发送。避免了数据的丢失或者重复。
3、grpc graceful restart原理
goaway帧发挥的作用:
goAway 帧相当于服务器端主动给客户端发送的连接关闭的信号,客户端收到这个信号后,将会关闭该连接上所有的 HTTP2 的流。这样客户端侧可以主动感知到连接关闭,同时不会继续发送新的请求过来。
3-1、grpc graceful restart的服务端实现:
golang grpc server提供了两个退出方法:
一个是stop,一个是gracefulStop。stop方法相比gracefulStop来说,减少了goaway帧的发送,等待连接的退出。
服务端gracefulStop的原理:
首先close listen fd,这样就无法建立新的请求,然后遍历所有的当前连接发送goaway帧信号。goaway帧信号在http2用来关闭连接的。
3-2、grpc graceful restart的客户端实现:
grpc客户端会new一个协程来执行reader方法,一直监听新数据的到来,当帧类型为goaway时调用handleGoAway,该方法会调用closeStream关闭当前连接的所有活动stream。对于开发者来说,只需监听grpc接口中的ctx就得到状态变更。
通常该连接不可用后,如客户端再次进行unary或streming请求时,grpc会按照规则来实例化新的连接,比如通过dns或者grpc balancer来地址变更。
4、如何合理使用grpc graceful restart
应用监听OS信号,启动平滑关闭:
知道 gRPC框架提供的服务平滑关闭的方法后,与HTTP服务的平滑关闭一样,我们的应用要能接收到OS发来的TERM 、Interrupt之类的信号,然后主动去触发调用GracefulStop进行服务的平滑关闭,当然调用平滑关闭前我们还可以做一些其他应用内的首尾工作,比如应用使用Etcd实现的服务注册,那么这里我建议要先去主动的把节点的IP对应的Key从Etcd上注销掉,如果Key不能及时过期,那么客户端做负载均衡时没有收到这个节点IP删除的通知就仍有可能会往要关闭的端点上发请求。
example:
errChan := make(chan error)
stopChan := make(chan os.Signal)
signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
go func() {
if err := grpcServer.Serve(lis); err != nil {
errChan <- err
}
}()
select {
case err := <- errChan:
panic(err)
case <-stopChan:
// TODO 做一些项目自己的清理工作
DoSomeCleanJob()
// 平滑关闭服务
grpcServer.GracefulStop()
}
5、grpc keepalived也是用goway frame 来结束idle connection
在一个grpc server上开启http debug即可看到在connection处于idle的时候,会有goway frame的交互,以结束处于idle状态太久的连接,以释放资源:GODEBUG=http2debug=2
示例:
测试server空闲连接回收===============================
2022/12/25 13:55:39 http2: Framer 0x140000d6000: wrote PING len=8 ping="\x02\x04\x10\x10\t\x0e\a\a"
2022/12/25 13:55:39 http2: Framer 0x140000d6000: read PING flags=ACK len=8 ping="\x02\x04\x10\x10\t\x0e\a\a"
2022/12/25 13:55:44 http2: Framer 0x140000d6000: read GOAWAY len=8 LastStreamID=2147483647 ErrCode=NO_ERROR Debug=""
2022/12/25 13:55:44 http2: Framer 0x140000d6000: read PING len=8 ping="\x01\x06\x01\b\x00\x03\x03\t"