Testes de retentativas e timeouts em aplicações .NET que utilizam Microsoft.Extensions.Http.Resilience

Tip

É novo na limitação? Aprende o que é o throttling e como lidar com isso.

De relance
Objetivo: Confirme que o seu handler de resiliência HTTP .NET tenta novamente, aplica um intervalo de espera entre tentativas e atinge o tempo limite conforme esperado
Tempo: 15 minutos
Plugins:GenericRandomErrorPlugin, RetryAfterPlugin, LatencyPlugin
Pré-requisitos:Configurar o Dev Proxy, uma aplicação .NET que utiliza Microsoft.Extensions.Http.Resilience

Acrescentaste AddStandardResilienceHandler() ao teu HttpClient. Como sabes que funciona? As APIs que chamas raramente falham a pedido, e testes unitários que simulam HttpMessageHandler saltam o pipeline de resiliência que queres testar.

O Dev Proxy situa-se entre a tua aplicação e a API. Ele devolve erros e respostas lentas à tua aplicação, por isso a aplicação continua a executar-se sem alterações e o handler de resiliência reage a respostas HTTP reais. Vês todas as tentativas no resultado do Dev Proxy.

O que faz o handler de resiliência padrão

Antes de testares, sabes o que esperar. Com as opções padrão, AddStandardResilienceHandler():

Comportamento Default
Retentativas ativadas HTTP 500 e superiores, 408, 429, HttpRequestException, e TimeoutRejectedException
Número de tentativas 3, com backoff exponencial e jitter, começando aos 2 segundos
Retry-After Cabeçalho Cumprido. O processador espera pelo tempo que a API pede.
Tempo limite para a tentativa 10 segundos por tentativa
Tempo total de espera 30 segundos para a solicitação, incluindo todas as tentativas

Para a lista completa de estratégias e os respetivos valores predefinidos, veja valores predefinidos do processador de resiliência padrão.

Encaminhe a sua aplicação através do Dev Proxy

O .NET usa o proxy do sistema, por isso, quando inicias o Dev Proxy, ele interceta os pedidos da tua app sem alterações de código. Para mais informações, consulte Usar Proxy de Desenvolvimento com aplicações .NET.

Simular erros transitórios

Cria uma configuração de Proxy de Desenvolvimento que faz falhar os pedidos à tua API com os erros que o handler repete. Este exemplo utiliza https://api.contoso.com. Substitua-a pelo URL da API que a sua aplicação invoca.

Ficheiro: 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

Adicione o RetryAfterPlugin antes do GenericRandomErrorPlugin no seu arquivo de configuração. Se adicionares depois, o GenericRandomErrorPlugin rejeita o pedido antes de o RetryAfterPlugin poder verificar.

No ficheiro de erros, defina uma resposta de limitação e dois erros de servidor. O valor @dynamic define o cabeçalho Retry-After e indica ao RetryAfterPlugin para verificar se a sua aplicação espera esse tempo antes de voltar a chamar a API.

Ficheiro: 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
        }
      ]
    }
  ]
}

Inicia o Dev Proxy e executa a tua aplicação.

devproxy --config-file devproxyrc.json

Com uma taxa de falha de 50%, a maioria dos pedidos é recuperada após uma ou duas tentativas. Na saída do Dev Proxy, verifique se:

  • Após uma resposta 429, a próxima tentativa para o mesmo URL ocorre decorrido o tempo Retry-After. O Dev Proxy usa 5 segundos por predefinição. Se a tua aplicação chamar a API demasiado cedo, o RetryAfterPlugin reporta isso e limita o pedido.
  • A tua aplicação não envia mais tentativas do que as que configuraste.
  • Pedidos que não queres retentar, como um POST que cria um registo, são enviados apenas uma vez. Para os excluir, ligue options.Retry.DisableForUnsafeHttpMethods() ou options.Retry.DisableFor(...).

Testa o que acontece quando as tentativas acabam

As retentativas ocultam falhas temporárias. Também precisas de saber o que a tua aplicação faz quando a API continua a falhar. Inicie o Proxy de Desenvolvimento com uma taxa de falha de 100%:

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

Dev Proxy mostra 4 tentativas para cada pedido: o pedido original e 3 novas tentativas. Depois da última tentativa, o processador padrão não gera. Ele devolve a última resposta de erro ao teu código. Vê o que a tua aplicação faz com isso. Por exemplo, EnsureSuccessStatusCode() lança um HttpRequestException, e GetStringAsync() também lança. Certifica-te de que a tua aplicação mostra uma mensagem útil ou recorre a uma alternativa, em vez de crashar ou mostrar um erro genérico.

Limites de tempo dos testes

APIs lentas ativam um caminho diferente no handler. Para testar, use o LatencyPlugin para atrasar respostas para além do timeout de tentativa de 10 segundos.

Ficheiro: 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
  }
}

Inicie o Dev Proxy e execute a sua app. Cada tentativa demora mais de 10 segundos, por isso o timeout da tentativa cancela-a e o manipulador tenta novamente. Após 30 segundos, o timeout total cancela o pedido e o seu código recebe um TimeoutRejectedException. Verifica se a tua aplicação lida com isso e informa o utilizador do que aconteceu.

Tip

Para testar os mesmos cenários com as suas próprias definições de resiliência, altere os valores na sua AddStandardResilienceHandler(options => ...) chamada e execute novamente as mesmas configurações de Dev Proxy.

Passo seguinte

Saiba mais sobre como simular throttling em qualquer API.

Ver também