訂單詳情頁的配送進度,平常呼叫第三方API只要0.5秒,今天卻要等上10秒。沒過多久,連不需要配送資訊的訂單列表,也開始變慢甚至逾時。
配送進度只是補充資訊,暫時查不到,原本仍可查看訂單內容。為什麼一個外部查詢,會讓其他功能跟著受到影響?
非同步等待,仍然占用資源
使用非同步呼叫,等待回應時不必一直占住執行緒,但請求本身仍需要記憶體,也可能占用連線與同時處理的名額。等待的工作越多,留在系統裡的資源也可能越多。假設每秒有20次配送查詢,平常0.5秒完成,穩定流量下平均約有10次尚未結束。當平均回應時間拉長到10秒,在系統仍能承接相同流量的情況下,就可能累積到平均約200次。
如果這些請求占滿了整個應用程式共用的處理名額,訂單列表即使不查物流,也可能跟著排隊。記憶體壓力增加,同樣可能影響其他操作。至於HTTP連線是否互相影響,還要看目的端與連線池設定。不能只因為兩個功能共用同一個HttpClient,就認定它們一定會搶同一組連線。
因此,CPU不高,也不能直接判斷系統還有餘裕。沿著請求查下去,需要知道它在等什麼,以及等待期間占用了哪些共用資源。
逾時要看整次操作,也要算進重試
假設頁面最多願意等配送資訊兩秒,但程式設定每次呼叫兩秒逾時,失敗後再重試兩次。三次都等到逾時,光是呼叫就可能接近六秒,還沒算重試間隔。
這時可以把兩秒視為整段操作的時間預算,讓後續呼叫與重試都受到同一個期限限制。以.NET為例,可以設定整體取消期限,再透過CancellationToken傳給支援取消的下層操作。第一次已經用完預算,就不再開始下一次重試;本地停止等待,也不代表遠端一定已經停止處理。
重試還可能出現在不同層。應用程式最多嘗試三次,底層套件每次又最多嘗試三次,一次查詢就可能變成九次呼叫。下游已經變慢時,新增的要求就會再增加負擔。
限制同時等待的數量,讓其他功能繼續運作
即使每次只等兩秒,尖峰時仍可能累積大量配送查詢。因此,除了時間上限,也可以為這類查詢設定共用的並行上限,限制同時執行的數量。
限制數量滿了之後,不一定要繼續排隊。這個案例的配送資訊可以暫時不提供,就能直接顯示「配送資訊暫時無法更新」,讓訂單主體先呈現。如果選擇排隊,排隊數量與等待時間也需要有上限,否則只是把等待搬到另一個地方。
限制器的作用範圍也要對得上預期。每個請求各自建立一個限制器,無法約束整個服務的呼叫量;每台主機各允許十次,四台合計就可能同時送出四十次。這個總量是否合適,要和下游容量、部署方式一起看。
取不到資料時,畫面顯示什麼也會影響後續操作。查不到配送進度,不能預設顯示「尚未出貨」;不知道最新狀態,和確定尚未出貨,會讓客服做出不同判斷。
這裡的降級,是先保留仍能提供的功能,清楚交代暫時缺少的資訊。若某個操作必須依最新配送狀態才能執行,就仍要限制那個操作,不能因為訂單內容已載入,就讓使用者照常送出。
*防護外部依賴,前面要用時間預算與並行限制鎖住連線資源,後面要用優雅降級做好商務防線——寧可明確標註「資訊暫時未知」,也絕不讓預設值或舊快取,無意間產出誤導決策的假狀態。
「物流API逾時了,畫面先顯示『尚未出貨』。」
「可是客戶說他已經收到貨了。」
「至少畫面沒有顯示錯誤。」
「對,客戶現在以為收到的是詐騙包裹。」
![]()