閘道匯總模式

Azure 流量管理員

使用閘道將多個個別要求匯總成單一要求。 當客戶端必須對不同的後端系統進行多次呼叫以執行作業時,此模式很有用。

內容和問題

若要執行單一工作,用戶端可能必須對各種後端服務進行多個呼叫。 依賴許多服務來執行工作的應用程式必須在每個要求上消耗資源。 將任何新功能或服務新增至應用程式時,需要額外的要求,進一步增加資源需求和網路呼叫。 用戶端與後端之間的這種閒聊可能會對應用程式的效能和規模產生負面影響。 微服務架構使得此問題更為常見,因為建置在許多較小服務的應用程式自然會有較高的跨服務呼叫量。

在下圖中,用戶端會將要求傳送至每個服務 (1,2,3)。 每個服務都會處理要求,並將回應傳回應用程式 (4,5,6)。 透過具有通常高延遲的行動電話通訊網路,以這種方式使用個別要求效率不佳,而且可能會導致連線中斷或要求不完整。 雖然每個要求都可以平行完成,但應用程式必須針對每個要求傳送、等候和處理數據,所有在個別連線上,增加失敗的機會。

閘道匯總模式的問題圖表

解決方案

使用閘道來減少客戶端與服務之間的閒聊。 閘道會接收用戶端要求、將要求分派至各種後端系統,然後匯總結果,並將其傳回要求用戶端。

此模式可減少應用程式對後端服務提出的要求數目,並改善高延遲網路的應用程式效能。

在下圖中,應用程式會將要求傳送至閘道 (1)。 要求包含其他要求的套件。 閘道會分解這些要求,並將它傳送至相關服務來處理每個要求 (2)。 每個服務都會傳回閘道的回應 (3)。 閘道會結合每個服務的回應,並將回應傳送至應用程式 (4)。 應用程式會發出單一要求,並只接收來自閘道的單一回應。

閘道匯總模式的解決方案圖表

問題和考慮

  • 閘道不應該在後端服務之間引入服務結合。
  • 網關應該位於後端服務附近,以盡可能減少延遲。
  • 閘道服務可能會造成單一失敗點。 請確定閘道已正確設計,以符合應用程式的可用性需求。
  • 閘道可能會造成瓶頸。 請確定閘道有足夠的效能來處理負載,並可調整以符合您預期的成長。
  • 對閘道執行負載測試,以確保您不會為服務帶來串聯失敗。
  • 使用重頭、斷路器重試和逾時等技術,實作復原性設計。
  • 如果一或多個服務呼叫花費的時間太長,可能會可以接受逾時並傳回一組部分數據。 請考慮您的應用程式如何處理此案例。
  • 使用異步 I/O 來確保後端的延遲不會在應用程式中造成效能問題。
  • 使用相互關聯標識碼實作分散式追蹤,以追蹤每個個別的呼叫。
  • 監視要求計量和回應大小。
  • 請考慮傳回快取的數據作為處理失敗的故障轉移策略。
  • 請考慮將匯總服務放在閘道後面,而不是將匯總建置至閘道。 要求匯總可能會有不同於網關中其他服務的資源需求,而且可能會影響網關的路由和卸除功能。

使用此模式的時機

當下列情況時,請使用此模式:

  • 客戶端必須與多個後端服務通訊,才能執行作業。
  • 用戶端可能會使用具有大量延遲的網路,例如行動數據網路。

此模式在下列情況下可能不適合:

  • 您想要減少用戶端與跨多個作業的單一服務之間的呼叫數目。 在該案例中,最好將批次作業新增至服務。
  • 用戶端或應用程式位於後端服務附近,且延遲不是一個重要因素。

工作負載設計

架構設計人員應該評估閘道匯總模式在工作負載的設計中如何使用,以解決 Azure 架構良好架構要素涵蓋的目標和原則。 例如:

要素 此模式如何支援支柱目標
可靠性設計決策可協助工作負載復原到故障,並確保它會在發生失敗后復原到完全正常運作的狀態。 此拓撲可讓您將暫時性錯誤處理從跨客戶端的分散式實作轉移到集中式實作。

- RE:07 暫時性錯誤
安全性 設計決策有助於確保 工作負載數據和系統的機密性完整性可用性 此拓撲通常會減少客戶端與系統搭配的接觸點數目,進而減少公用介面區和驗證點。 匯總的後端可以保持與用戶端完全網路隔離。

- SE:04 分割
- SE:08 強化
營運卓越可透過標準化的流程和小組凝聚力,協助提供工作負載品質 此模式可讓後端邏輯與客戶端獨立發展,讓您變更鏈結的服務實作,甚至是數據源,而不需要變更客戶端接觸點。

- OE:04 工具和程式
效能效率 可透過調整、數據、程式代碼的優化,有效率地協助您的工作負載 符合需求 此設計會產生比用戶端建立多個連線的設計更少的延遲。 匯總實作中的快取可將後端系統的呼叫降到最低。

- PE:03 選取服務
- PE:08 數據效能

如同任何設計決策,請考慮對其他可能以此模式導入之目標的任何取捨。

範例

下列範例說明如何使用 Lua 建立簡單的閘道匯總 NGINX 服務。

worker_processes  4;

events {
  worker_connections 1024;
}

http {
  server {
    listen 80;

    location = /batch {
      content_by_lua '
        ngx.req.read_body()

        -- read json body content
        local cjson = require "cjson"
        local batch = cjson.decode(ngx.req.get_body_data())["batch"]

        -- create capture_multi table
        local requests = {}
        for i, item in ipairs(batch) do
          table.insert(requests, {item.relative_url, { method = ngx.HTTP_GET}})
        end

        -- execute batch requests in parallel
        local results = {}
        local resps = { ngx.location.capture_multi(requests) }
        for i, res in ipairs(resps) do
          table.insert(results, {status = res.status, body = cjson.decode(res.body), header = res.header})
        end

        ngx.say(cjson.encode({results = results}))
      ';
    }

    location = /service1 {
      default_type application/json;
      echo '{"attr1":"val1"}';
    }

    location = /service2 {
      default_type application/json;
      echo '{"attr2":"val2"}';
    }
  }
}