grpc-go源码剖析六十之服务器端一侧,是如何处理截止时间deadline呢?

已发表的技术专栏
0  grpc-go、protobuf、multus-cni 技术专栏 总入口

1  grpc-go 源码剖析与实战  文章目录

2  Protobuf介绍与实战 图文专栏  文章目录

3  multus-cni   文章目录(k8s多网络实现方案)

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方法里:

1func (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

 //---省略不相关代码
7if 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方法里:

1func (d *decodeState) processHeaderField(f hpack.HeaderField) {
2switch f.Name {
3case "content-type":
4//---省略不相关代码
5case "grpc-status":
6//---省略不相关代码
7case "grpc-message":
8//---省略不相关代码
9case "grpc-timeout":
10.		d.data.timeoutSet = true
11var err error
12.		d.data.timeout, err = decodeTimeout(f.Value);
13if err != nil {
14.			d.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed time-out: %v", err)
15}
16case ":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服务,那么他们的超时时间如何?

点击下面的图片,返回到专栏大纲

gRPC-go源码剖析与实战之专栏大纲

gRPC-go源码剖析与实战感谢

  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码二哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值