測試使用 Microsoft.Extensions.Http.Resilience 的 .NET 應用程式中的重試與逾時

Tip

第一次接觸節流? 了解什麼是限速(throttling)以及如何處理它。

一目了然
目標:確認你的 .NET HTTP 韌性處理器會如預期重試、退避並超時
時間: 15分鐘
插件:GenericRandomErrorPlugin、RetryAfterPlugin、LatencyPlugin
前置條件:設定 Dev Proxy,這是一個使用 Microsoft.Extensions.Http.Resilience 的 .NET 應用程式

你已將 AddStandardResilienceHandler() 新增至你的 HttpClient。 你怎麼知道它有效? 你呼叫的 API 很少能依需求刻意失敗,而模擬 HttpMessageHandler 的單元測試會跳過你想測試的韌性管線。

Dev Proxy 位於你的應用程式和 API 之間。 它會回傳錯誤和緩慢的回應給你的應用程式,所以你的應用程式無須修改即可執行,韌性處理器會對真實的 HTTP 回應做出反應。 你會在 Dev Proxy 輸出中看到每一次嘗試。

標準韌性處理常式的作用

在測試前,先知道會遇到什麼。 在預設選項下,AddStandardResilienceHandler():

行為 預設
重試已啟用 HTTP 500 及以上、408、429、 HttpRequestException和 TimeoutRejectedException
重試次數 3,並以指數式後退與抖動,從2秒開始
Retry-After 標頭 榮幸。 處理器會等待 API 所要求的時間。
嘗試超時 每次嘗試10秒
總逾時 請求時間30秒,包含所有重試

完整策略及其預設值列表,請參見 標準韌性處理者預設值。

透過 Dev Proxy 路由你的應用程式

.NET 使用系統代理,所以當你啟動 Dev Proxy 時,它會攔截應用程式的請求,且不需修改程式碼。 欲了解更多資訊,請參閱「使用 Dev Proxy 搭配 .NET 應用程式」。

模擬瞬態錯誤

建立一個開發代理設定,讓對 API 的請求因處理常式會重試的錯誤而失敗。 這個範例使用 https://api.contoso.com。 用你應用程式呼叫的 API URL 來取代它。

檔案: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "RetryAfterPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll"
    },
    {
      "name": "GenericRandomErrorPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "transientErrors"
    }
  ],
  "urlsToWatch": [
    "https://api.contoso.com/*"
  ],
  "transientErrors": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.schema.json",
    "errorsFile": "transient-errors.json",
    "rate": 50
  }
}

Caution

請將RetryAfterPlugin新增在GenericRandomErrorPlugin之前的組態檔中。 如果你是在之後才加,GenericRandomErrorPlugin 會在 RetryAfterPlugin 檢查之前先讓該請求失敗。

在 error 檔案中,定義一個限速回應和兩個伺服器錯誤。 @dynamic 值會設定 Retry-After 標頭,並告訴 RetryAfterPlugin 檢查你的應用程式是否等了那麼久才再次呼叫 API。

檔案: transient-errors.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.errorsfile.schema.json",
  "errors": [
    {
      "request": {
        "url": "https://api.contoso.com/*"
      },
      "responses": [
        {
          "statusCode": 429,
          "headers": [
            {
              "name": "Retry-After",
              "value": "@dynamic"
            }
          ]
        },
        {
          "statusCode": 500
        },
        {
          "statusCode": 503
        }
      ]
    }
  ]
}

啟動 Dev Proxy 並執行你的應用程式。

devproxy --config-file devproxyrc.json

失敗率高達 50%,大多數請求在一兩次重試後就能恢復。 在開發代理輸出中,請檢查:

  • 在收到 429 回應後,對同一 URL 的下一次嘗試會在 Retry-After 時間之後進行。 Dev Proxy 預設使用 5 秒。 如果你的應用程式太早呼叫 API,RetryAfterPlugin 會回報此情況並限制該請求。
  • 你的應用程式發送的重試次數不會超過你設定的數量。
  • 你不想重試的請求,比如會建立紀錄的 POST,只會發送一次。 要排除它們,請使用 options.Retry.DisableForUnsafeHttpMethods() 或 options.Retry.DisableFor(...)。

測試重試用完後會發生什麼

重試會隱藏短暫的失敗。 你也需要知道當 API 持續失敗時,你的應用程式會怎麼做。 以 100% 失敗率啟動 Dev Proxy:

devproxy --config-file devproxyrc.json --failure-rate 100

Dev Proxy 顯示每個請求有 4 次嘗試:原始請求和 3 次重試。 最後一次重試後,標準處理常式不會再拋出例外。 它會回傳你程式碼的最後錯誤回應。 看看你的應用程式怎麼處理它。 例如,EnsureSuccessStatusCode() 會引發 HttpRequestException,而 GetStringAsync() 也會引發例外。 確保你的應用程式顯示有用的訊息或採用備援方案,而不是當機或顯示通用錯誤。

測試逾時

慢速的 API 會在處理器中觸發不同的路徑。 測試時,可以用 LatencyPlugin 將回應延遲到超過 10 秒的嘗試逾時時間。

檔案: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "LatencyPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "slowApi"
    }
  ],
  "urlsToWatch": [
    "https://api.contoso.com/*"
  ],
  "slowApi": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/latencyplugin.schema.json",
    "minMs": 11000,
    "maxMs": 15000
  }
}

啟動 Dev Proxy 並執行你的應用程式。 每次嘗試都會超過 10 秒,因此嘗試逾時會將其取消,而處理常式會重試。 30 秒後,總逾時會取消請求,你的程式碼會收到 TimeoutRejectedException。 檢查你的應用程式是否能偵測到並告訴使用者發生了什麼事。

Tip

要用你自己的韌性設定測試同樣的情況,請更改 AddStandardResilienceHandler(options => ...) 呼叫中的數值,並重新執行相同的開發代理設定。

下一步

了解更多關於在任何 API 上模擬限速的資訊。

也請參閱