gRPC-Go -- #7 链路级别流控 sendQuota
在向 HTTP/2 链路发送数据帧时,发送方不能无限制地发送数据。本文将介绍 gRPC-Go 在链路级别的流量控制机制,即何时允许发送数据,何时需要暂停。
loopyWriter
loopyWriter
的主要功能是向 HTTP/2 链路发送数据帧,其中的 sendQuota
字段负责链路级别的流量控制,表示链路的发送窗口大小。初始化时,sendQuota
使用 defaultWindowSize
,默认值为 65535,且没有配置项可以修改。
// The default value of flow control window size in HTTP2 spec.
const defaultWindowSize = 65535
type loopyWriter struct {
...
sendQuota uint32
...
}
唯一能够修改 sendQuota
默认值的是 BDP。当 BDP 发生变化时,会发送窗口大小更新帧,streamID
为 0 表示这是链路级别的发送窗口更新。
func (t *http2Server) updateFlowControl(n uint32) {
...
t.controlBuf.put(&outgoingWindowUpdate{
streamID: 0,
increment: t.fc.newLimit(n),
})
...
}
接收到该帧后,sendQuota
会相应调整,注意 increment
可以是正数或负数。
func (l *loopyWriter) incomingWindowUpdateHandler(w *incomingWindowUpdate) {
// Otherwise update the quota.
if w.streamID == 0 {
l.sendQuota += w.increment
return
}
...
}
消耗 sendQuota
在发送数据前,会检查 sendQuota
是否充足,若发送成功,sendQuota
会按发送的数据大小减少相应的配额。
func (l *loopyWriter) processData() (bool, error) {
// sendQuota 不足,直接返回
if l.sendQuota == 0 {
return true, nil
}
...
l.sendQuota -= uint32(size)
...
}
补充 sendQuota
接收方在 handleData
方法中处理收到的 http2 数据帧时,可能会发送窗口更新帧以补充 sendQuota
的配额。具体满足什么条件会发送呢,请听下回分解。
func (t *http2Server) handleData(f *http2.DataFrame) {
...
// 接收数据时可能会发送窗口更新帧
if w := t.fc.onData(size); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{
streamID: 0,
increment: w,
})
}
// 发送 bdpPing 前可能会发送窗口更新帧
if sendBDPPing {
// Avoid excessive ping detection (e.g. in an L7 proxy)
// by sending a window update prior to the BDP ping.
if w := t.fc.reset(); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{
streamID: 0,
increment: w,
})
}
t.controlBuf.put(bdpPing)
}
...
}
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。