grpc deadlines
最近在将应用的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);
看到了吧,这样的话,超时时间就不用硬编码了,我们可以动态的设定,直到我们的业务和程序运行都收敛了,或许就可以真正硬编码一个超时时间了。