記錄 gRPC Deadlines 的一次事故

收到交易服務的報警,服務器內存暴增。後經排查發現是由於gRPC客戶端調用的時候在上下文(context)中未設置Deadline導致的。
那麼爲什麼未設置Deadline會導致內存耗盡呢?

當您使用gRPC時,gRPC庫負責通信,編組,解組和最後期限執行。Deadline允許gRPC客戶端指定在RPC以錯誤DEADLINE_EXCEEDED終止之前,他們願意等待RPC完成的時間。默認情況下,此截止日期是一個非常大的數字,取決於語言實現。如何指定截止日期也取決於語言。指定截止日期或超時的方式因語言而異 - 例如,並非所有語言都有默認的截止日期,某些語言使用 deadline ,而某些語言使用timeouts。在服務器端,服務器可以查詢特定RPC是否已超時,或者剩餘多少時間來完成RPC。

通常,當您未設置截止日期時,將爲所有正在進行的請求保留資源,並且所有請求都可能達到最大超時。這會使服務面臨資源耗盡的風險,例如內存,這會增加服務的延遲,或者在最壞的情況下可能導致整個過程崩潰。

“什麼是好的截止日期/超時值?”沒有單一的答案。那麼您需要考慮什麼才能明智地選擇截止日期?要考慮的因素包括整個系統的端到端延遲,哪些RPC是串行的,哪些可以並行進行。工程師需要了解服務,然後設置一個刻意的截止日期用於客戶端和服務器之間的RPC。

在gRPC中,關於遠程過程調用(RPC)是否成功的,客戶端和服務器都做了獨立的和本地的判斷。這意味着他們的結論可能不匹配!一個在服務端成功完成的RPC可能在客戶端失敗。例如,服務器可以發送響應,但是這個回覆可以在截止日期到期後到達客戶端,而客戶端將會在回覆到達前使用錯誤狀態DEADLINE_EXCEEDED終止。

Setting a deadline

Go

作爲客戶端,您應始終設置一個期限,以確定您願意等待服務器回覆的時間。

clientDeadline := time.Now().Add(time.Duration(*deadlineMs) * time.Millisecond)
ctx, cancel := context.WithDeadline(ctx, clientDeadline)

Checking deadlines

On the server side, the server can query to see if a particular RPC is no longer wanted.
在服務端,服務器可以查看是否一個特定的RPC不再需要。在服務器開始作出響應之前,檢查是否還有客戶端等待它是非常重要的。在開始昂貴的處理之前,這一點尤其重要。

Go

if ctx.Err() == context.Canceled {
    return status.New(codes.Canceled, "Client cancelled, abandoning.")
}

當您知道客戶已達到截止日期時,服務器繼續處理請求是否有用?這取決於。如果響應可以緩存在服務器中,則值得處理和緩存;特別是如果它的資源很重,並且每個請求都要花錢。這將使未來的請求更快,因爲結果已經可用。

Adjusting deadlines

如果您設置截止日期但新版本或服務器版本會導致錯誤迴歸,該怎麼辦?截止日期可能太小,導致您的所有請求超時DEADLINE_EXCEEDED,或者太大,您的用戶尾部延遲現在很大。您可以使用標誌來設置和調整截止日期。

Go

var deadlineMs = flag.Int("deadline_ms", 20*1000, "Default deadline in milliseconds.")

ctx, cancel := context.WithTimeout(ctx, time.Duration(*deadlineMs) * time.Millisecond)

參考

gRPC and Deadlines
gRPC

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章