最近在将应用的rpc更换为grpc,使用过程中,发现报“rpc error:code=DeadlineExceeded desc = context deadline exceeded”,这是啥?原来是某位仁兄设置了环境的超时时间,但是设置了1S,看好了,是1S。所以,任何稍微费时的交互,都直接报错了。

        如果你不显式设置的话,GRPC自己默认的超时时间是一个很大的值,那就不会出现这种问题。但是给出的错误信息也是无语了,既然是超时,不能给个timeout的提示吗?搞了个什么deadline exceeded,初次看见,还是有点懵。

        Anyway,谷歌出品,必属精品。出问题还是反思自己的使用吧。

        Rule 1,建议显式的指定一个超时时间,一方面可以节省资源,不然所有的请求都无休止的在发送,在server中运行,有可能导致资源耗尽以致系统崩溃,另一方面,业务本身肯定也需要有一个返回时间,而不是提交请求之后,就傻傻的等着,到天荒地老吗?

        Rule 2,没有明确的规则,什么样的超时时间是合适的,这个是开发人员需要加以考虑的。需要考虑网络,如果网络耗时很明显,需要考虑服务器的资源,内存,CPU以及负载,需要考虑外部交互,更重要的,其实是搞清楚业务规则,这个request所对应的业务对时间的要求。虽然只是设置了个时间,但也不是那么容易,不然程序员怎么那么值钱。

        Rule 3,当设置超时时间时,需要考虑client和server两方面的情况。首先,设置超时的方式不一样,server端或者在proto文件中指定就行,client端就需要编码了。其次,当client端设置了超时时间,server端就必须有所响应,不然就会发生意料之外的事情。

        代码片段一,设置超时

        作为客户端,你应该知道自己希望最晚多长时间拿到返回结果吧,那就设置他吧。

1 C++
 2 
 3 ClientContext context;
 4 time_point deadline = std::chrono::system_clock::now() + 
 5     std::chrono::milliseconds(100);
 6 context.set_deadline(deadline);
 7 
 8 
 9 Go
10 
11 clientDeadline := time.Now().Add(time.Duration(*deadlineMs) * time.Millisecond)
12 ctx, cancel := context.WithDeadline(ctx, clientDeadline)
13 
14 
15 Java
16 
17 response = blockingStub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS).sayHello(request);

如上的代码中,都设置了超时时间为100毫秒。

        代码片段二,检查超时

        作为服务器端,如果客户端设置了超时,服务器端就需要去检测下,否则如果客户端已经超时了,服务器端还傻乎乎的干活?岂不是真傻了。

1 C++
 2 if (context->IsCancelled()) {
 3   return Status(StatusCode::CANCELLED, "Deadline exceeded or Client cancelled, abandoning.");
 4 }
 5 
 6 
 7 Go
 8 if ctx.Err() == context.Canceled {
 9     return status.New(codes.Canceled, "Client cancelled, abandoning.")
10 }
11 
12 
13 Java
14 if (Context.current().isCancelled()) {
15   responseObserver.onError(Status.CANCELLED.withDescription("Cancelled by client").asRuntimeException());
16   return;
17 }

         一般而言,server在拿到request之后就应该检测client的超时时间,如果超时了,就不在执行逻辑。不过,如果在server开始执行逻辑但并没有结束的时候client超时了怎么办,当然可以在server执行逻辑的同时检测是否超时,如果超时,cancel掉逻辑。但是也有特殊情况,比如这个逻辑很耗费资源,但是结果对客户端而言是可重用的,或者说结果是可以缓存的,那么就需要把结果保存下来,别cancel逻辑了。so, it depends。

        代码片段三,调整超时

        程序员的苦逼就在于需求一直在变,不过没办法,我们学的哲学不就是说变是永恒的,不变是幻觉吗?那么设置了超时,怎么改?废话,怎么设置的就怎么改啊,这么说当然是废话,这里要说的是在不进行新的版本发布的情况下,怎么改。

1 C++
 2 #include <gflags/gflags.h>
 3 DEFINE_int32(deadline_ms, 20*1000, "Deadline in milliseconds.");
 4 
 5 ClientContext context;
 6 time_point deadline = std::chrono::system_clock::now() + 
 7     std::chrono::milliseconds(FLAGS_deadline_ms);
 8 context.set_deadline(deadline);
 9 
10 
11 Go
12 var deadlineMs = flag.Int("deadline_ms", 20*1000, "Default deadline in milliseconds.")
13 
14 ctx, cancel := context.WithTimeout(ctx, time.Duration(*deadlineMs) * time.Millisecond)
15 
16 
17 Java
18 @Option(name="--deadline_ms", usage="Deadline in milliseconds.")
19 private int deadlineMs = 20*1000;
20 
21 response = blockingStub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS).sayHello(request);

看到了吧,这样的话,超时时间就不用硬编码了,我们可以动态的设定,直到我们的业务和程序运行都收敛了,或许就可以真正硬编码一个超时时间了。