翻譯自Envoy官方文檔。c++
下面描述一個通過Envoy代理的請求的生命週期。首先會描述Envoy如何在請求路徑中處理請求,而後描述請求從下游到達Envoy代理以後發生的內部事件。咱們將跟蹤該請求,直到其被分發到上游和響應路徑中。git
Envoy會在代碼和文檔中使用以下術語:github
一個請求是如何經過一個網絡組件取決於該網絡的模型。Envoy可能會使用大量網絡拓撲。下面會重點介紹Envoy的內部運做方式,但在本節中會簡要介紹Envoy與網絡其他部分的關係。算法
Envoy起源於服務網格Sidecar代理,用於剝離應用程序的負載平衡,路由,可觀察性,安全性和發現服務。在服務網格模型中,請求會通過做爲網關的Envoy,或經過ingress或egress監聽器到達一個Envoy。數據庫
Envoy會用到除服務網格使用到的各類配置,例如,它能夠做爲一個內部的負載均衡器:bootstrap
或做爲一個邊緣網絡的ingress/egress代理:api
實際中,一般會在服務網格中混合使用Envoy的特性,在網格邊緣做爲ingress/egress代理,以及在內部做爲負載均衡器。一個請求路徑可能會通過多個Envoys。緩存
Envoy能夠配置爲多層拓撲來實現可伸縮性和可靠性,一個請求會首先通過一個邊緣Envoy,而後傳遞給第二個Envoy層。安全
以上全部場景中,請求經過下游的TCP,UDP或Unix域套接字到達一個指定的Envoy,而後由該Envoy經過TCP,UDP或UNIX域套接字轉發到上游。下面僅關注單個Envoy代理。網絡
Envoy是一個可擴展的平臺。經過以下條件能夠組合成豐富的請求路徑:
本例將涵蓋以下內容:
router <arch_overview_http_routing>
(HTTP 過濾器鏈)假設使用以下靜態的bootstrap配置文件,該配置僅包含一個listener和一個cluster。在listener中靜態指定了路由配置,在cluster靜態指定了endpoints。
static_resources: listeners: # There is a single listener bound to port 443. - name: listener_https address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 443 # A single listener filter exists for TLS inspector. listener_filters: - name: "envoy.filters.listener.tls_inspector" typed_config: {} # On the listener, there is a single filter chain that matches SNI for acme.com. filter_chains: - filter_chain_match: # This will match the SNI extracted by the TLS Inspector filter. server_names: ["acme.com"] # Downstream TLS configuration. transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "certs/servercert.pem" } private_key: { filename: "certs/serverkey.pem" } filters: # The HTTP connection manager is the only network filter. - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http use_remote_address: true http2_protocol_options: max_concurrent_streams: 100 # File system based access logging. access_log: - name: envoy.access_loggers.file typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: "/var/log/envoy/access.log" # The route table, mapping /foo to some_service. route_config: # 靜態路由配置 name: local_route virtual_hosts: - name: local_service domains: ["acme.com"] routes: - match: path: "/foo" route: cluster: some_service # CustomFilter and the HTTP router filter are the HTTP filter chain. http_filters: - name: some.customer.filter - name: envoy.filters.http.router clusters: - name: some_service connect_timeout: 5s # Upstream TLS configuration. transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext load_assignment: cluster_name: some_service # Static endpoint assignment. endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 10.1.2.10 port_value: 10002 - endpoint: address: socket_address: address: 10.1.2.11 port_value: 10002 http2_protocol_options: max_concurrent_streams: 100 - name: some_statsd_sink connect_timeout: 5s # The rest of the configuration for statsd sink cluster. # statsd sink. stats_sinks: - name: envoy.stat_sinks.statsd typed_config: "@type": type.googleapis.com/envoy.config.metrics.v3.StatsdSink tcp_cluster_name: some_statsd_cluster
Envoy中的請求處理主要包含兩大部分:
這兩個子系統與HTTP router filter橋接在一塊兒,用於將HTTP請求從下游轉發到上游。
咱們使用術語listener subsystem 和cluster subsystem 指代模塊組以及由高層ListenerManager 和ClusterManager類建立的實例類。在下面討論的不少組件都是由這些管理系統在請求前和請求過程當中實例化的,如監聽器, 過濾器鏈, 編解碼器, 鏈接池和負載均衡數據結構。
Envoy有一個基於事件的線程模型。主線程負責生命週期、配置處理、統計等。工做線程用於處理請求。全部線程都圍繞一個事件循環(libevent)進行操做,任何給定的下游TCP鏈接(包括其中的全部多路複用流),在其生命週期內都由一個工做線程進行處理。每一個工做線程維護各自到上游endpoints的TCP鏈接池。UDP處理中會使用SO_REUSEPORT
,經過內核一致性哈希將源/目標IP:端口元組散列到同一個工做線程。UDP過濾器狀態會共享給特定的工做線程,過濾器負責根據須要提供會話語義。這與下面討論的面向鏈接的TCP過濾器造成了對比,後者的過濾器狀態以每一個鏈接爲基礎,在HTTP過濾器的狀況下,則以每一個請求爲基礎。
工做線程不多會共享狀態,且不多會並行運行。 該線程模型能夠擴展到core數量很是多的CPU。
使用上面的示例配置簡要概述請求和響應的生命週期:
decodeHeaders
時,會選擇路由和cluster。數據流中的請求首部會轉發到上游cluster對應的endpoint中。router 過濾器會從羣集管理器中爲匹配的cluster獲取HTTP鏈接池。咱們將在如下各節中詳細介紹每一個步驟。
ListenerManager負責獲取監聽器的配置,並實例化綁定到各自IP/端口的多個Listener
實例。監聽器的狀態可能爲:
每一個工做線程會爲每一個監聽器維護各自的監聽器實例。每一個監聽器可能經過SO_REUSEPORT 綁定到相同的端口,或共享綁定到該端口的socket。當接收到一個新的TCP鏈接,內核會決定哪一個工做線程來接收該鏈接,而後由該工做線程對應的監聽器調用Server::ConnectionHandlerImpl::ActiveTcpListener::onAccept()
。
工做線程的監聽器而後會建立並運行監聽過濾器鏈。過濾器鏈是經過每一個過濾器的過濾器工廠建立的,該工廠會感知過濾器的配置,併爲每一個鏈接或流建立新的過濾器實例。
在TLS 過濾器配置下,監聽過濾器鏈會包含TLS檢查過濾器(envoy.filters.listener.tls_inspector
)。該過濾器會檢查初始的TLS握手,並抽取server name(SNI),而後使用SNI進行過濾器鏈的匹配。儘管tls_inspector
會明確出如今監聽過濾器鏈配置中,但Envoy還可以在監聽過濾器鏈須要SNI(或ALPN)時自動將其插入。
TLS檢查過濾器實現了 ListenerFilter接口。全部的過濾器接口,不管是監聽或網絡/HTTP過濾器,都須要實現特定鏈接或流事件的回調方法,ListenerFilter中爲:
virtual FilterStatus onAccept(ListenerFilterCallbacks& cb) PURE;
onAccept()
容許在TCP accept處理時運行一個過濾器。回調方法的FilterStatus
控制監聽過濾器鏈將如何運行。監聽過濾器可能會暫停過濾器鏈,後續再恢復運行,如響應另外一個服務進行的RPC請求。
在過濾器鏈進行匹配時,會抽取監聽過濾器和鏈接的屬性,提供給用於處理鏈接的網絡過濾器鏈和傳輸socket。
Envoy經過TransportSocket擴展接口提供了插件式的傳輸socket。傳輸socket遵循TCP鏈接的生命週期事件,使用網絡buffer進行讀寫。傳輸socket須要實現以下關鍵方法:
virtual void onConnected() PURE; virtual IoResult doRead(Buffer::Instance& buffer) PURE; virtual IoResult doWrite(Buffer::Instance& buffer, bool end_stream) PURE; virtual void closeSocket(Network::ConnectionEvent event) PURE;
當一個TCP鏈接能夠傳輸數據時,Network::ConnectionImpl::onReadReady()
會經過SslSocket::doRead()
調用TLS傳輸socket。傳輸socket而後會在TCP鏈接上進行TLS握手。TLS握手結束後,SslSocket::doRead()
會給Network::FilterManagerImpl
實例提供一個解密的字節流,該實例負責管理網絡過濾器鏈。
須要注意的是,不管是TLS握手仍是過濾器處理流程的暫停,都不會真正阻塞任何操做。因爲Envoy是基於事件的,所以任何須要額外數據才能進行處理的狀況都將致使提早完成事件,並將CPU轉移給另外一個事件。如當網絡提供了更多的可讀數據時,該讀事件將會觸發TLS握手恢復。
與監聽過濾器鏈相同,Envoy會經過Network::FilterManagerImpl,從對應的過濾器工廠實例化一些列網絡過濾器。每一個新鏈接的實例都是新的。與傳輸socket相同,網絡過濾器也會遵循TCP的生命週期事件,並在來自傳輸socket中的數據可用時被喚醒。
網絡過濾器包含一個pipeline,與一個鏈接一個的傳輸socket不一樣,網絡過濾器分爲三種:
onData()
,當鏈接中的數據可用時(因爲某些請求)被調用onWrite()
, 當給鏈接寫入數據時(因爲某些響應)被調用關鍵過濾器方法的方法簽名爲:
virtual FilterStatus onNewConnection() PURE; virtual FilterStatus onData(Buffer::Instance& data, bool end_stream) PURE; virtual FilterStatus onWrite(Buffer::Instance& data, bool end_stream) PURE;
與監聽過濾器相似,FilterStatus
容許過濾器暫停過濾器鏈的執行。例如,若是須要查詢限速服務,限速網絡過濾器將會從onData()
中返回Network::FilterStatus::StopIteration
,並在請求結束後調用continueReading()
。
HTTP的監聽器的最後一個網絡過濾器是HTTP鏈接管理器(HCM)。該過濾器負責建立HTTP/2編解碼器並管理HTTP過濾器鏈。在上面的例子中,它是惟一的網絡過濾器。使用多個網絡過濾器的網絡過濾器鏈相似:
在響應路徑中,網絡過濾器執行的順序與請求路徑相反
Envoy的HTTP/2編解碼器基於nghttp2,當TCP鏈接使用明文字節時(通過網絡過濾器鏈變換後),會被HCM調用。編解碼器將字節流解碼爲一系列HTTP/2幀,並將鏈接解複用爲多個獨立的HTTP流。流複用是HTTP/2的一個關鍵特性,與HTTP/1相比具備明顯的性能優點。每一個HTTP流會處理一個單獨的請求和響應。
編解碼器也負責處理HTTP/2設置幀,以及鏈接級別的流控制。
編解碼器負責抽象HTTP鏈接的細節,向HTTP鏈接管理器展現標準視圖,並將鏈接的HTTP過濾器鏈拆分爲多個流,每一個流都有請求/響應標頭/正文/尾部(不管協議是HTTP/1,HTTP/2仍是HTTP/3 )。
對於每一個HTTP流,HCM會實例化一個HTTP過濾器鏈,遵循上面爲監聽器和網絡過濾器鏈創建的模式。
HTTP過濾器接口有三種類型:
查看解碼器過濾器接口:
virtual FilterHeadersStatus decodeHeaders(RequestHeaderMap& headers, bool end_stream) PURE; virtual FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) PURE; virtual FilterTrailersStatus decodeTrailers(RequestTrailerMap& trailers) PURE;
HTTP過濾器遵循HTTP請求的生命週期,而不針對鏈接緩衝區和事件進行操做,如decodeHeaders()
使用HTTP首部做爲參數,而不是字節buffer。與網絡和監聽過濾器同樣,返回的FilterStatus
提供了管理過濾器鏈控制流的功能。
當可使用HTTP/2編解碼器處理HTTP請求首部時,會首先傳遞給在CustomFilter中的decodeHeaders()
。若是返回的FilterHeadersStatus
爲Continue
,則HCM會將首部(可能會被CustomFilter修改)傳遞給路由過濾器。
解碼器和編解碼過濾器運行在請求路徑上,編碼器和編碼解碼過濾器運行在響應路徑上。考慮以下過濾器鏈:
請求路徑爲:
響應路徑爲:
當在路由過濾器中調用decodeHeaders()
時,會肯定路由選擇並挑選cluster。HCM會在HTTP過濾器鏈執行開始時從RouteConfiguration
中選擇一個路由,該路由被稱爲緩存路由。過濾器可能會經過要求HCM清除路由緩存並請求HCM從新評估路由選擇來修改首部,並致使選擇一個新的路由。當調用路由過濾器時,也就肯定了路由。顯選擇的路由會指向一個上游cluster名稱。而後路由過濾器會向ClusterManager 爲該cluster請求一個HTTP鏈接池。該過程涉及負載平衡和鏈接池,將在下一節中討論。
HTTP鏈接池是用來在router中構建一個UpstreamRequest對象,該對象封裝了用於處理上游HTTP請求的HTTP編碼和解碼的回調方法。一旦在HTTP鏈接池的鏈接上分配了一個流,則會經過UpstreamRequest::encoderHeaders()
將請求首部轉發到上游endpoint。
路由過濾器負責(從HTTP鏈接池上分配的流上的)到上游的請求的生命週期管理,同時也負責請求超時,重試和親和性等。
每一個cluster都有一個負載均衡,當接收到一個請求時會選擇一個endpoint。Envoy支持多種類型的負載均衡算法,如基於權重的輪詢,Maglev,負載最小,隨機等算法。負載均衡會從靜態的bootstrap配置,DNS,動態xDS以及主動/被動健康檢查中得到其須要處理的內容。更多詳情參見官方文檔。
一旦選擇了endpoint,會使用鏈接池來爲該endpoint選擇一個鏈接來轉發請求。若是沒有到該主機的鏈接,或全部的鏈接已經達到了併發流的上線,則會創建一條新的流,並將它放到鏈接池裏(除非觸發了羣集的最大鏈接的斷路器)。若是配置了流的最大生命時間,且已經達到了該時間點,那麼此時會在鏈接池中分配一個新的鏈接,並終止舊的HTTP/2鏈接。此外還會檢查其餘斷路器,如到一個cluster的最大併發請求等。
鏈接的HTTP/2的編解碼器會對單條TCP鏈接上的到達相同上游的其餘請求流進行多路複用,與HTTP/2編解碼器的解碼是相反的
與下游HTTP/2編解碼器同樣,上游的編解碼器負責採用Envoy的HTTP標準抽象,即多個流在單個鏈接上與請求/響應標頭/正文/尾部進行復用,並經過生成一系列HTTP/2幀將其映射到HTTP/2的細節中。
上游endpoint鏈接的TLS傳輸socket會加密來自HTTP/2編解碼器的輸出,並將其寫入到上游鏈接的TCP socket中。 與TLS傳輸套接字的解碼同樣,在咱們的示例中,羣集配置了傳輸套接字,用來提供TLS傳輸的安全性。上游和下游傳輸socket擴展都存在相同的接口。
請求包含首部,可選擇的主體和尾部,經過代理到上游,並將響應代理到下游。響應會經過以與請求相反的順序通過HTTP和network過濾器。
HTTP過濾器會調用解碼器/編碼器請求生命週期事件的各類回調,例如 當轉發響應尾部或請求主體被流式傳輸時。相似地,讀/寫network過濾器還將在數據在請求期間繼續在兩個方向上流動時調用它們各自的回調。
endpoint的異常檢測狀態會隨着請求的進行而修改。
當上遊響應到達流的末端後即完成了一個請求。即接收到尾部或帶有最終流集的響應頭/主體時。這個流程在Router::Filter::onUpstreamComplete()
在進行處理。
一個請求有可能提早結束,可能的緣由爲:
若是上游響應尚未發送,則Envoy會緣由生成一個內部的響應;若是響應首部已經轉發到了下游,則會重置流。更多參見Envoy的調試FAQ。
一旦請求完成,則流會被銷燬。發生的事件以下: