記錄 gRPC Deadlines 的一次事故

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

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

一般,當您未設置截止日期時,將爲全部正在進行的請求保留資源,而且全部請求均可能達到最大超時。這會使服務面臨資源耗盡的風險,例如內存,這會增長服務的延遲,或者在最壞的狀況下可能致使整個過程崩潰。ide

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

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

Setting a deadline

Go

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

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再也不須要。在服務器開始做出響應以前,檢查是否還有客戶端等待它是很是重要的。在開始昂貴的處理以前,這一點尤爲重要。blog

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
gRPCrpc

相關文章
相關標籤/搜索