最近在將應用的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);
看到了吧,這樣的話,超時時間就不用硬編碼了,我們可以動態的設定,直到我們的業務和程序運行都收斂了,或許就可以真正硬編碼一個超時時間了。