異步請求 - 響應模式
一些場景下後端的邏輯處理需要以異步(而非同步)的方式進行,通過異步請求 - 響應模式,把邏輯處理與前端進行解耦,同時返回給前端一個邏輯清晰的響應。
產生背景
在當前最常見的應用開發模式下,客戶端應用程序(包括網絡瀏覽器裏的代碼)通常靠遠端的 API 服務提供業務邏輯上的支持,從而提供完整的用戶體驗。API 服務可以是和客戶端應用程序同時開發的,也可以是第三方的服務。大多數 API 調用基於 HTTP(S) 實現,並且遵循 REST 規範;目前移動端也會採用基於 HTTP/2 的 gRPC 協議。
在絕大多數應用場景中,API 設計時以快速響應爲目標,指標上體現爲 100ms 以內。可能導致響應延遲的因素很多,包括:
-
應用的託管模式:物理機 / 虛擬機 / 容器及對應的編排方案
-
安全組件:流量清洗服務等
-
client 與 server 的物理距離:如果一箇中國的瀏覽器訪問美國的網站,不考慮 CDN、中間沒有任何路由,數據也要繞一個地球,額外增加 130ms 的延遲;
-
網絡基礎設施:中轉路由器的數量、性能(是否有專線)、網絡接入速度、網卡速度等
-
當前的網絡負載:如果負載比較高,甚至有網絡擁塞,響應會變慢
-
請求報文的大小
-
要處理事件的隊列大小
-
後端處理請求的耗時
上面提到的任何因素都會增加網絡延遲。一些可以通過對後端服務擴容來緩解,另外一些可能是開發者不可控的部分,比如網絡基礎設施。大多數 API 都能夠在同一個連接上快速響應請求。client 端代碼可以以非阻塞的方式進行 API 同步調用,比較適合 IO 密集型服務。注意:非阻塞看起來有點像異步處理,但阻塞 / 非阻塞、同步 / 異步是完全不同的概念,阻塞 / 非阻塞是線程從網卡讀取 / 發送數據角度的概念,同步 / 異步是服務架構角度 client/server 進行交互相關的概念,網上有很多相關的面試八股,這裏不細說了。
儘管常規的 API 對短請求做了很多優化,在一些業務場景中,後端處理的時間比較長,以秒、分甚至小時作爲計時單位。這類場景下,如果等着任務完成後再響應客戶端的請求,必然會有問題。
爲了解決這類問題,一些架構引入了消息隊列中間件,消息隊列把請求階段和響應階段進行了分離。這類架構的額外收益是可以對請求進行削峯,以避免高峯期服務器被打垮,比如美國和印度每年一次的報稅日。請求 - 響應分離還允許前端和後端 API 能夠獨立進行擴縮容,同時增加了架構的複雜度,因爲 client 接收成功通知也需要異步處理。
在異步請求 - 響應模式下要考量很多個方面,分佈式系統中 Server 間的 REST API 調用存在同樣的問題,一般出現在微服務架構中。
解決方案
最簡單的解決方案是 HTTP 輪詢。在 client 端 (瀏覽器 / 安卓 / iOS / 小程序等) 代碼中我們只能輪詢,因爲 client 端基本上無法提供回調接口,也很難使用長連接。即便理論上回調是可支持的,額外的庫依賴或服務依賴使得技術方案極其複雜,讓人望而卻步。
使用 HTTP 輪詢的規範如下:
-
client 端發起對 API 服務的同步調用,觸發一個長任務的創建和執行;
-
API 服務在最短的時間內對 client 端同步做出響應。返回的 HTTP 202 (Accepted),表示請求已經被接收;
注意:API 服務在創建長任務之前,應當校驗請求的合法性和即將執行的操作的合理性。如果請求不合理,應當立即返回 400(Bad Request)
-
響應中應當攜帶任務狀態的輪詢地址,以便 client 端後續檢查長任務的執行狀態;
-
API 服務把請求存儲到數據庫,併發送給消息隊列中間件;
-
對於每一個狀態輪詢請求,如果成功則返回 200。如果任務還處於執行中,響應中應該包含對應的狀態碼。一旦任務執行完成,響應中應當包含一個狀態碼,標識任務完成,或重定向到一個新的 URL(查看任務產出的數據)。
一個典型的工作流如下圖所示:
-
client 發送一個請求,接收到狀態碼爲 HTTP 202 (Accepted) 的響應;
-
client 持續對狀態查詢接口發起 HTTP GET 請求,任務狀態爲執行中,所以返回 HTTP 200;
-
在某個時間點,任務執行完成,查詢接口返回 HTTP 302 (Found),並重定向到對應的資源;
-
client 從資源 URL 獲取任務的執行結果;
問題和注意事項
-
僅僅基於 HTTP,異步請求 - 響應模式就有很多實現方式,並不是所有上游服務都遵循同樣的語義。舉個例子,當遠端的任務還在執行中時,大多數服務的狀態查詢接口並不會返回 HTTP 202;如果遵循純粹的 REST 語義,應該返回 HTTP 404(Not Found)。由於請求的結果還沒有產出,這兩種狀態碼都是合理的;
-
HTTP 202 的響應應該包含輪詢的地址和頻率,通常定義在 header 裏:
-
我們可以使用代理去操作響應的 header 或 payload(數據)
-
如果狀態查詢接口重定向到任務完成的資源鏈接,可以使用 HTTP 302/303;
-
任務創建成功後,client 再去調用 header["Location"] 中的 URL,接收到的狀態碼應當是 200(OK), 201(Created), 或 204(No Content) ;
-
如果在任務執行中出現錯誤,應該把錯誤在 header["Location"] 中的 URL 中體現出來,如果 client 訪問這個 URL 應當接收到 4xx 錯誤;
-
舊的 client 可能不支持該模式,就需要我們在異步服務至上封裝一層同步 API,以保證兼容性;
-
在一些場景中,API 需要提供取消機制,通常以提供取消接口的方式實現;
什麼時候(不)使用該模式
該模式適合的場景有:
-
client 端代碼很難提供回調接口,或者使用長鏈接代價過高。比如網絡瀏覽器、移動端 app;
-
服務端調用只支持 HTTP 協議,且由於防火牆 Server 端無法發起回調;
-
服務端需要兼容比較舊的系統,無法使用現代的回調技術,比如 WebSocket 或 Webhook。
該模式不適合的場景有:
-
有現成通知機制的服務;
-
響應必須以實時流的方式返回給 client,比如 SSE;
-
client 必須從多個服務獲取結果,對每個結果的延遲比較敏感。這時可以考慮 Service Bus 模式;
-
可使用持久化連接技術,比如 WebSockets 或 SignalR,client-server 可藉助這些技術實現雙向通信;
-
網絡防火牆允許開放一些端口,接收異步回調或 webhooks;
例子
-
調度框架:比如大數據調度框架 YARN、容器編排工具 Kubernetes;
-
支付場景:需要先對 transaction 進行持久化,再扔到隊列裏,由 worker 撿起任務執行;
-
大量數據的下載
有一些比較典型的數據量大但不適用異步的場景,比如推廣搜中的廣告召回等;
還有一些場景處於模糊地帶,比如大數據計算,從早期基於 Hive/Spark 的異步計算,逐漸演變到 Clickhouse 的同步計算,大大提升了用戶體驗。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/YkOoLbH2P2HheQxomGsbNQ