练习 - 实现基于代码的复原

已完成

在此练习中,你将使用 Polly 实现复原处理程序。 初始的 eShopOnContainers 部署包含从结帐购物车验证优惠券时的故障模拟功能。 借助此功能,你可配置特定折扣代码请求失败的次数。

在本单元中,你将:

  • 使用 Polly 更新应用的代码以实现故障处理。
  • 创建一个 ACR 实例,并将已更新的应用部署到 AKS。
  • 探索实现复原后系统出现故障时的响应。

备注

如果 Cloud Shell 会话由于不活动而断开连接,请重新连接并运行以下命令以返回到此目录并打开 Cloud Shell 编辑器:

cd ~/clouddrive/aspnet-learn/src/ && \
  code .

使用 Polly 添加故障处理代码

在本部分中,你将修改应用,使其自动重试失败的操作,直到成功为止。 如果多次尝试后操作仍然失败,UI 将显示异常。

验证折扣优惠券时,会将 HTTP 请求发送到 Web 购物聚合器。 Web 购物聚合器负责将请求路由到优惠券服务。 这是服务前端的后端模式 (BFF) 的实现。 BFF 实现:

  • 再向优惠券服务发送一个 HTTP 请求来获取所需的信息。
  • 使用 IHttpClientFactoryPolly 处理复原。

若要使优惠券服务具有复原能力,你需要实现重试和断路器策略以处理 Web 购物聚合器中的故障。 将 Polly 与 IHttpClientFactory 结合使用来向 Web 应用添加复原能力是一种典型的故障处理解决方案。 IHttpClientFactory 负责创建 HttpClient 的实例。

下面的序列图显示了从 HttpClient 实例到 Polly 的重试和断路器策略的事件流:

通过多个 PolicyHttpMessageHandler 调用 HttpClient

完成以下步骤,实现如上所述的优惠券服务的故障处理:

  1. 运行以下命令,将当前位置设置为 HTTP 聚合器项目目录:

    pushd src/ApiGateways/Aggregators/Web.Shopping.HttpAggregator/
    

    你的当前位置是 ~/clouddrive/aspnet-learn/src/src/ApiGateways/Aggregators/Web.Shopping.HttpAggregator。

  2. 运行以下命令:

    dotnet add package Microsoft.Extensions.Http.Polly --version 3.1.6
    

    上述命令在 Web.Shopping.HttpAggregator 项目中安装 NuGet 包。 此包将 IHttpClientFactory 与 Polly 集成,并将实际的 Polly 包作为依赖项安装。 若要配置 Polly 策略来处理发出 HTTP 请求时出现的暂时性故障,则必须使用此包。 通过调用包的 HttpPolicyExtensions.HandleTransientHttpError 方法来处理此类情况。 这些情况包括:

    • 网络故障,由 HttpRequestException 类型的异常指示
    • 服务器错误,由 HTTP 5xx 状态代码指示
    • 请求超时,由 HTTP 408 状态代码指示
  3. 在 src/ApiGateways/Aggregators/Web.Shopping.HttpAggregator/Extensions/ServiceCollectionExtensions.cs 文件中应用以下更改:

    1. 使用以下方法替换 // Add the GetRetryPolicy method 注释:

      public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
      {
          return HttpPolicyExtensions.HandleTransientHttpError()
              .WaitAndRetryAsync(5,
                  retryAttempt => TimeSpan.FromMilliseconds(Math.Pow(1.5, retryAttempt) * 1000),
                  (_, waitingTime) =>
                  {
                      Log.Logger.Information(
                          "----- Retrying in {WaitingTime}s", $"{ waitingTime.TotalSeconds:n1}");
                  });
      }
      
    2. 使用以下方法替换 // Add the GetCircuitBreakerPolicy method 注释:

      public static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy() =>
          HttpPolicyExtensions.HandleTransientHttpError()
              .CircuitBreakerAsync(15, TimeSpan.FromSeconds(15));
      
    3. AddApplicationServices 方法中,调用 AddPolicyHandler 扩展方法两次。 将方法调用链接到优惠券服务的 AddHttpMessageHandler 方法调用:

      public static IServiceCollection AddApplicationServices(this IServiceCollection services)
      {
          //register delegating handlers
          services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
          services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
      
          //register HTTP services
          services.AddHttpClient<ICouponService, CouponService>()
              .AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
              .AddPolicyHandler(GetRetryPolicy())
              .AddPolicyHandler(GetCircuitBreakerPolicy());
      
          //code omitted for brevity
      
          return services;
      }
      
    4. 使用以下 using 指令替换 // Add the using statements 注释:

      using Polly;
      using Polly.Extensions.Http;
      using System.Net.Http;
      

      导入前面的命名空间可解析 GetRetryPolicyGetCircuitBreakerPolicy 方法中的成员引用。

  4. 保存 ServiceCollectionExtensions.cs 文件。

  5. 运行以下命令以生成应用:

    dotnet build --no-restore
    

    包含 --no-restore 选项,因为自上次构建以来未添加任何 NuGet 包。 生成过程会跳过 NuGet 包的恢复过程,并且在成功后不会发出任何警告。 如果生成失败,请检查输出以获取故障排除信息。

  6. 运行以下命令返回到先前位置:

    popd
    

通过前面的更改:

  • 定义了重试策略,最多可重试 5 次,每次尝试之间的延迟以指数增加。 此策略的前提是故障是暂时性的,并且可能在短时间延迟后自动纠正。 策略延迟:

    • 每次尝试后以 1.5 秒的幂增加。
    • 默认情况下,为 2 秒的幂。 若要缩短此练习的等待时间,请改用 1.5 秒。
  • 定义了断路器策略,在 15 次连续失败之后强制暂停 15 秒。 此策略的前提是,保护服务免受重载的影响可帮助其恢复。

  • 优惠券服务使用的 HttpClient 实例配置为应用重试和断路器策略。 这个特定的 HttpClient 实例通过构造函数注入提供给 CouponService 类:

    public class CouponService : ICouponService
    {
        public readonly HttpClient _httpClient;
        private readonly UrlsConfig _urls;
        private readonly ILogger<CouponService> _logger;
    
        public CouponService(
            HttpClient httpClient,
            IOptions<UrlsConfig> config,
            ILogger<CouponService> logger)
        {
            _urls = config.Value;
            _httpClient = httpClient;
            _logger = logger;
        }
    
        // code omitted for brevity
    

    从项目的 Startup.cs 文件中的 ConfigureServices 方法调用 AddApplicationServices 扩展方法:

    public void ConfigureServices(IServiceCollection services)
    {
        // code omitted for brevity
    
        services.AddCustomMvc(Configuration)
            .AddCustomAuthentication(Configuration)
            .AddApplicationServices();
    }
    

部署已更新的微服务

完成以下步骤来部署已实现的更改:

  1. 运行以下脚本,将聚合器已更新的 Docker 映像发布到 ACR:

    ./deploy/k8s/build-to-acr.sh --services webshoppingagg
    

    上述脚本会生成已更新的映像并将其发布到 ACR 实例。 ACR 快速任务用于生成 webshoppingagg 映像并将其发布到 ACR 实例。 你将看到以下输出的变体:

    Building images to ACR
    ======================
    ~/clouddrive/aspnet-learn/src/deploy/k8s ~/clouddrive/aspnet-learn/src
    
    Building and publishing docker images to eshoplearn20200729161705092.azurecr.io
    ~/clouddrive/aspnet-learn/src ~/clouddrive/aspnet-learn/src/deploy/k8s ~/clouddrive/aspnet-learn/src
    
    Building image "webshoppingagg" for service "webshoppingagg" with "src/ApiGateways/Aggregators/Web.Shopping.HttpAggregator/Dockerfile.acr"...
    
     > az acr build -r eshoplearn20200729161705092 -t eshoplearn20200729161705092.azurecr.io/webshoppingagg:linux-latest -f src/ApiGateways/Aggregators/Web.Shopping.HttpAggregator/Dockerfile.acr .
    
    Packing source code into tar to upload...
    Excluding '.gitignore' based on default ignore rules
    Uploading archived source code from '/tmp/build_archive_1a826ecd8db64f8c846d796af13d6318.tar.gz'...
    Sending context (7.838 MiB) to registry: eshoplearn20200729161705092...
    Queued a build with ID: cf2
    Waiting for an agent...
    2020/07/29 17:03:19 Downloading source code...
    2020/07/29 17:03:21 Finished downloading source code
    2020/07/29 17:03:22 Using acb_vol_faae1c90-bbea-4ea6-89e9-daa0ab059f5a as the home volume
    2020/07/29 17:03:22 Setting up Docker configuration...
    2020/07/29 17:03:23 Successfully set up Docker configuration
    2020/07/29 17:03:23 Logging in to registry: eshoplearn20200729161705092.azurecr.io
    2020/07/29 17:03:24 Successfully logged into eshoplearn20200729161705092.azurecr.io
    2020/07/29 17:03:24 Executing step ID: build. Timeout(sec): 28800, Working directory: '', Network: ''
    2020/07/29 17:03:24 Scanning for dependencies...
    2020/07/29 17:03:25 Successfully scanned dependencies
    2020/07/29 17:03:25 Launching container with name: build
    

    将映像发布到 ACR 后,将会出现以下特定行:

    2020/07/29 17:04:57 Successfully pushed image: eshoplearn20200729161705092.azurecr.io/webshoppingagg:linux-latest
    
  2. 运行以下命令,验证 ACR 实例的 URL:

    eval $(cat ~/clouddrive/aspnet-learn/create-acr-exports.txt) && \
        echo $ESHOP_REGISTRY
    

    安装脚本在文本文件中保存了一些环境变量声明。 上述命令会计算文本文件以设置环境变量。 你将看到以下输出的变体:

    eshoplearn2020072900000000.azurecr.io
    
  3. 运行以下脚本,将 ACR 中更新的映像部署到 AKS:

    ./deploy/k8s/deploy-application.sh --registry $ESHOP_REGISTRY --charts webshoppingagg
    

    上述脚本将卸载旧的 webshoppingagg Helm 图表,然后再次安装它。 AKS 群集使用 ACR 实例中的新映像。 你将看到以下输出的变体:

    ~/clouddrive/aspnet-learn ~/clouddrive/aspnet-learn/src/deploy/k8s
    ~/clouddrive/aspnet-learn/src/deploy/k8s
    
    Uninstalling chart webshoppingagg...
    release "eshoplearn-webshoppingagg" uninstalled
    
    Deploying Helm charts from registry "eshoplearn20200731194920286.azurecr.io" to "http://13.87.153.177"...
    ---------------------
    
    Installing chart "webshoppingagg"...
    NAME: eshoplearn-webshoppingagg
    LAST DEPLOYED: Fri Jul 31 20:38:05 2020
    NAMESPACE: default
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    
    Helm charts deployed!
    

再次测试应用

已部署 Polly 重试和断路器策略。 现在可测试应用的行为。

验证服务的可用性

  1. 执行以下命令:

    cat ~/clouddrive/aspnet-learn/deployment-urls.txt
    
  2. 选择命令行界面中的 General application status 链接,查看 WebStatus 运行状况检查仪表板。

  3. 如果所有服务都正常运行,请继续阅读下一部分。

重试策略

完成以下步骤来测试重试策略:

  1. 将一件商品放入购物袋,开始结帐过程。

  2. 输入折扣代码 FAIL 2 DISC-10 并选择 APPLY。

    你将收到以下确认消息,其中包含为代码配置的故障数:CONFIG: 2 failure(s) configured for code "DISC-10"!!。

  3. 将现有折扣代码替换为 DISC-10,然后选择 APPLY。

    在短暂等待后,第一次尝试操作似乎成功了。 可复原的 BFF 将以透明方式从用户的角度处理重试。 请注意,已应用 10 美元折扣。

  4. 运行以下命令,查看日志记录页 URL。 选择 Centralized logging 链接。

    cat ../deployment-urls.txt
    
  5. 检查日志跟踪。 你将看到以下输出的变体:

    日志跟踪

    在上图中,可以看到:

    • 配置模拟故障时的日志跟踪(标记为 1)。
    • 重试三次,直到聚合器最后获得值(标记为 2)。
  6. 完成结帐过程,选择 CONTINUE SHOPPING。

断路器策略

若要测试断路器策略,需要将代码配置为故障 20 次。 相应地,你将使用折扣代码 FAIL 20 DISC-10:

配置严重故障

  1. 将一件商品放入购物袋,开始结帐过程。

  2. 输入折扣代码 FAIL 20 DISC-10 并选择 APPLY。

    你将收到以下确认消息,其中包含为代码配置的故障数:CONFIG: 20 failure(s) configured for code "DISC-10"!!。

  3. 再次输入折扣代码 DISC-10 并选择 APPLY

  4. 等待大约 20 秒。 你将收到 HTTP 500 错误消息。

  5. 再次选择 APPLY。 大约 20 秒后再次收到错误消息。

  6. 再次选择 APPLY。 由于采用断路器策略,HTTP 500 错误消息出现的速度要快得多。

  7. 再次选择 APPLY。

    立即收到错误消息。 在日志跟踪中可以清楚地看到此错误:

    日志跟踪中的严重故障

    在上图中,请注意:

    • 在等待7.6 秒后(标记为 1),你收到了带有重试策略的 HTTP 500 错误消息(标记为 2)。

    • 下一次尝试时,验证该代码。 仅等待 3.4 秒后(标记为 3),收到 HTTP 500 错误消息。 你看不到 Get coupon... 跟踪,这意味着该跟踪失败了,没有转到服务器。

    • 如果检查最后这个跟踪的详细信息,你应会看到以下输出的变体:

      严重故障日志详细信息

      请注意,最后一个跟踪具有 The circuit is now open... 消息。

在此单元中,你使用 Polly 添加了基于代码的复原。 接下来,你将使用 Linkerd 实现基于基础结构的复原。