請求的生命週期

以下我們描述請求通過 Envoy 代理的生命週期中的事件。我們首先描述 Envoy 如何適用於請求的請求路徑,然後描述從下游到達 Envoy 代理後發生的內部事件。我們會追蹤請求直到對應的上游分派和回應路徑。

術語

Envoy 在其程式碼庫和文件中使用以下術語

  • 叢集 (Cluster):具有一組端點的邏輯服務,Envoy 會將請求轉發到這些端點。

  • 下游 (Downstream):連接到 Envoy 的實體。這可能是本機應用程式(在 Sidecar 模型中)或網路節點。在非 Sidecar 模型中,這是遠端用戶端。

  • 端點 (Endpoints):實作邏輯服務的網路節點。它們會分組到叢集中。叢集中的端點是 Envoy 代理的上游

  • 篩選器 (Filter):連線或請求處理管線中的模組,提供請求處理的某些方面。Unix 的一個類比是使用 Unix 管道(篩選器鏈)組合小型實用程式(篩選器)。

  • 篩選器鏈 (Filter chain):一系列篩選器。

  • 監聽器 (Listeners):Envoy 模組負責繫結到 IP/埠、接受新的 TCP 連線(或 UDP 資料包)並協調請求處理的面向下游方面。

  • 上游 (Upstream):Envoy 在轉發服務請求時連接到的端點(網路節點)。這可能是本機應用程式(在 Sidecar 模型中)或網路節點。在非 Sidecar 模型中,這對應於遠端後端。

網路拓撲

請求如何流經網路中的元件(包括 Envoy)取決於網路的拓撲。Envoy 可用於各種網路拓撲。我們將重點放在下面 Envoy 的內部運作,但在本節中我們會簡要說明 Envoy 如何與網路的其餘部分相關。

Envoy 源自 服務網格 Sidecar 代理,從應用程式中分離出負載平衡、路由、可觀測性、安全性和探索服務。在服務網格模型中,請求會透過 Envoy 作為網路的閘道。請求透過入口或出口監聽器到達 Envoy

  • 入口監聽器會接收來自服務網格中其他節點的請求,並將它們轉發到本機應用程式。來自本機應用程式的回應會流回 Envoy 到下游。

  • 出口監聽器會接收來自本機應用程式的請求,並將它們轉發到網路中的其他節點。這些接收節點通常也會執行 Envoy,並透過其入口監聽器接受請求。

../_images/lor-topology-service-mesh.svg ../_images/lor-topology-service-mesh-node.svg

Envoy 除了服務網格之外,還用於各種設定。例如,它也可以作為內部負載平衡器

../_images/lor-topology-ilb.svg

或作為網路邊緣上的入口/出口代理

../_images/lor-topology-edge.svg

在實務上,通常會使用這些的混合,其中 Envoy 會在服務網格中、邊緣上以及作為內部負載平衡器中發揮作用。請求路徑可能會遍歷多個 Envoy。

../_images/lor-topology-hybrid.svg

Envoy 可以在多層拓撲中設定以實現可擴展性和可靠性,請求會先通過邊緣 Envoy,然後再通過第二個 Envoy 層

../_images/lor-topology-tiered.svg

在上述所有情況下,請求會透過 TCP、UDP 或 Unix 網域套接字從下游到達特定的 Envoy。Envoy 會透過 TCP、UDP 或 Unix 網域套接字將請求轉發到上游。我們將重點放在下面的單個 Envoy 代理。

設定

Envoy 是一個非常可擴展的平台。這會導致可能的請求路徑發生組合爆炸,這取決於

  • L3/4 協定,例如 TCP、UDP、Unix 網域套接字。

  • L7 協定,例如 HTTP/1、HTTP/2、HTTP/3、gRPC、Thrift、Dubbo、Kafka、Redis 和各種資料庫。

  • 傳輸層套接字,例如純文字、TLS、ALTS。

  • 連線路由,例如 PROXY 協定、原始目的地、動態轉發。

  • 驗證和授權。

  • 斷路器和離群值偵測設定和啟用狀態。

  • 許多其他用於網路、HTTP、監聽器、存取記錄、健康檢查、追蹤和統計資料擴充功能的設定。

一次專注於一個設定很有幫助,因此此範例涵蓋以下內容

為簡化起見,我們假設使用靜態引導設定檔

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:
        "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
    # 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
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
  - name: some_service
    # 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
    typed_extension_protocol_options:
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options:
            max_concurrent_streams: 100
  - name: some_statsd_sink
  # 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_sink

高階架構

Envoy 中的請求處理路徑有兩個主要部分

  • 監聽器子系統,處理下游請求處理。它還負責管理下游請求生命週期和到用戶端的回應路徑。下游 HTTP/2 編碼解碼存在於此處。

  • 叢集子系統,負責選取和設定到端點的上游連線。這是叢集和端點健康狀態、負載平衡和連線集區的知識所在。上游 HTTP/2 編碼解碼存在於此處。

這兩個子系統透過 HTTP 路由器篩選器橋接,該篩選器會將 HTTP 請求從下游轉發到上游。

../_images/lor-architecture.svg

我們使用上面的術語 監聽器子系統叢集子系統 來指代由頂層 ListenerManagerClusterManager 類別建立的模組和執行個體類別群組。在請求過程中,這些管理系統會在請求之前和期間執行個體化許多元件,例如監聽器、篩選器鏈、編碼解碼器、連線集區和負載平衡資料結構。

Envoy 具有 基於事件的執行緒模型。主執行緒負責伺服器生命週期、設定處理、統計資料等,以及一些 工作執行緒 處理請求。所有執行緒都圍繞事件迴圈 (libevent) 運作,而任何給定的下游 TCP 連線(包括其上的所有多工串流)都會由一個工作執行緒在其生命週期內處理。每個工作執行緒都會維護其自己的上游端點 TCP 連線集區。 UDP 處理使用 SO_REUSEPORT 來讓核心將來源/目的地 IP:埠元組一致地雜湊到同一個工作執行緒。UDP 篩選器狀態會針對給定的工作執行緒共享,篩選器負責根據需要提供工作階段語義。這與我們在下面討論的面向連線的 TCP 篩選器形成對比,後者篩選器狀態存在於每個連線,並且在 HTTP 篩選器的情況下,存在於每個請求的基礎上。

工作執行緒很少共享狀態,並以簡單的並行方式運作。此執行緒模型能夠擴展到非常高的核心計數 CPU。

請求流程

概觀

使用上述範例設定的請求和回應生命週期簡要概述

  1. 來自下游的 TCP 連線會由在 工作執行緒 上執行的 Envoy 監聽器 接受。

  2. 建立並執行 監聽器篩選器 鏈。它可以提供 SNI 和其他 TLS 前資訊。完成後,監聽器會匹配網路篩選器鏈。每個監聽器可能有多個篩選器鏈,這些篩選器鏈會根據目的地 IP CIDR 範圍、SNI、ALPN、來源埠等的某種組合進行匹配。傳輸層套接字,在我們的案例中為 TLS 傳輸層套接字,與此篩選器鏈關聯。

  3. 在網路讀取時,TLS 傳輸層套接字會將從 TCP 連線讀取的資料解密,轉換成解密後的資料流以進行後續處理。

  4. 接著會建立並執行網路篩選器鏈。對於 HTTP 來說,最重要的篩選器是 HTTP 連線管理器,它是鏈中的最後一個網路篩選器。

  5. HTTP 連線管理器中的 HTTP/2 編解碼器會將 TLS 連線的解密資料流解幀並解多工成多個獨立的資料流。每個資料流處理一個單一的請求和回應。

  6. 對於每個 HTTP 資料流,會建立並執行一個下游 HTTP 篩選器鏈。請求首先會通過 CustomFilter,該篩選器可以讀取並修改請求。最重要的 HTTP 篩選器是位於 HTTP 篩選器鏈末端的路由篩選器。當在路由篩選器上呼叫 decodeHeaders 時,會選取路由並選定叢集。該資料流上的請求標頭會轉發到該叢集中的上游端點。路由篩選器會從叢集管理器取得符合條件的叢集的 HTTP 連線池,以執行此操作。

  7. 接著會執行叢集特定的負載平衡,以找到端點。會檢查叢集的熔斷器,以判斷是否允許新的資料流。如果端點的連線池為空或容量不足,則會建立與該端點的新連線。

  8. 對於每個資料流,會建立並執行一個上游 HTTP 篩選器鏈。預設情況下,這僅包含 CodecFilter,將資料傳送到適當的編解碼器,但是如果叢集配置了上游 HTTP 篩選器鏈,則會在每個資料流上建立並執行該篩選器鏈,其中包含為重試和影子請求建立並執行獨立的篩選器鏈。

  9. 上游端點連線的 HTTP/2 編解碼器會將請求的資料流與流向該上游的任何其他資料流多工處理並成幀,它們會透過單一的 TCP 連線傳輸。

  10. 上游端點連線的 TLS 傳輸層套接字會加密這些位元組,並將它們寫入上游連線的 TCP 套接字。

  11. 包含標頭和可選的主體和尾部的請求會被代理到上游,而回應則會被代理到下游。回應會以與請求相反的順序通過 HTTP 篩選器,從編解碼器篩選器開始,經過任何上游 HTTP 篩選器,然後通過路由篩選器,並通過 CustomFilter,然後被傳送到下游。

  12. 如果啟用了獨立的半關閉,則會在請求和回應都完成後銷毀資料流 (在兩個方向上都觀察到 HTTP/2 資料流的 END_STREAM) 並且回應具有成功 (2xx) 狀態代碼。否則,即使請求尚未完成,也會在回應完成時銷毀資料流。請求後處理將更新統計資訊、寫入存取日誌並完成追蹤跨度。

我們將在以下章節中詳細說明每個步驟。

1. Listener TCP 接受

../_images/lor-listeners.svg

ListenerManager 負責取得代表監聽器的組態,並將多個繫結到各自 IP/埠的 Listener 實例化。監聽器可能處於以下三種狀態之一

  • 預熱中:監聽器正在等待組態相依性 (例如,路由組態、動態密碼)。監聽器尚未準備好接受 TCP 連線。

  • 啟用:監聽器已繫結到其 IP/埠並接受 TCP 連線。

  • 排空中:監聽器不再接受新的 TCP 連線,同時允許現有的 TCP 連線在排空期間繼續。

每個工作執行緒都會為每個已設定的監聽器維護其自己的 Listener 實例。每個監聽器都可以透過 SO_REUSEPORT 繫結到同一埠,或共用繫結到此埠的單一套接字。當新的 TCP 連線到達時,核心會決定哪個工作執行緒將接受連線,並且此工作執行緒的 Listener 將呼叫其 Server::ConnectionHandlerImpl::ActiveTcpListener::onAccept() 回呼。

2. 監聽器篩選器鏈和網路篩選器鏈比對

然後,工作執行緒的 Listener 會建立並執行監聽器篩選器鏈。篩選器鏈是透過套用每個篩選器的 篩選器工廠 所建立。工廠會知道篩選器的組態,並為每個連線或資料流建立新的篩選器實例。

在我們的 TLS 監聽器組態案例中,監聽器篩選器鏈包含TLS 檢測器篩選器 (envoy.filters.listener.tls_inspector)。此篩選器會檢查初始 TLS 交握並擷取伺服器名稱 (SNI)。然後,SNI 可用於篩選器鏈比對。雖然 TLS 檢測器會明確顯示在監聽器篩選器鏈組態中,但只要監聽器的篩選器鏈中需要 SNI (或 ALPN),Envoy 也能夠自動插入此篩選器。

../_images/lor-listener-filters.svg

TLS 檢測器篩選器實作了 ListenerFilter 介面。所有篩選器介面 (無論是監聽器還是網路/HTTP) 都要求篩選器為特定的連線或資料流事件實作回呼。在 ListenerFilter 的案例中,它是

virtual FilterStatus onAccept(ListenerFilterCallbacks& cb) PURE;

onAccept() 允許篩選器在 TCP 接受處理期間執行。回呼傳回的 FilterStatus 會控制監聽器篩選器鏈的後續運作。監聽器篩選器可能會暫停篩選器鏈,然後稍後恢復,例如,為了回應對另一個服務發出的 RPC。

從監聽器篩選器和連線屬性擷取的資訊會用於比對篩選器鏈,進而提供將用於處理連線的網路篩選器鏈和傳輸層套接字。

../_images/lor-filter-chain-match.svg

3. TLS 傳輸層套接字解密

Envoy 透過 TransportSocket 擴充介面提供可插入的傳輸層套接字。傳輸層套接字會遵循 TCP 連線的生命週期事件,並讀取/寫入網路緩衝區。傳輸層套接字必須實作的一些主要方法包括

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 傳輸層套接字。然後,傳輸層套接字會對 TCP 連線執行 TLS 交握。當交握完成時,SslSocket::doRead() 會將解密的位元組串流提供給 Network::FilterManagerImpl 的實例,後者負責管理網路篩選器鏈。

../_images/lor-transport-socket.svg

請務必注意,沒有任何操作 (無論是 TLS 交握還是暫停篩選器管道) 是真正阻擋的。由於 Envoy 是事件驅動的,因此任何需要額外資料才能進行處理的情況都會導致事件提前完成,並將 CPU 讓給另一個事件。當網路提供更多可讀取的資料時,讀取事件會觸發 TLS 交握的恢復。

4. 網路篩選器鏈處理

如同監聽器篩選器鏈一樣,Envoy 會透過 Network::FilterManagerImpl 從其篩選器工廠實例化一系列的網路篩選器。每個新連線的實例都是全新的。網路篩選器 (如同傳輸層套接字) 會遵循 TCP 生命週期事件,並在傳輸層套接字有資料可用時被呼叫。

../_images/lor-network-filters.svg

網路篩選器被組成一個管道,這與每個連線只有一個的傳輸層套接字不同。網路篩選器有三種變體

  • ReadFilter 實作 onData(),當連線有資料可用時呼叫 (由於某些請求)。

  • WriteFilter 實作 onWrite(),當資料即將寫入連線時呼叫 (由於某些回應)。

  • Filter 實作 ReadFilterWriteFilter

主要篩選器方法的方法簽名為

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 篩選器鏈。在我們的範例中,這是唯一的網路篩選器。使用多個網路篩選器的範例網路篩選器鏈如下所示

../_images/lor-network-read.svg

在回應路徑上,網路篩選器鏈的執行順序與請求路徑相反。

../_images/lor-network-write.svg

5. HTTP/2 編碼解碼

Envoy 中的 HTTP/2 編碼器基於 nghttp2。它由 HCM 使用來自 TCP 連線的純文字位元組(在網路篩選器鏈轉換後)調用。編碼器將位元組串流解碼為一系列 HTTP/2 框架,並將連線多路分解為多個獨立的 HTTP 串流。串流多路復用是 HTTP/2 的關鍵功能,與 HTTP/1 相比,提供了顯著的效能優勢。每個 HTTP 串流處理單個請求和回應。

編碼器還負責處理 HTTP/2 設定框架以及串流和連線層級的 流量控制

編碼器負責抽象 HTTP 連線的細節,向 HTTP 連線管理器和 HTTP 篩選器鏈呈現標準視圖,將連線分割為多個串流,每個串流都有請求/回應標頭/主體/尾部。無論協定是 HTTP/1、HTTP/2 還是 HTTP/3,情況都是如此。

6. HTTP 篩選器鏈處理

對於每個 HTTP 串流,HCM 會實例化一個 下游 HTTP 篩選器鏈,遵循上面為監聽器和網路篩選器鏈建立的模式。

../_images/lor-http-filters.svg

有三種 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 標頭作為引數,而不是位元組緩衝區。與網路和監聽器篩選器一樣,返回的 FilterStatus 提供了管理篩選器鏈控制流程的能力。

當 HTTP/2 編碼器使 HTTP 請求標頭可用時,這些標頭首先傳遞到 CustomFilter 中的 decodeHeaders()。如果返回的 FilterHeadersStatusContinue,則 HCM 會將標頭(可能由 CustomFilter 修改)傳遞到路由器篩選器。

解碼器和編碼器-解碼器篩選器在請求路徑上執行。編碼器和編碼器-解碼器篩選器在回應路徑上執行,順序與 相反方向。考慮以下範例篩選器鏈

../_images/lor-http.svg

請求路徑將如下所示

../_images/lor-http-decode.svg

而回應路徑將如下所示

../_images/lor-http-encode.svg

當在 路由器篩選器上調用 decodeHeaders() 時,路由選擇將最終確定,並選擇一個叢集。HCM 會在 HTTP 篩選器鏈執行開始時從其 RouteConfiguration 中選擇路由。這稱為「*快取路由*」。篩選器可能會修改標頭並導致選擇新的路由,方法是要求 HCM 清除路由快取並要求 HCM 重新評估路由選擇。篩選器也可以透過 setRoute 回呼直接設定此快取路由選擇。調用路由器篩選器時,路由將最終確定。所選路由的組態將指向上游叢集名稱。然後,路由器篩選器會向 ClusterManager 請求叢集的 HTTP 連線池。這涉及負載平衡和連線池,將在下一節中討論。

../_images/lor-route-config.svg

產生的 HTTP 連線池用於在路由器中建立一個 UpstreamRequest 物件,該物件封裝了上游 HTTP 請求的 HTTP 編碼和解碼回呼方法。在 HTTP 連線池中的連線上分配串流後,請求標頭會透過調用 UpstreamRequest::encodeHeaders 轉發到上游端點。

路由器篩選器負責上游請求生命週期管理在 HTTP 連線池中分配的串流上的所有方面。它還負責請求逾時、重試和關聯性。

路由器篩選器還負責建立和執行 上游 HTTP 篩選器鏈。依預設,上游 HTTP 篩選器會在標頭到達路由器篩選器後立即開始執行,但是,如果 C++ 篩選器需要檢查上游串流或連線,則可以暫停執行,直到建立上游連線為止。上游 HTTP 篩選器鏈依預設透過叢集組態設定,因此,例如,陰影請求可以針對主要和陰影叢集具有單獨的上游 HTTP 篩選器鏈。此外,由於上游 HTTP 篩選器鏈位於路由器篩選器的上游,因此每次重試嘗試都會執行該篩選器鏈,允許每次重試進行標頭操作,並包含有關上游串流和連線的資訊。與下游 HTTP 篩選器不同,上游 HTTP 篩選器無法變更路由。

7. 負載平衡

每個叢集都有一個 負載平衡器,該負載平衡器會在新請求到達時選擇端點。Envoy 支援各種負載平衡演算法,例如加權循環配置、Maglev、最少負載、隨機。負載平衡器會從靜態引導組態、DNS、動態 xDS(CDS 和 EDS 探索服務)以及主動/被動健康檢查的組合中取得其有效指派。有關負載平衡在 Envoy 中如何運作的更多詳細資訊,請參閱 負載平衡文件

選擇端點後,會使用此端點的 連線池 來尋找連線以轉發請求。如果沒有到主機的連線,或者所有連線都達到其最大並行串流限制,則會建立一個新的連線並放入連線池,除非叢集的最大連線熔斷器已觸發。如果設定並達到連線的最大生命週期串流限制,則會在池中分配一個新連線,並且會清空受影響的 HTTP/2 連線。也會檢查其他熔斷器,例如叢集的最大並行請求數。請參閱 熔斷器連線池,以取得更多詳細資訊。

../_images/lor-lb.svg

8. HTTP/2 編碼

所選連線的 HTTP/2 編碼器會透過單一 TCP 連線,將請求串流與任何其他發送到同一個上游的串流多路復用。這與 HTTP/2 編碼解碼 相反。

與下游 HTTP/2 編碼器一樣,上游編碼器負責採用 Envoy 的 HTTP 標準抽象概念,即在具有請求/回應標頭/主體/尾部的單一連線上多路復用的多個串流,並透過產生一系列 HTTP/2 框架將其對應到 HTTP/2 的細節。

9. TLS 傳輸插槽加密

上游端點連線的 TLS 傳輸插槽會加密來自 HTTP/2 編碼器輸出的位元組,並將其寫入上游連線的 TCP 插槽。如同 TLS 傳輸插槽解密,在我們的範例中,叢集已設定提供 TLS 傳輸安全性的傳輸插槽。上游和下游傳輸插槽延伸功能都存在相同的介面。

../_images/lor-client.svg

10. 回應路徑和 HTTP 生命週期

包含標頭以及可選主體和尾部的請求會向上游代理,而回應會向下游代理。回應會以與請求 相反的順序 通過 HTTP 和網路篩選器。

會在 HTTP 篩選器中調用解碼器/編碼器請求生命週期事件的各種回呼,例如,當正在轉發回應尾部或串流請求主體時。同樣地,當資料在請求期間持續雙向流動時,讀取/寫入網路篩選器也會調用它們各自的回呼。

端點的 離群值偵測 狀態會在請求進行時進行修訂。

代理完成且 HTTP/2 和 HTTP/3 協定的串流被銷毀的時間點由獨立的半關閉選項決定。如果啟用獨立的半關閉,則會在請求和回應都完成後銷毀串流,也就是說,透過接收尾部或在兩個方向中都設定了 end-stream 的標頭/主體,且回應具有成功 (2xx) 狀態代碼來達到各自的 end-of-stream。這在 FilterManager::checkAndCloseStreamIfFullyClosed() 中處理。

對於 HTTP/1 協定或如果停用獨立的半關閉,則會在回應完成並達到其 end-of-stream 時銷毀串流,也就是說,當接收到尾部或設定了 end-stream 的回應標頭/主體時,即使請求尚未完成。這在 Router::Filter::onUpstreamComplete() 中處理。

請求可能會提早終止。這可能是由於(但不限於)

  • 請求逾時。

  • 上游端點串流重設。

  • HTTP 篩選器串流重設。

  • 熔斷。

  • 上游資源不可用,例如遺失路由的叢集。

  • 沒有正常的端點。

  • 阻斷服務 (DoS) 保護。

  • HTTP 協定違規。

  • 來自 HCM 或 HTTP 過濾器的本地回覆。例如,速率限制 HTTP 過濾器返回 429 回應。

如果發生任何這些情況,Envoy 可能會發送內部產生的回應(如果上游回應標頭尚未發送),或者如果回應標頭已經向下游轉發,則會重置串流。Envoy 除錯常見問題 有關解釋這些早期串流終止的更多資訊。

11. 請求後處理

請求完成後,串流會被銷毀。還會發生以下情況:

  • 請求後的 統計資料 會被更新(例如,計時、活動請求、升級、健康檢查)。但是,某些統計資料會在請求處理期間更早更新。統計資料不會在此時寫入統計資料 接收器,它們會被批次處理並由主線程定期寫入。在我們的範例中,這是一個 statsd 接收器。

  • 存取日誌 會寫入存取日誌 接收器。在我們的範例中,這是一個檔案存取日誌。

  • 追蹤 span 會被最終確定。如果我們的範例請求被追蹤,當處理請求標頭時,HCM 會建立一個追蹤 span,描述請求的持續時間和詳細資訊,然後在請求後處理期間由 HCM 最終確定。