共用方式為


組建復原性 HTTP 應用程式: 主要開發模式

組建可從暫時性錯誤錯誤中復原的健全 HTTP 應用程式是常見的需求。 本文假設您已閱讀了復原性應用程式開發簡介,因為本文會延申討論該簡介所傳達的核心概念。 為了協助組建復原性 HTTP 應用程式,Microsoft.Extensions.Http.Resilience NuGet 套件會提供專為 HttpClient 設計的復原機制。 此 NuGet 套件會依賴 Microsoft.Extensions.Resilience 程式庫和 Polly 這個熱門的開放原始碼專案。 如需詳細資訊,請參閱 Polly

開始使用

若要在 HTTP 應用程式中使用復原性模式,請安裝 Microsoft.Extensions.Http.Resilience NuGet 套件。

dotnet add package Microsoft.Extensions.Http.Resilience --version 8.0.0

如需詳細資訊,請參閱 dotnet 新增套件在 .NET 應用程式中管理套件相依性

將復原功能新增至 HTTP 用戶端

若要將復原功能新增至 HttpClient,您可以鏈結從呼叫任何可用 AddHttpClient 方法所傳回之 IHttpClientBuilder 型別的呼叫。 如需詳細資訊,請參閱 搭配 .NET 使用 IHttpClientFactory

有數個以復原為中心的延伸模組可供使用。 一些標準的模組會採用各種產業最佳做法,而其他的則較容易自訂。 新增復原功能時,您應該只新增一個復原處理常式,並避免堆疊處理常式。 如果您需要新增多個復原處理常式,您應該考慮使用 AddResilienceHandler 擴充方法,這可讓您自訂復原策略。

重要

本文中的所有範例都依賴來自 Microsoft.Extensions.Http 程式庫的 AddHttpClientAPI,這會傳回 IHttpClientBuilder 執行個體。 IHttpClientBuilder 執行個體可用來設定 HttpClient 並新增復原處理常式。

新增標準復原處理常式

標準復原處理常式會使用堆疊在彼此上方的多個復原策略,並使用預設選項來傳送要求和處理任何暫時性錯誤。 標準復原處理常式是藉由在 IHttpClientBuilder 執行個體上呼叫 AddStandardResilienceHandler 擴充方法來新增。

var services = new ServiceCollection();

var httpClientBuilder = services.AddHttpClient<ExampleClient>(
    configureClient: static client =>
    {
        client.BaseAddress = new("https://jsonplaceholder.typicode.com");
    });

上述 程式碼:

  • 建立 ServiceCollection 執行個體。
  • ExampleClient 型別的 HttpClient 加入服務容器。
  • 設定 HttpClient 以使用 "https://jsonplaceholder.typicode.com" 作爲基底位址。
  • 建立本文中其他範例中使用的 httpClientBuilder

更真實的範例會依賴裝載,例如 .NET 泛型主機 一文中所述。 請使用 Microsoft.Extensions.Hosting NuGet 套件,考慮下列更新的範例:

using Http.Resilience.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

IHttpClientBuilder httpClientBuilder = builder.Services.AddHttpClient<ExampleClient>(
    configureClient: static client =>
    {
        client.BaseAddress = new("https://jsonplaceholder.typicode.com");
    });

前面的程式碼類似於手動 ServiceCollection 建立方法,但卻依賴 Host.CreateApplicationBuilder() 來組建公開服務的主機。

ExampleClient 的定義如下:

using System.Net.Http.Json;

namespace Http.Resilience.Example;

/// <summary>
/// An example client service, that relies on the <see cref="HttpClient"/> instance.
/// </summary>
/// <param name="client">The given <see cref="HttpClient"/> instance.</param>
internal sealed class ExampleClient(HttpClient client)
{
    /// <summary>
    /// Returns an <see cref="IAsyncEnumerable{T}"/> of <see cref="Comment"/>s.
    /// </summary>
    public IAsyncEnumerable<Comment?> GetCommentsAsync()
    {
        return client.GetFromJsonAsAsyncEnumerable<Comment>("/comments");
    }
}

上述 程式碼:

  • 定義具有接受 HttpClient 建構函式的 ExampleClient 型別。
  • 公開將 GET 要求傳送至 /comments 端點並傳回回應的 GetCommentsAsync 方法。

Comment 型別的定義如下:

namespace Http.Resilience.Example;

public record class Comment(
    int PostId, int Id, string Name, string Email, string Body);

假設您已建立 IHttpClientBuilder (httpClientBuilder),且您現在已了解 ExampleClient 實作和對應的 Comment 模型,請考慮下列範例:

httpClientBuilder.AddStandardResilienceHandler();

前面的程式碼會將標準復原處理常式新增至 HttpClient。 如同大部分的復原性 API,有多載可讓您自訂預設選項和套用的復原策略。

標準復原處理程序預設值

預設設定會依下列順序鏈結五個復原策略 (從最外層到最內層):

訂單 策略 描述 Defaults
1 速率限制器 速率限制器管線會限制傳送至相依性之並行要求的數目上限。 佇列:0
允許:1_000
2 總逾時 要求逾時管線總計會將整體逾時套用至執行,以確保要求,包括重試嘗試,不會超過設定的限制。 總逾時:30 秒
3 重試 重試管線會重試要求,以防相依性變慢或傳回暫時性錯誤。 重試次數上限:3
輪詢:Exponential
使用 Jitter:true
延遲:2 秒
4 斷路器 如果偵測到太多直接失敗或逾時,斷路器會封鎖執行。 失敗率:10%
最小輸送量:100
取樣持續時間:30 秒
中斷持續時間:5 秒
5 嘗試逾時 嘗試逾時管線會限制每個要求嘗試持續時間,並在超過時擲回。 嘗試逾時:10 秒

重試和斷路器

重試和斷路器策略都會處理一組特定的 HTTP 狀態碼和例外狀況。 請考慮下列 HTTP 狀態碼:

  • HTTP 500 和更新版本 (伺服器錯誤)
  • HTTP 408 (要求逾時)
  • HTTP 429 (太多要求)

此外,這些策略會處理下列例外狀況:

  • HttpRequestException
  • TimeoutRejectedException

新增標準對沖處理常式

標準對沖處理常式會使用標準對沖機制包裝要求的執行。 對沖會重試平行的緩慢要求。

若要使用標準對沖處理常式,請呼叫 AddStandardHedgingHandler 擴充方法。 下列範例會將 ExampleClient 設定為使用標準對沖處理常式。

httpClientBuilder.AddStandardHedgingHandler();

前面的程式碼會將標準對沖處理常式新增至 HttpClient

標準對沖處理常式預設值

標準對沖會使用斷路器集區,以確保不會對沖狀況不良的端點。 根據預設,集區中的選取項目是以 URL 授權 (結構描述 + 主機 + 連接埠) 為基礎。

提示

建議您藉由呼叫 StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthorityStandardHedgingHandlerBuilderExtensions.SelectPipelineBy 針對更進階的案例來設定策略選取的方式。

前面的程式碼會將標準對沖處理常式新增至 IHttpClientBuilder。 預設設定會依下列順序鏈結五個復原策略 (從最外層到最內層):

訂單 策略 描述 Defaults
1 要求逾時總計 要求逾時管線總計會將整體逾時套用至執行,以確保要求,包括對沖嘗試,不會超過設定的限制。 總逾時:30 秒
2 對沖 當相依性變慢或傳回暫時性錯誤時,對沖策略會針對多個端點執行要求。 路由是選項,預設只會對沖原始 HttpRequestMessage 所提供的URL。 嘗試次數下限:1
嘗試次數上限:10
延遲:2 秒
3 速率限制器 (每個端點) 速率限制器管線會限制傳送至相依性之並行要求的數目上限。 佇列:0
允許:1_000
4 斷路器 (每個端點) 如果偵測到太多直接失敗或逾時,斷路器會封鎖執行。 失敗率:10%
最小輸送量:100
取樣持續時間:30 秒
中斷持續時間:5 秒
5 嘗試逾時 (每個端點) 嘗試逾時管線會限制每個要求嘗試持續時間,並在超過時擲回。 逾時:10 秒

自訂對沖處理常式路由選取項目

使用標準對沖處理常式時,您可以藉由呼叫 IRoutingStrategyBuilder 型別上的各種延伸模組來自訂要求端點選取的方式。 這適用於 A/B 測試等案例,您可以在其中將要求百分比路由傳送至不同的端點:

httpClientBuilder.AddStandardHedgingHandler(static (IRoutingStrategyBuilder builder) =>
{
    // Hedging allows sending multiple concurrent requests
    builder.ConfigureOrderedGroups(static options =>
    {
        options.Groups.Add(new UriEndpointGroup()
        {
            Endpoints =
            {
                // Imagine a scenario where 3% of the requests are 
                // sent to the experimental endpoint.
                new() { Uri = new("https://example.net/api/experimental"), Weight = 3 },
                new() { Uri = new("https://example.net/api/stable"), Weight = 97 }
            }
        });
    });
});

上述 程式碼:

  • 將沖處理常式新增至 IHttpClientBuilder
  • IRoutingStrategyBuilder 設定為使用 ConfigureOrderedGroups 方法來設定已排序的群組。
  • EndpointGroup 新增至 orderedGroup,這會將 3% 的要求路由傳送至 https://example.net/api/experimental 端點,以及將 97% 的要求路由傳送至 https://example.net/api/stable 端點。
  • IRoutingStrategyBuilder 設定為使用 ConfigureWeightedGroups 方法來設定

若要設定加權群組,請在 IRoutingStrategyBuilder 型別上呼叫 ConfigureWeightedGroups 方法。 下列範例會將 IRoutingStrategyBuilder 設定為使用 ConfigureWeightedGroups 方法來設定加權群組。

httpClientBuilder.AddStandardHedgingHandler(static (IRoutingStrategyBuilder builder) =>
{
    // Hedging allows sending multiple concurrent requests
    builder.ConfigureWeightedGroups(static options =>
    {
        options.SelectionMode = WeightedGroupSelectionMode.EveryAttempt;

        options.Groups.Add(new WeightedUriEndpointGroup()
        {
            Endpoints =
            {
                // Imagine A/B testing
                new() { Uri = new("https://example.net/api/a"), Weight = 33 },
                new() { Uri = new("https://example.net/api/b"), Weight = 33 },
                new() { Uri = new("https://example.net/api/c"), Weight = 33 }
            }
        });
    });
});

上述 程式碼:

  • 將沖處理常式新增至 IHttpClientBuilder
  • IRoutingStrategyBuilder 設定為使用 ConfigureWeightedGroups 方法來設定加權群組。
  • SelectionMode 設定為 WeightedGroupSelectionMode.EveryAttempt
  • WeightedEndpointGroup 新增至 weightedGroup,這會將 33% 的要求路由傳送至 https://example.net/api/a 端點,33% 的要求路由傳送至 https://example.net/api/b 端點,以及將 33% 的要求路由傳送至 https://example.net/api/c 端點。

提示

對沖嘗試的最大數目會直接與已設定的群組數目相互關聯。 例如,如果您有兩個群組,嘗試次數的上限則為兩次。

如需詳細資訊,請參閱 Polly 文件: 對沖復原策略

設定已排序的群組或加權群組很常見,但設定這兩者都是有效的。 在您想要將要求的某個百分比傳送至不同端點的案例中 (例如 A/B 測試的案例),使用已排序和加權群組會很有幫助。

新增自訂復原處理常式

若要擁有更多控制,您可以使用 AddResilienceHandler API 來自訂復原處理程式。 此方法接受委派,這個委派會設定來建立復原策略的 ResiliencePipelineBuilder<HttpResponseMessage> 執行個體。

若要設定具名的復原處理常式,請使用處理常式的名稱呼叫 AddResilienceHandler 擴充方法。 下列範例會設定名為 "CustomPipeline" 的具名復原處理常式。

httpClientBuilder.AddResilienceHandler(
    "CustomPipeline",
    static builder =>
{
    // See: https://www.pollydocs.org/strategies/retry.html
    builder.AddRetry(new HttpRetryStrategyOptions
    {
        // Customize and configure the retry logic.
        BackoffType = DelayBackoffType.Exponential,
        MaxRetryAttempts = 5,
        UseJitter = true
    });

    // See: https://www.pollydocs.org/strategies/circuit-breaker.html
    builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
    {
        // Customize and configure the circuit breaker logic.
        SamplingDuration = TimeSpan.FromSeconds(10),
        FailureRatio = 0.2,
        MinimumThroughput = 3,
        ShouldHandle = static args =>
        {
            return ValueTask.FromResult(args is
            {
                Outcome.Result.StatusCode:
                    HttpStatusCode.RequestTimeout or
                        HttpStatusCode.TooManyRequests
            });
        }
    });

    // See: https://www.pollydocs.org/strategies/timeout.html
    builder.AddTimeout(TimeSpan.FromSeconds(5));
});

上述 程式碼:

  • 將名稱為 "CustomPipeline" 作爲 pipelineName 的復原處理常式新增至服務容器中。
  • 將具有指數輪詢、五次重試和抖動喜好設定的重試策略新增至復原建立器中。
  • 新增取樣持續時間為 10 秒的斷路器策略、失敗比率為 0.2 (20%),最小輸送量為 3,以及處理 RequestTimeoutTooManyRequests HTTP 狀態代碼的述詞給復原建立器。
  • 將有五秒逾時的逾時策略新增至復原建立器。

每個復原策略都有許多可用的選項。 如需詳細資訊,請參閱 Polly 文件: 策略。 如需設定 ShouldHandle 委派的詳細資訊,請參閱 Polly 文件: 回應式策略中的錯誤處理

動態重新載入

Polly 支援動態重新載入已設定的復原策略。 這表示您可以在運行時間變更復原策略的設定。 若要啟用動態重新載入,請使用公開 ResilienceHandlerContextAddResilienceHandler多載。 鑑於該内容,請呼叫對應復原策略選項的 EnableReloads:

httpClientBuilder.AddResilienceHandler(
    "AdvancedPipeline",
    static (ResiliencePipelineBuilder<HttpResponseMessage> builder,
        ResilienceHandlerContext context) =>
    {
        // Enable reloads whenever the named options change
        context.EnableReloads<HttpRetryStrategyOptions>("RetryOptions");

        // Retrieve the named options
        var retryOptions =
            context.GetOptions<HttpRetryStrategyOptions>("RetryOptions");

        // Add retries using the resolved options
        builder.AddRetry(retryOptions);
    });

上述 程式碼:

  • 將名稱為 "AdvancedPipeline" 作爲 pipelineName 的復原處理常式新增至服務容器中。
  • 每當具名 RetryStrategyOptions 選項變更時,啟用 "AdvancedPipeline" 管線的重載。
  • IOptionsMonitor<TOptions> 服務擷取具名選項。
  • 將具有擷取選項的重試策略新增至復原建立器。

如需詳細資訊,請參閱 Polly 文件: 進階相依性插入

此範例會依賴能夠變更的選項區段,例如 appsettings.json 檔案。 以下列 appsettings.json 檔案為例:

{
    "RetryOptions": {
        "Retry": {
            "BackoffType": "Linear",
            "UseJitter": false,
            "MaxRetryAttempts": 7
        }
    }
}

現在假設這些選項已繫結至應用程式的設定,並將繫結 HttpRetryStrategyOptions"RetryOptions" 區段:

var section = builder.Configuration.GetSection("RetryOptions");

builder.Services.Configure<HttpStandardResilienceOptions>(section);

如需詳細資訊,請參閱 .NET 中的選項模式

範例使用方式

您的應用程式會依賴 相依性插入 來解析 ExampleClient 及其對應的 HttpClient。 程式碼會組建 IServiceProvider 並從中解析 ExampleClient

IHost host = builder.Build();

ExampleClient client = host.Services.GetRequiredService<ExampleClient>();

await foreach (Comment? comment in client.GetCommentsAsync())
{
    Console.WriteLine(comment);
}

上述 程式碼:

想像一下網路中斷或伺服器沒有回應的情況。 下圖顯示復原策略在 ExampleClientGetCommentsAsync 方法的情況下會如何處理情況:

具有復原管線的範例 HTTP GET 工作流程。

上圖描述:

  • ExampleClient 會將 HTTP GET 要求傳送至 /comments 端點。
  • 會在以下狀況時評估 HttpResponseMessage:
    • 如果回應成功 (HTTP 200),則會傳回回應。
    • 如果回應失敗 (HTTP 非 200),復原管線會採用已設定的復原策略。

雖然這是一個簡單的範例,但它示範如何使用復原策略來處理暫時性錯誤。 如需詳細資訊,請參閱 Polly 文件: 策略

已知問題

下列各節詳細說明各種已知問題。

Grpc.Net.ClientFactory 套件的相容性

如果您使用 Grpc.Net.ClientFactory 版本 2.63.0 或更早版本,則啟用 gRPC 用戶端的標準復原能力或避險處理常式,可能會導致執行階段例外狀況。 具體而言,請考慮下列程式碼範例:

services
    .AddGrpcClient<Greeter.GreeterClient>()
    .AddStandardResilienceHandler();

上述程式碼會導致以下例外狀況:

System.InvalidOperationException: The ConfigureHttpClient method is not supported when creating gRPC clients. Unable to create client with name 'GreeterClient'.

若要解決此問題,建議您升級至 Grpc.Net.ClientFactory 版本 2.64.0 或更新版本。

建置時間檢查會驗證您是否使用 Grpc.Net.ClientFactory 版本 2.63.0 或更早版本,以及您的檢查是否會產生編譯警告。 您可以在專案檔案中設定下列屬性來隱藏警告:

<PropertyGroup>
  <SuppressCheckGrpcNetClientFactoryVersion>true</SuppressCheckGrpcNetClientFactoryVersion>
</PropertyGroup>

與 .NET Application Insights 的相容性

如果您使用 .NET Application Insights,則啟用應用程式中的復原功能可能會導致所有 Application Insights 遙測遺失。 在 Application Insights 服務之前註冊復原功能時,就會發生此問題。 請考慮下列造成問題的範例:

// At first, we register resilience functionality.
services.AddHttpClient().AddStandardResilienceHandler();

// And then we register Application Insights. As a result, Application Insights doesn't work.
services.AddApplicationInsightsTelemetry();

此問題是由 Application Insights 中的下列錯誤所造成,而且可以藉由在復原功能之前註冊 Application Insights 服務來修正,如下所示:

// We register Application Insights first, and now it will be working correctly.
services.AddApplicationInsightsTelemetry();
services.AddHttpClient().AddStandardResilienceHandler();