💂 關於封面:
Tower Bridge watercolor painting by Juan Bosco📚 摘錄説明:
本文摘自一本我在寫作中的開源書《Istio & Envoy 內幕》 中 Envoy 請求與響應調度 一節。如果説你看到的轉載圖片不清,可回到原書。《Istio & Envoy 內幕》epub 格式下載
-
請求與響應調度
- 相關組件
- 相關的監控指標
- Envoy 請求調度流程
- 請求與響應調度時序線
- 總結
- 一些有趣的擴展閲讀
🎤 正式開編前。想説説寫本節的一些故事緣由。為何去研究 Envoy 的請求與響應調度?
緣起於一個客户需求,需要對 Istio 網格節點故障快速恢復做一些調研。為此,我翻閲了大量的 Istio/Envoy 文檔、大咖 Blog。看了很多很雜亂的信息:
- 健康檢測
- 熔斷
- Envoy 中的各個神秘又關係千絲萬縷的 timeout 配置
- 請求 Retry
TCP keepalive、TCP_USER_TIMEOUT配置
雜亂到最後,我不得不寫個文章去梳理一下信息:Istio 網格節點故障快速恢復初探 。 但信息是梳理了,基礎原理卻沒理順。於是,我下決心去鑽研一下 Envoy 的文檔。是的,其實 Envoy 的文檔已經寫得比較細緻。只是:
- 信息散落在一個個網頁中,無法用時序和流程的方法組織起來,構成一個有機的整體。
- 不去了解這個整體協作關係,只是一個一個參數分開來看,是無法理性去權衡這些參數的。
- 指標與指標,指標與參數,關係複雜
- 而上面的關係,都可以通過請求與響應調度流程串聯起來
基於上面原因。我從文檔、參數、指標推導出以下流程。<mark>注意:暫時未完全在代碼中驗證,請謹慎參考。</mark>
本文寫於 2022-10-01
- 更新於 2024-06-08 : 加入 http2 相關流程,並在 draw.io 圖中加入到相關源碼的鏈接
請求與響應調度
本質上説,Envoy 就是一個代理。説起代理,第一反應應該是有以下流程的軟件/硬件:
- 接收來自
downstream的Request - 做一些邏輯,必要時修改
Request,並判定upstream目的地 - 轉發(修改後)的
Request到upstream -
如果協議是一個
Request&Reponse式的協議(如 HTTP)- 代理通常會接收
upstream的Response - 做一些邏輯,必要時修改
Response - 轉發
Response給downstream
- 代理通常會接收
的確,這也是 Envoy 代理 HTTP 協議的概要流程。但 Envoy 還要實現很多特性:
- 高效的
downstream/upstream傳輸 ➡️ 需要連接複用與連接池 - 靈活配置的轉發目標服務策略 ➡️ 需要
Router配置策略與實現邏輯 -
彈性服務 (resilient micro-services)
- 負載均衡
- 突發流量的削峯平谷 ➡️ 請求排隊: pending request
- 應對異常 upstream、熔斷器、保護服務不雪崩 ➡️ 各種 timeout 配置、 Health checking 、 Outlier detection 、 Circuit breaking
- 彈性重試 ➡️ retry
- 可觀察性 ➡️ 無處不在的性能指標
- 動態編程配置接口 ➡️ xDS: EDS/LDS/...
要實現這些特性,請求與響應的流程自然不可能簡單。
💡 看到這裏,讀者可能有疑問,本節的標題叫 “請求與響應調度” ? 難度 Envoy 需要類似 Linux Kernel 調度線程一樣,去調度處理 Request 嗎?
對的,你説到點上了。
Envoy 應用了 事件驅動 設計模式。事件驅動 的程序,相對於 非事件驅動 的程序,可以用更少的線程,更靈活地控制在什麼時候做什麼任務,即更靈活的調度邏輯。且更絕的是:由於線程間共享的數據不多,線程的數據併發控制同時被大大簡化。
在本節中,事件類型最少有:
- 外部的網絡可讀、可寫、連接關閉事件
-
各類定時器
- 重試定時
- 各種超時配置定時
由於使用了無限的請求分配到有限的線程的模式,加上請求可能需要重試,所以線程一定要有一系列的邏輯,來 “排序” 什麼請求應該先處理。什麼請求由於 超時 或資源使用 超過配置上限 而應立即返回失敗。
按本書的習慣,先上圖。後面,對這個圖一步步展開和説明。
💡 互動圖書:
- 建議用 Draw.io 打開。圖中包含大量的鏈接,鏈接到每一個組件、配置項、指標的文檔説明。
- 雙屏,一屏看圖,一屏看文檔,是本書的正確閲讀姿勢。如果你在用手機看,那麼,忽略我吧 🤦
圖:Envoy HTTP1 請求與響應調度
用 Draw.io 打開
圖:Envoy HTTP/2 請求與響應調度
用 Draw.io 打開
相關組件
上圖是嘗試説明 Envoy 請求與響應調度 過程,以及串聯相關的組件。其中可以看到一些組件:
- Listener - 應答 downstream 連接請求
- HTTP Connection Manager(HCM) - HTTP 的核心組件,推動 http 流的讀取、解釋、路由(Router)
-
HCM-router - HTTP 路由核心組件,職責是:
- 判定 HTTP 下一跳的目標 cluster,即 upsteam cluster
- 重試
- Load balancing - upstream cluster 內的負載均衡
- pending request queue -
等待連接池可用連接的請求隊列 - requests bind to connection - 已經分配到連接的請求
- connection pool - worker 線程與 upstream host 專用的連接池
- health checker/Outlier detection - upsteam host 健康監視,發現異常 host 並隔離。
和一些 Circuit breaking(熔斷開關) 上限條件:
- max_retries - 最大重試併發上限
- max_pending_requests -
pending request queue的隊列上限 - max_request - 最大併發請求數上限
- max_connections - upstream cluster 的最大連接上限
需要注意的是,上面的參數是對於整個 upstream cluster 的,即是所有 worker thread、upstream host 彙總的上限。
相關的監控指標
我們用類似著名的 Utilization Saturation and Errors (USE) 方法學來分類指標。
資源過載形的指標:
- downstream_cx_overflow
- upstream_rq_retry_overflow
- upstream_rq_pending_overflow
- upstream_cx_overflow
資源飽和度指標:
- upstream_rq_pending_active
- upstream_rq_pending_total
- upstream_rq_active
錯誤形的指標:
- upstream_rq_retry
- ejections_acive
- ejections_*
- ssl.connection_error
信息形的指標:
- upstream_cx_total
- upstream_cx_active
- upstream_cx_http*_total
由於圖中已經説明了指標、組件、配置項的關係,這裏就不再文字敍述了。圖中也提供了到指標文檔和相關配置的鏈接。
Envoy 請求調度流程
先説説請求組件流轉部分,流程圖可以從相關的文檔推理為(未完全驗證,存在部分推理):
圖:Envoy 請求調度流程圖
用 Draw.io 打開
圖:Envoy HTTP/2 請求調度流程圖
用 Draw.io 打開
請求與響應調度時序線
本節開頭説了,寫本節的直接緣由是: 需要對 Istio 網格節點故障快速恢復做一些調研。快速恢復 的前提是:
- 對已經發送到
故障 upstream host或綁定到故障 upstream host的請求,快速響應失敗 - 用
Outlier detection / health checker識別出故障 upstream host,並把它移出負載均衡列表
所有問題都依賴於一個問題:如何定義和發現 upstream host 出了故障?
-
網絡分區或對端崩潰或負載過高
- 大多數情況下,分佈式系統只能通過超時來發現這種問題。所以,要發現
故障 upstream host或故障 request,需要配置
- 大多數情況下,分佈式系統只能通過超時來發現這種問題。所以,要發現
-
對端有響應,L7 層的失敗(如 HTTP 500),或 L3 層的失敗(如 TCP REST/No router to destination/ICMP error)
- 這是可以快速發現的失敗
對於 網絡分區或對端崩潰或負載過高,需要 timeout 發現的情況,Envoy 提供了豐富的 timeout 配置。豐富到有時讓人不知道應該用哪個才是合理的。甚至配置一不小心,就配置出一些邏輯上長短與實現設計矛盾的值。所以,我嘗試用理清楚 請求與響應調度時序線 ,然後看相關 timeout 配置關聯到這個時間線的哪個點,那麼整個邏輯就清楚了。配置也更容易合理化了。
下圖是請求與響應的時序線,以及相關的 timeout 配置與產生的指標,以及它們的聯繫。
圖:Envoy 請求與響應時序線
用 Draw.io 打開
簡單説明一下時間線:
- 如果 downstream 複用了之前的連接,可以跳過 2 & 3
- downstream發起 新連接(TCP 握手)
- TLS 握手
- Envoy 接收 downstream request header & body
- Envoy 執行路由(Router)規則,判定下一跳的 upstream cluster
- Envoy 執行 Load Balancing 算法 ,判定下一跳的 upstream cluster 的 upstream host
- 如果 Envoy 已經有空閒連接到 upstream host,則跳過 8 & 9
- Envoy 向 upstream host 發起新連接(TCP 握手)
- Envoy 向 upstream host 發起TLS 握手
- Envoy 向 upstream host 轉發送 requst header & body
- Envoy 接收 upstream host 響應的 response header & body
- upstream host 連接開始 idle
- Envoy 向 downstream 轉發送 response header & body
- downstream host 連接開始 idle
相應地,圖中也標註了相關超時配置與時間線步驟的關係,從開始計時順序排列如下
- max_connection_duration
-
transport_socket_connect_timeout
- 指標
listener.downstream_cx_transport_socket_connect_timeout
- 指標
- request_headers_timeout
- requst_timeout
-
Envoy 的 route.timeout 即 Istio 的
Istio request timeout(outbound)注意,這個超時值是把 請求處理時實際的 retry 的總時間也算上的。
- 指標
cluster.upstream_rq_timeout - 指標
vhost.vcluster.upstream_rq_timeout
- 指標
- max_connection_duration
-
connection_timeout
- 指標
upstream_cx_connect_timeout
- 指標
- transport_socket_connect_timeout
- httpprotocoloptions.idle_timeout
總結
想要 Envoy 在壓力與異常情況下,有個比較符合預期的表現,需要給 Envoy 一些合理於具體應用環境與場景的配置。而要配置好這堆參數的前提,是對相關處理流程與邏輯的洞察。 上面把 請求與響應調度 與 請求與響應調度時序線 都過了一遍。希望對了解這些方面有一定的幫助。
不只是 Envoy ,其實所有做代理的中間件,可能最核心的東西都在這一塊了。所以,不要期望一下把知識完全吃透。這裏,也只是希望讓讀者在這些流程上,有一個線索,然後通過線索去學習,方可不迷失方向。
一些有趣的擴展閲讀
- https://www.istioworkshop.io/09-traffic-management/06-circuit...
- https://tech.olx.com/demystifying-istio-circuit-breaking-27a6...