已发表的技术专栏
0 grpc-go、protobuf、multus-cni 技术专栏 总入口
4 grpc、oauth2、openssl、双向认证、单向认证等专栏文章目录)
本节开始介绍grpc服务器端一侧,是如何来处理截止时间Deadline的?
1、服务器端是如何知道客户端是否设置了Deadline呢? |
1.1、客户端一侧,是如何存储超时时间的? |
将超时时间设置在上下文中,如下面语句: |
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*20)
超时时间,设置为20秒
然后经过下面的方法调用链,层层传递到createHeaderField里,最终存储到头帧headerFrame里的hf属性里,然后发送给服务器端: |
main.go→unaryCall→UnaryEcho→UnaryEcho→Invoke →invoke →newClientStream→newStream()→NewStream→createHeaderField
进入grpc-go/internal/transport/http2_client.go方法里:
1.func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr) ([]hpack.HeaderField, error) {
//---省略不相关代码
2. hfLen += len(authData) + len(callAuthData)
3. headerFields := make([]hpack.HeaderField, 0, hfLen)
4. headerFields = append(headerFields, hpack.HeaderField{Name: ":method", Value: "POST"}) // 存储的是 请求类型,如POST
5. headerFields = append(headerFields, hpack.HeaderField{Name: ":scheme", Value: t.scheme}) // 协议类型,如http
6. headerFields = append(headerFields, hpack.HeaderField{Name: ":path", Value: callHdr.Method}) // 请求服务,方法名称,如/helloworld.Greeter/SayHello
//---省略不相关代码
7. if dl, ok := ctx.Deadline(); ok {
8. timeout := time.Until(dl)
9. headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-timeout", Value: encodeTimeout(timeout)})
10. }
主要流程说明:
- 通过第7-9行,获取上下文里设置的Deadline时间,并将此值设置到grpc-timeout里。
- 再存储到切片headerFields里。
- 然后,将headerFields存储到头帧headerFrame里的hf属性里,
- 最后通过帧发送器,将头帧发送给服务器端
1.2、服务器端一侧,是如何读取超时时间的 |
将帧接收器作为分析入口:
当帧接收器接收到头帧后,会交由头帧处理器处理,即
grpc-go/internal/transport/http2_server.go文件中的operateHeaders方法里:
func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream), traceCtx func(context.Context, string) context.Context) (fatal bool) {
streamID := frame.Header().StreamID
state := &decodeState{
serverSide: true,
}
if err := state.decodeHeader(frame); err != nil {
进入decodeHeader方法里:
func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {
//---省略不相关代码
for _, hf := range frame.Fields {
d.processHeaderField(hf)
}
进入processHeaderField方法里:
1.func (d *decodeState) processHeaderField(f hpack.HeaderField) {
2. switch f.Name {
3. case "content-type":
4. //---省略不相关代码
5. case "grpc-status":
6. //---省略不相关代码
7. case "grpc-message":
8. //---省略不相关代码
9. case "grpc-timeout":
10. d.data.timeoutSet = true
11. var err error
12. d.data.timeout, err = decodeTimeout(f.Value);
13. if err != nil {
14. d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed time-out: %v", err)
15. }
16. case ":path":
通过第12行,服务器端从头帧里根据"grpc-timeout"来获取到客户端设置的超时时间值
最终,会根据超时时间,来创建上下文
1.3、服务器端一侧,拿到超时时间后,做了什么操作 |
进入operateHeaders方法里
func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream), traceCtx func(context.Context, string) context.Context) (fatal bool) {
streamID := frame.Header().StreamID
//---省略不相关代码
s := &Stream{
//---省略不相关代码
}
if state.data.timeoutSet {
s.ctx, s.cancel = context.WithTimeout(t.ctx, state.data.timeout)
} else {
s.ctx, s.cancel = context.WithCancel(t.ctx)
}
服务器端拿到超时时间后,会通过 context.WithTimeout(t.ctx, state.data.timeout)来创建上下文,取消函数,并赋值给新创建的Stream。
这样的话服务器端一侧的Deadline就开始计时了。
2、服务器端Deadline到期后,做了什么? |
服务器端一侧,Deadline到期后,只是将错误信息注入到了上下文里,关闭了上下文中的通道。跟客户端一侧一样,就是在WithDeadline设置的;
但是,服务器端一侧,似乎没有监听通道的状态,至少没有找到。
3.服务器端一侧,在接收数据帧时,deadline到期,如何处理? |
服务器端接收到客户端发送的RST帧后的处理逻辑,跟前文取消功能一样,这里就不再赘述了。
4、服务器端一侧,在发送数据帧时,deadline到期,如何处理? |
即使在发送阶段Deadline到期,帧发送器也会继续发送数据,但是,客户端一侧,已经接收失败,开始清理相关流了。
同样,服务器端接收到客户端发送的RST帧后的处理逻辑,跟前文取消功能一样,这里就不再赘述了。
主要是没有找到帧发送器停止发送的逻辑。
下一篇文章
假设在一条调用链上,存在多个grpc服务的调用,如A服务调用B服务调用C服务,那么他们的超时时间如何?
点击下面的图片,返回到专栏大纲 |