1. 程式人生 > 其它 >【雲原生小課堂】Envoy請求流程原始碼解析(三):請求解析

【雲原生小課堂】Envoy請求流程原始碼解析(三):請求解析

 

前言

Envoy 是一款面向 Service Mesh 的高效能網路代理服務。它與應用程式並行執行,通過以平臺無關的方式提供通用功能來抽象網路。當基礎架構中的所有服務流量都通過 Envoy 網格時,通過一致的可觀測性,很容易地檢視問題區域,調整整體效能。

Envoy也是istio的核心元件之一,以 sidecar 的方式與服務執行在一起,對服務的流量進行攔截轉發,具有路由,流量控制等等強大特性。本系列文章,我們將不侷限於istio,envoy的官方文件,從原始碼級別切入,分享Envoy啟動、流量劫持、http 請求處理流程的進階應用例項,深度分析Envoy架構。

本篇將是Envoy請求流程原始碼解析的第三篇,主要分享Envoy的outbound方向下篇,包含:接收請求、傳送請求、接收響應、返回響應。注:本文中所討論的issue和pr基於21年12月。

 

outbound方向

接收請求

1、client開始向socket寫入請求資料

2、eventloop在觸發read event後,transport_socket_.doRead中會迴圈讀取加入read_buffer_,直到返回EAGAIN

3、

4、把buffer傳入Envoy::Http::ConnectionManagerImpl::onData進行HTTP請求的處理

5、

 

6、如果codec_type是AUTO(HTTP1,2,3目前還不支援,在計劃中)的情況下,會判斷請求是否以PRI * HTTP/2為開始來判斷是否http2 

7、利用http_parser進行http解析的callback,ConnectionImpl::settings_靜態初始化了parse各個階段的callbacks

8、

envoy社群有討論會將協議解析器從http_parser換成llhttp

 

https://github.com/envoyproxy/envoy/issues/5155

https://github.com/envoyproxy/envoy/pull/15263/files 使用解析器介面,重構http parser

https://github.com/envoyproxy/envoy/pull/15814新增llhttp解析器的實現,暫時還沒合併

9、

10、onMessageBeginBase

11、

    

12、
建立ActiveStream, 儲存downstream的資訊,和對應的route資訊對於https,會把TLS握手的時候儲存的SNI寫入ActiveStream.requested_server_name_  

13、onHeaderField,onHeaderValue 迭代新增header到current_header_map_中

14、解析完最後一個請求頭後會執行 onHeadersComplete 把request中的一些欄位(method, path, host )加入headers中

15、回撥 onHeadersComplete, 依次回撥onMessageComplete,onMessageCompleteBase,ServerConnectionImpl::onMessageComplete

這個請求解碼是Envoy上下文的,它會執行Envoy的核心代理邏輯 —— 遍歷HTTP過濾器鏈、進行路由選擇

此過濾器當中判斷請求過載

通過route上的cluster name從ThreadLocalClusterManager中查詢cluster, 快取在cached_cluster_info_中

根據配置構造在route上的filterChain (具體的filter實現是通過registerFactory方法註冊進去,在createFilterChain的時候根據名稱構造,比如istio-proxy的stats)

如果對應http connection manager上有trace配置

request header中有trace,就建立子span, sampled跟隨parent span

如果header中沒有trace,就建立root span, 並設定sampled

16、根據http connection manager上配置的filters (envoy.cors,envoy.fault,envoy.router),一個個執行decodeHeaders

這裡主要寫一下和envoy.router

(1)envoy.router

在構造RouteMatcher的時候會遍歷virtual_hosts下的domains,並根據萬用字元的位置和domain的長度分為4個map<domain_len, std::unordered_map<domain, virtualHost>, std::greater<int64_t>>

default_virtual_host_`domain就是一個萬用字元(只允許存在一個)

wildcard_virtual_host_suffixes_domain中萬用字元在開頭

wildcard_virtual_host_prefixes_domain中萬用字元在結尾

virtual_hosts_不包含通配

按照virtual_hosts_=>wildcard_virtual_host_suffixes_=>wildcard_virtual_host_prefixes_=>default_virtual_host_的順序查詢

同時按照map的迭代順序(domain len降序)查詢最先除去萬用字元後能匹配到的virtualhost,如果沒有直接返回 404

在一個virtualhost上查詢對應route和cluster

在通過domain匹配到virtualhost,會在那個virtualhost上匹配查詢cluster,如果沒匹配上,會直接返回404

match可以根據配置分為prefix,regex,path三種route進行匹配

如果存在weighted_clusters,會根據stream_id, 和clusters的weight進行分發,stream_id本身是每個請求獨立隨機生成,所以weighted_clusters的權重分發可以視為隨機分發

(2)

沒有route能匹配請求,返回 404no cluster match for URL

有配置directResponseEntry,直接返回

route上的clustername在clustermanager上找不到對應cluster,返回配置的clusterNotFoundResponseCode

當前處於maintenanceMode (和主動健康檢查相關)

呼叫createConnPool獲取upstream conn pool

根據 cluster上的features配置和USE_DOWNSTREAM_PROTOCOL來確定使用http1還是http2協議向上遊傳送請求

在ThreadLocalClusterManager上根據cluster name查詢cluster

根據loadbalancer演算法挑選節點(此處worker之間的負載均衡根據不同的負載均衡演算法有的是獨立的,比如round robin,只有同一個Worker上的才是嚴格的順序)

根據節點和協議拿到連線池 (連線池由ThreadLocalClusterManager管理,各個Worker不共享)

沒有做直接503,中止解析鏈

根據配置(timeout, perTryTimeout)確定本次請求的timeout

把之前生成的trace寫入request header

對request做一些最終的修改,headers_to_remove``headers_to_add``host_rewrite``rewritePathHeader(路由的配置)

         

構造 retry和shadowing的物件

 

傳送請求

傳送請求部分也是在envoy.router中的邏輯

1、檢視當前conn pool是否有空閒client

2、

如果存在空閒連線

根據downstream request和tracing等配置構造發往upstream的請求buffer

把buffer一次性移入write_buffer_, 立即觸發Write Event

ConnectionImpl::onWriteReady隨後會被觸發

把write_ buffer_的內容寫入socket傳送出去

如果不存在空閒連線

根據max_pending_requests和max_connections判斷是否可以建立新的連線(此處的指標為worker間共享),但是每個執行緒會向上遊最少建立一條連線,也就是極端策略可能需要和工作執行緒數相關
根據配置設定新連線的socket options, 使用dispatcher.createClientConnection建立連線上游的連線,並繫結到eventloop
新建PendingRequest並加到pending_requests_頭部
當連線成功建立的時候,會觸發ConnectionImpl::onFileEvent

在onConnected的回撥中停止connect_timer_;複用存在空閒連線時的邏輯,傳送請求

3、在onRequestComplete裡呼叫maybeDoShadowing進行流量複製

4、

shadowing流量並不會返回錯誤
shadowing 流量為asynclient傳送,不會阻塞downstream,timeout也為global_timeout_

shadowing 會修改request header裡的host 和 authority 新增-shadow字尾
5、根據global_timeout_啟動響應超時的定時器

 

接收響應

1、eventloop 觸發ClientConnectionImpl.ConnectionImpl上的onFileEvent的read ready事件

2、經過http_parser execute後觸發onHeadersComplete後執行到UpstreamRequest::decodeHeaders

3、upstream_request_->upstream_host_->outlierDelector().putHttpResponseCode寫入status code,更新外部檢測的狀態

4、

5、

6、根據返回結果、配置和retries_remaining_判斷是否應該retry

根據internal_redirect_action的配置和response來確定是否需要redirect到新的host

 

返回響應

1、停止request_timer, 重置idle_timer

2、和向upstream傳送請求一樣的邏輯,傳送響應給downstream

 

閱讀原始碼總結

1、envoy當中各種繼承,模板,組合使用的非常多,子類初始化時需要關注父類的建構函式做了什麼

2、可以根據請求日誌的資訊,通過日誌的順序再到程式碼走一遍大體過程

3、善用各種除錯工具,例如抓包,gdb,放開指標等,個人的經驗 百分之90的問題日誌+抓包+部分原始碼的閱讀可以解決

 

ASM試用申請

Envoy是Istio中的Sidecar官方標配,是一個面向Service Mesh的高效能網路代理服務。

當前Service Mesh是Kubernetes上微服務治理的最佳實踐,靈雀雲微服務治理平臺Alauda Service Mesh(簡稱:ASM)可完整覆蓋微服務落地所需要的基礎設施,讓開發者真正聚焦業務。

如果您想深入體驗ASM,掃描下方二維碼即可報名!

 

 

附錄:

關於重複header的rfc規範:

https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

關於header大小寫處理:

https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/header_casing

關於修改header append行為:

https://www.envoyproxy.io/docs/envoy/latest/version_history/v1.15.1

 

關於【雲原生小課堂】

【雲原生小課堂】是由靈雀雲、Kube-OVN社群、雲原生技術社群聯合開設的公益性技術分享類專題,將以豐富詳實的精品內容和靈活多樣的呈現形式,持續為您分享雲原生前沿技術,帶您瞭解更多雲原生實踐乾貨。

在數字化轉型的背景下,雲原生已經成為企業創新發展的核心驅動力。作為國內最早將 Kubernetes 產品化的廠商之一,靈雀雲從出生便攜帶“雲原生基因”,致力於通過革命性的技術幫助企業完成數字化轉型,我們期待著雲原生給這個世界帶來更多改變。

關注我們,學習更多雲原生知識,一起讓改變發生。