grpc链接池(6)超时的设置和传递
我们经常看到下面的日志: rpc error: code = DeadlineExceeded desc = context deadline exceeded
我们需要思考两个问题:1,这个错误码来源是哪里?2,超时是如何设置和生效的?
首先我们看下第一个问题:我们可以发现这段错误文案是golang源码里的错误文案:src/context/context.go var DeadlineExceeded error = deadlineExceededError{} func (deadlineExceededError) Error() string { return "context deadline exceeded" }
什么时候会返回这个错误呢?同样是golang源码的context包里 func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { if cur, ok := parent.Deadline(); ok && cur.Before(d) { // The current deadline is already sooner than the new one. return WithCancel(parent) } dur := time.Until(d) if dur <= 0 { c.cancel(true, DeadlineExceeded) // deadline has already passed return c, func() { c.cancel(false, Canceled) } } if c.err == nil { c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) } }func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
了解了上面的背景后,我们就可以排查grpc-go的client在何时使用了 WithTimeout
google.golang.org/grpc@v1.50.1/clientconn.go type ClientConn struct { dopts dialOptions }func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) { if cc.dopts.timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout) defer cancel() } }
可以看到,在发起连接的时候会有,当server超过超时时间没有响应的时候就会报上面的错误。
第二个地方就是我们发送请求的时候,我先会获取一个连接 func invoke(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error { cs, err := newClientStream(ctx, unaryStreamDesc, cc, method, opts...)func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (_ ClientStream, err error) { var newStream = func(ctx context.Context, done func()) (iresolver.ClientStream, error) { return newClientStreamWithParams(ctx, desc, cc, method, mc, onCommit, done, opts...) } rpcConfig, err := cc.safeConfigSelector.SelectConfig(rpcInfo)func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc serviceconfig.MethodConfig, onCommit, doneFunc func(), opts ...CallOption) (_ iresolver.ClientStream, err error) { if mc.Timeout != nil && *mc.Timeout >= 0 { ctx, cancel = context.WithTimeout(ctx, *mc.Timeout) } else { ctx, cancel = context.WithCancel(ctx) }
可以看到,如果方法配置了超时,在超时时间完成之前,没有响应,也会报错。
还有没有其它地方可以配置超时呢?答案是肯定的,Interceptor里我们也可以定义超时。下面就是我们常用的两种设置的超时的方法,分别是连接维度和请求方法维度。 clientConn, err := grpc.Dial(serverAddress, grpc.WithTimeout(5 * time.Second), grpc.WithInsecure()) if err != nil { log.Println("Dial failed!") return err }c := pb.NewGreeterClient(conn) c.SayHello(context.Background(), &pb.HelloRequest{Name: "world"}, WithForcedTimeout(time.Duration(10)*time.Second))
那么上述设置是如何生效的?如何传递到服务端的呢?先看下
grpc.WithTimeout 源码位于google.golang.org/grpc@v1.50.1/dialoptions.go func WithTimeout(d time.Duration) DialOption { return newFuncDialOption(func(o *dialOptions) { o.timeout = d }) }
它修改了dialOptions的timeout type dialOptions struct { timeout time.Duration }type DialOption interface { apply(*dialOptions) }
而dialOptions是ClientConn的一个属性 type ClientConn struct { dopts dialOptions }
我们发起连接的时候用的就是这个属性上的timeout func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) { if cc.dopts.timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout) defer cancel() } }
Interceptor 是如何让超时生效的呢,逻辑更简单,我们看下它的定义,在发起真正调用之前先调用Interceptor ,这个时候设置超时时间: func TimeoutInterceptor(t time.Duration) grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { timeout := t if v, ok := getForcedTimeout(opts); ok { timeout = v } if timeout <= 0 { return invoker(ctx, method, req, reply, cc, opts...) } ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() return invoker(ctx, method, req, reply, cc, opts...) } }func getForcedTimeout(callOptions []grpc.CallOption) (time.Duration, bool) { for _, opt := range callOptions { if co, ok := opt.(TimeoutCallOption); ok { return co.forcedTimeout, true } } return 0, false }
而超时时间是我们发起调用的时候通过option传递下来的 type TimeoutCallOption struct { grpc.EmptyCallOption forcedTimeout time.Duration } func WithForcedTimeout(forceTimeout time.Duration) TimeoutCallOption { return TimeoutCallOption{forcedTimeout: forceTimeout} }
弄清楚客户端的超时时间是如何设置和生效的以后,服务端怎么保证,客户端超时以后,马上终止当前任务呢?回答这个问题之前,我们看下超时是如何传递的。首先,给出答案:grpc协议将超时时间放置在HTTP Header 请求头里面。客户端设置的超时时间为5秒,http2的header如下 grpc-timeout: 4995884u
其中u表示时间单位为纳秒,4995884u 约等于 5秒。然后服务端接收到该请求后,就可以根据这个时间计算出是否超时,由header 超时设置。
那么header是何时由client设置的,以及何时由服务端解析的呢?
google.golang.org/grpc@v1.50.1/internal/transport/http2_client.go
发起客户端请求的时候会调用 func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*Stream, error) { headerFields, err := t.createHeaderFields(ctx, callHdr)
内部我们可以看到,它从context里面取出超时截止时间,然后写入header "grpc-timeout" 里面 func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr) ([]hpack.HeaderField, error) { if dl, ok := ctx.Deadline(); ok { // Send out timeout regardless its value. The server can detect timeout context by itself. // TODO(mmukhi): Perhaps this field should be updated when actually writing out to the wire. timeout := time.Until(dl) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-timeout", Value: grpcutil.EncodeDuration(timeout)}) }
解析的过程:
google.golang.org/grpc@v1.50.1/internal/transport/handler_server.go func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats []stats.Handler) (ServerTransport, error) { if v := r.Header.Get("grpc-timeout"); v != "" { to, err := decodeTimeout(v) if err != nil { return nil, status.Errorf(codes.Internal, "malformed time-out: %v", err) } st.timeoutSet = true st.timeout = to } if timeoutSet { s.ctx, s.cancel = context.WithTimeout(t.ctx, timeout) } else { s.ctx, s.cancel = context.WithCancel(t.ctx) }
可以看到,首先从header里面取出超时时间,然后设置context.WithTimeout func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), traceCtx func(context.Context, string) context.Context) { if ht.timeoutSet { ctx, cancel = context.WithTimeout(ctx, ht.timeout) } else { ctx, cancel = context.WithCancel(ctx) }
google.golang.org/grpc@v1.50.1/internal/transport/http2_server.go func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream), traceCtx func(context.Context, string) context.Context) (fatal bool) { case "grpc-timeout": timeoutSet = true var err error if timeout, err = decodeTimeout(hf.Value); err != nil { headerError = true }
网友们的绝世文采续写我来人间一趟我来人间一趟,忘了最初模样,历尽人间沧桑,只为碎银几两。我来人间一趟,未惧风雨猖狂,却叹世态炎凉,丢了初时模样。我来人间一趟,走过山高水长,邀过月光,酣醉于青春模样也曾经历百雪千霜
网友们的绝世文采续写我来人间一趟我来人间一趟,忘了最初模样,历尽人间沧桑,只为碎银几两。我来人间一趟,未惧风雨猖狂,却叹世态炎凉,丢了初时模样。我来人间一趟,走过山高水长,邀过月光,酣醉于青春模样也曾经历百雪千霜
新能源化大势所趋,江淮1卡混动轻卡为何更受关注?江山代有才人出,各领风骚数百年。自19世纪60年代活塞式内燃机问世以来,经过不断的改进和发展,其已成为全球各类汽车拖拉机农业机械工程机械等设备的动力来源。然而近年来,随着全球排放标
新能源化大势所趋,江淮1卡混动轻卡为何更受关注?江山代有才人出,各领风骚数百年。自19世纪60年代活塞式内燃机问世以来,经过不断的改进和发展,其已成为全球各类汽车拖拉机农业机械工程机械等设备的动力来源。然而近年来,随着全球排放标
新能源卷王横空出世,在加油站换电池或许不再是幻想最近这后台收到的粉丝私信里,咨询新能源车型的相关问题是越来越多了。今天一大早刚起床,就看到一条非常有意思的,所以决定来和大家一起聊一聊。这位粉丝跟我说师兄,最近想入手35万左右的新
新能源卷王横空出世,在加油站换电池或许不再是幻想最近这后台收到的粉丝私信里,咨询新能源车型的相关问题是越来越多了。今天一大早刚起床,就看到一条非常有意思的,所以决定来和大家一起聊一聊。这位粉丝跟我说师兄,最近想入手35万左右的新
把宝宝抱上大床后啪的一声掉下来杭州的袁先生反映,花7000多块钱雇了一位育婴师,结果孩子还是没有看牢,摔地上了。1818黄金眼育婴师走开后,7月大的宝宝掉下床(啪)(哇哇哇)画面是袁先生家里的监控拍到的。监控正
把宝宝抱上大床后啪的一声掉下来杭州的袁先生反映,花7000多块钱雇了一位育婴师,结果孩子还是没有看牢,摔地上了。1818黄金眼育婴师走开后,7月大的宝宝掉下床(啪)(哇哇哇)画面是袁先生家里的监控拍到的。监控正
不忘初心,方得始终!我的学医之路时间如闪电,转瞬即逝。不知不觉间我已经在学医的路上行走了近10载,蓦然回首,这一路有心酸有无奈有喜悦!虽然心酸无奈过,但內心始终没有真正想要放弃这份坚守!特别是自己的努力得到病人及
不忘初心,方得始终!我的学医之路时间如闪电,转瞬即逝。不知不觉间我已经在学医的路上行走了近10载,蓦然回首,这一路有心酸有无奈有喜悦!虽然心酸无奈过,但內心始终没有真正想要放弃这份坚守!特别是自己的努力得到病人及
电力物联网行业发展概况及细分市场分析发展机遇挑战市场规模电力物联网行业发展概况及细分市场分析发展机遇挑战市场规模1电力物联网行业概况电力物联网是物联网在智能电网中的应用,是有效整合通信基础设施资源和电力基础设施资源,提高电力系统信息化水
酒店前台工作多工资少,还经常熬夜,为何还有很多女生抢着做?现在我国的经济在不断的发展,各行各业都进入了高速发展时期,尤其是人们生活好了,纷纷踏出家门旅游,旅游业的繁荣也带动了酒店行业的发展,大家出门在外总不论是出差还是旅游都离不开酒店。大
女孩起名,精妙无双,白玉无瑕的女宝宝名字大全新生婴儿的到来对于每个家庭来说,都非常幸福,因此,在很多时候家长们给孩子起名之时也会赋予这份爱,如以下小编为大家分享名字,希望能帮助到大家。女孩起名,精妙无双,白玉无瑕的女宝宝名字
那些登过长城的外国领导,叶利钦被震撼到,奥巴马独自一人悠闲万里长城作为古代世界七大建筑奇迹之一,早就成为了中华民族的象征。它修建于西周时期,可在之后的时间里每个朝代都会对它进行维修一直到明朝时期。它曾是保卫中原大地的古城墙,不过现在已经是
NBA实力排名太阳反超勇士升至第1!篮网第5,湖人第15,火箭25凯文杜兰特能带网多高?这位超级巨星在凯里欧文缺席和詹姆斯哈登哈登表现不佳的情况下让布鲁克林保持稳定。凯文杜兰特能得到一些帮助吗?这支最初被认为是一个时代的超级球队已经变成了一支基本
飞机上这3样服务都免费,乘客不要求,空姐是不会主动给的在飞机上,这三种隐藏服务空姐是不会告诉你的,只要乘客不提出,就永远享受不到。(此处已添加小程序,请到今日头条客户端查看)大家都知道,这几年我国交通行业的发展如火如荼,再加上国民的么
如果城里逛不了,那我们去山里看雪吧近期的疫情反反复复,如果不是必要的出行,出门一趟都要思考好久,而随着第一场雪的介绍,城里已经很久没有看到雪了,那不妨趁着天气甚好,去一睹山里的美景吧。不管是深山还是浅山,因为高海拔
自驾新疆的一点体会想象中的新疆很大,也很美丽,像西藏内蒙一样大片的绿色草地,清澈的河水弯弯曲曲流向远方,蓝天白云下牛羊正在悠闲地吃草,远处的屋外升起缕缕的炊烟,一片祥和安宁的情景。退休了没事,想去新
观光度假特辑吉隆坡美食打卡攻略编者按吉隆坡KualaLumpur,马来西亚的首都,是大马政治经济文化的中心。马来西亚的多元化多种族特色在这里体现地淋漓尽致。它仿佛是个生长于热带丛林的都市。坐车穿越其中,道路两旁
nba球队球衣火箭最经典,湖人最高贵,勇士最霸气我们都知道,除了球星在篮球场上的精湛球技,他们的球衣和球鞋也非常抢眼。每个球员都签约了不同的球鞋公司,所以球场上的球鞋让人眼花缭乱,除非我特别喜欢的球星平时不在乎球鞋,而且每个球队
双赢!火箭森林狼酝酿4换1交易!伍德戈登2首轮打劫唐斯?森林狼本赛季战绩不佳,12胜15负排名西部第九,一直在季后赛边缘徘徊,这与球队赛季之初的预期相差甚远。事实证明唐斯拉塞尔爱德华兹组成的三巨头难以带领球队步入强队行列。据悉,森林狼老
火箭捡到宝贝啦火箭捡到宝贝啦!申京绝对是个大宝贝!失之东隅,收之桑榆!榜眼格林打了十几场比赛,但是火箭连续输了15场。受伤以后火箭连续七连胜。在NBA最新的新秀排名统计中,格林排在所有新秀的后面