Google 怎麼解決長尾延遲問題

Tail at Scale[1],是 Google 2013 年發佈的一篇論文,大規模在線服務的長尾延遲問題。

要知道怎麼解決長尾問題,先要理解長尾延遲是個什麼問題,在開發在線服務的時候,我們都知道要關注服務的 p99/p999 延遲,要讓大部分用戶都能夠在預期的時間範圍內獲得響應。

下面是一個不同時間的請求數分佈圖:

tail_latency

大部分系統也都遵循這種分佈規律,現在互聯網的系統規模比較大,一個服務依賴幾十上百個服務的情況都是有可能的。單一模塊的長尾延遲會在有大量依賴的情況下,在服務粒度被放大,《The Tail at Scale》論文裏給出了這樣的例子。

考慮一個系統,大部分服務調用在 10ms 內響應,但 99 分位數的延遲爲 1 秒。如果一個用戶請求只在一個這樣的服務上處理,那麼 100 個用戶請求中只有一個會很慢(一秒鐘)。這裏的圖表概述了在這種假設的情況下,服務級別的延遲是如何被非常小概率的大延遲值影響的。

tail

如果一個用戶請求必須從 100 個這樣的服務並行收集響應,那麼 63% 的用戶請求將需要超過一秒鐘(圖中標記爲 "x")。即使對於只有萬分之一概率在單臺服務器上遇到超過一秒的響應延遲的服務,如果服務的規模到達 2000 實例的話,也會觀察到幾乎五分之一的用戶請求需要超過一秒(圖中標記爲 "o")。

Xargin 注:

  1. 因爲請求是並行的,所以只受最慢的那個影響,100 個請求都落在 99 分位內延遲纔會小於 1s,所以延遲小於 1s 的概率是 pow(0.99, 100) = 0.3660323412732292,也就是說一定有 63% 的概率會超過 1s。

  2. 第二個,我們落在 99 分位的概率是 pow(0.9999, 2000) = 0.8187225652655495,也就是將近 20% 的用戶響應會超過 1s。

tail2

上面的表格列出了一個真實的谷歌服務的測量結果,該服務在邏輯上與前文簡化過的場景相似;根服務通過中間服務將一個請求分發到非常多的葉子服務。表展示了大量扇出 (fan-out) 調用時,對延遲分佈的影響。

在根服務器上測量的單個隨機請求完成的 99 分位延遲是 10ms。然而,所有請求完成的 99 分位數延遲是 140ms,95% 的請求 99 分位數延遲是 70ms,這意味着等待最慢的 5% 的慢請求要對總的 99 百分位數延遲的一半負責。對這些慢請求場景進行優化,會對服務的整體延遲產生巨大影響。

爲什麼會有長尾延遲?

單個服務組件的長尾高延遲可能由於許多原因產生,包括:

  1. 共享資源 (Xargin: 混部現在越來越多了)。機器可能被爭奪共享資源 (如 CPU 核心、處理器緩存、內存帶寬和網絡帶寬) 的不同應用所共享,而在同一應用中,不同的請求可能爭奪資源。

  2. 守護程序。後臺守護程序可能平均只使用有限的資源,但在運行時可能會產生幾毫秒的峯值。

  3. 全局資源共享。在不同機器上運行的應用程序可能會爭搶全局資源 (如網絡交換機和共享文件系統)。

  4. 後臺任務。後臺活動 (如分佈式文件系統中的數據重建,BigTable 等存儲系統中的定期日誌壓縮,以及垃圾收集語言中的定期垃圾收集) 會導致週期性的延遲高峯。

  5. 排隊。中間服務器和網絡交換機中的多層隊列放大了這種可能性。

同時也是因爲當前硬件的趨勢:

  1. 功率限制。現代 CPU 被設計成可以暫時運行在其平均功率之上 (英特爾睿頻加速技術),如果這種活動持續很長時間,可以通過降頻降低發熱;

  2. 垃圾收集。固態存儲設備提供了非常快的隨機讀取訪問,但是需要定期對大量的數據塊進行垃圾收集,即使是適度的寫入活動,也會使讀取延遲增加 100 倍;

  3. 能源管理。許多類型的設備的省電模式可以節省相當多的能量,但在從非活動模式轉爲活動模式時,會增加額外的延遲。

解決方案

模塊內儘量降低長尾延遲

服務分級 && 優先級隊列。差異化服務級別可以用來優先調度用戶正在等待的請求,而不是非交互式請求。保持低級隊列較短,以便更高級別的策略更快生效。

減少隊頭阻塞。將需要長時間運行的請求打散成一連串小請求,使其可以與其它短時間任務交錯執行;例如,谷歌的網絡搜索系統使用這種時間分割,以防止大請求影響到大量其它計算成本較低的大量查詢延遲。(隊頭阻塞還有協議和連接層面的問題,需要通過使用更新的協議來解決,比如 h1 -> h2 -> h3 的升級思路)

管理後臺活動和同步中斷。後臺任務可能產生巨大的 CPU、磁盤或網絡負載;例子是面向日誌的存儲系統的日誌壓縮和垃圾收集語言的垃圾收集器活動。可以結合限流功能,把重量級操作分解成成本較低的操作,並在整體負載較低的時候觸發這些操作 (比如半夜),以減少後臺活動對交互式請求延遲的影響。

請求期間內的一些自適應手段

對沖請求。抑制延遲變化的一個簡單方法是向多個副本發出相同的請求 (Go 併發模式中的 or channel),並使用首先響應的結果。一旦收到第一個結果,客戶端就會取消剩餘的未處理請求。不過直接這麼實現會造成額外的多倍負載。所以需要考慮優化。

一個方法是推遲發送第二個請求,直到第一個請求到達 95 分位數還沒有返回。這種方法將額外的負載限制在 5% 左右,同時大大縮短了長尾時間。

捆綁式請求。不像對沖一樣等待一段時間發送,而是同時發給多個副本,但告訴副本還有其它的服務也在執行這個請求,副本任務處理完之後,會主動請求其它副本取消其正在處理的同一個請求。需要額外的網絡同步。

跨請求的自適應手段

微 分區。劃分單個機器爲多個 分區,以解決負載不平衡問題。例如,每臺機器平均有 20 個分區,系統可以以大約 5% 的增量來調輕負載,時間是系統簡單地將分區與機器進行一對一映射的 1/20。(這裏我感覺像是說類似一致性 hash 的虛擬節點)

選擇性的複製。爲你檢測到的或預測到的會很熱的分區增加複製因子。然後,負載均衡器可以幫助分散負載。谷歌的主要網絡搜索系統採用了這種方法,在多個微分區中對流行和重要的文件進行額外的複製。(就是熱點分區多準備些實例啦,感覺應該需要按具體業務去做一些預測)

將慢速機器置於考察期。當檢測到一臺慢速機器時,暫時將其排除在操作之外 (circuit breaker)。由於緩慢往往是暫時的,監測何時使受影響的系統重新上線。繼續向這些被排除的服務器發出影子請求,收集它們的延遲統計數據,以便在問題緩解時將它們重新納入服務中。(這裏就是簡單的熔斷的實現)

一些其它的權衡

考慮 “足夠好” 的響應。一旦所有的服務器中有足夠的一部分做出了響應,用戶可能會得到最好的服務,即得到輕微的不完整的結果,以換取更好的端到端延遲。(這裏應該是大家常說的降級)

使用金絲雀請求。在具有非常高扇出的系統中可能發生的另一個問題是,一個特定的請求觸發了未經測試的代碼路徑,導致崩潰或同時在成千上萬的服務器上出現極長的延遲。爲了防止這種相關的崩潰情況,谷歌的一些 IR 系統採用了一種叫做 “金絲雀請求” 的技術;根服務器並不是一開始就把一個請求發送給成千上萬的葉子服務器,而是先把它發送給一個或兩個葉子服務器。其餘的服務器只有在根服務器在合理的時間內從金絲雀那裏得到成功的響應時纔會被查詢。

[1]

Tail at Scale: https://research.google/pubs/pub40801/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Rmo_ftHLU-V4tpegBZ6jag