简体   繁体   English

grpc 和 polly - .net 核心 6

[英]grpc and polly - .net core 6

I'm trying to use Polly as retry policy handler for grpc in my .net core 6 project.我正在尝试在我的 .net core 6 项目中使用 Polly 作为 grpc 的重试策略处理程序。 I noticed that the retryFunc is never invoked.我注意到retryFunc从未被调用过。 I started from this project gRPC & ASP.NET Core 3.1: Resiliency with Polly我从这个项目开始gRPC & ASP.NET Core 3.1: Resiliency with Polly

class Program
{
    static async Task Main(string[] args)
    {
        // DI
        var services = new ServiceCollection();

        var loggerFactory = LoggerFactory.Create(logging =>
        {
            logging.AddConsole();
            logging.SetMinimumLevel(LogLevel.Debug);
        });

        var serverErrors = new HttpStatusCode[] { 
            HttpStatusCode.BadGateway, 
            HttpStatusCode.GatewayTimeout, 
            HttpStatusCode.ServiceUnavailable, 
            HttpStatusCode.InternalServerError, 
            HttpStatusCode.TooManyRequests, 
            HttpStatusCode.RequestTimeout 
        };

        var gRpcErrors = new StatusCode[] {
            StatusCode.DeadlineExceeded,
            StatusCode.Internal,
            StatusCode.NotFound,
            StatusCode.ResourceExhausted,
            StatusCode.Unavailable,
            StatusCode.Unknown
        };

        Func<HttpRequestMessage, IAsyncPolicy<HttpResponseMessage>> retryFunc = (request) =>
        {
            return Policy.HandleResult<HttpResponseMessage>(r => {
                
                var grpcStatus = StatusManager.GetStatusCode(r);
                var httpStatusCode = r.StatusCode;

                return (grpcStatus == null && serverErrors.Contains(httpStatusCode)) || // if the server send an error before gRPC pipeline
                       (httpStatusCode == HttpStatusCode.OK && gRpcErrors.Contains(grpcStatus.Value)); // if gRPC pipeline handled the request (gRPC always answers OK)
            })
            .WaitAndRetryAsync(3, (input) => TimeSpan.FromSeconds(3 + input), (result, timeSpan, retryCount, context) =>
                                {
                                    var grpcStatus = StatusManager.GetStatusCode(result.Result);
                                    Console.WriteLine($"Request failed with {grpcStatus}. Retry");
                                });
        };

        services.AddGrpcClient<CountryServiceClient>(o =>
        {
            o.Address = new Uri("https://localhost:5001");
        }).AddPolicyHandler(retryFunc);

        var provider = services.BuildServiceProvider();
        var client = provider.GetRequiredService<CountryServiceClient>();

        try
        {
            var countries = (await client.GetAllAsync(new EmptyRequest())).Countries.Select(x => new Country
            {
                CountryId = x.Id,
                Description = x.Description,
                CountryName = x.Name
            }).ToList();

            Console.WriteLine("Found countries");
            countries.ForEach(x => Console.WriteLine($"Found country {x.CountryName} ({x.CountryId}) {x.Description}"));

        }
        catch (RpcException e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

but at the end WaitAndRetryAsync is never called.但最后WaitAndRetryAsync永远不会被调用。

I created a small project available on github in order to reproduce it.为了重现它,我在github上创建了一个可用的小项目。

My test is fairly simple.我的测试相当简单。 I start the client without a listening back-end, expecting to read 3 times the output from Console.WriteLine($"Request failed with {grpcStatus}. Retry");我在没有监听后端的情况下启动客户端,期望读取Console.WriteLine($"Request failed with {grpcStatus}. Retry");输出的 3 倍。 on the console.在控制台上。 But the policy handler in never fired.但是政策处理程序从未被解雇。 I have the following exception instead我有以下异常

Status(StatusCode="Unavailable", Detail="Error connecting to
subchannel.", DebugException="System.Net.Sockets.SocketException
(10061): No connection could be made because the target machine
actively refused it.

without any retry.没有任何重试。

With the help of @PeterCsala I tried some fix.在@PeterCsala 的帮助下,我尝试了一些修复。

As a first attempt I tried without DependencyInjection, registering the policy as follows作为第一次尝试,我尝试不使用 DependencyInjection,注册策略如下

var policy = Policy
    .Handle<Exception>()
    .RetryAsync(3, (exception, count) =>
    {
        Console.WriteLine($"Request {count}, {exception.Message}. Retry");
    });

var channel = GrpcChannel.ForAddress("https://localhost:5001");
TestServiceClient client = new TestServiceClient(channel);

await policy.ExecuteAsync(async () => await client.TestAsync(new Empty()));

This way it's working.这样它就可以工作了。

Then I came back to DI and used to register the policy as follows然后我回到DI,用来注册策略如下

IAsyncPolicy<HttpResponseMessage> policy = 
Policy<HttpResponseMessage>.Handle<Exception>().RetryAsync(3, (exception, count) =>
{
    Console.WriteLine($"Request {count}, {exception.Exception.Message}. Retry");
});

var services = new ServiceCollection();
services.AddGrpcClient<TestServiceClient>(o => { 
    o.Address = new Uri("https://localhost:5001");
}).AddPolicyHandler(policy);

var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<TestServiceClient>();
    
var testClient = (await client.TestAsync(new Empty()));

And still not working.仍然无法正常工作。

At the end it seems AddPolicyHandler is not suitable for grpc clients?最后似乎 AddPolicyHandler 不适合 grpc 客户端?

This is not working for you because Retry is now built into Grpc.这对您不起作用,因为 Retry 现在内置于 Grpc 中。 In order to make this work, register your service as follows:为了完成这项工作,请按如下方式注册您的服务:

var defaultMethodConfig = new MethodConfig
{
   Names = { MethodName.Default },
   RetryPolicy = new RetryPolicy
   {
       MaxAttempts = 3,
       InitialBackoff = TimeSpan.FromSeconds(3),
       MaxBackoff = TimeSpan.FromSeconds(3),
       BackoffMultiplier = 1,
       RetryableStatusCodes =
       {
           // Whatever status codes you want to look for
           StatusCode.Unauthenticated, StatusCode.NotFound, StatusCode.Unavailable,
       }
   }
  };
  var services = new ServiceCollection();
  services.AddGrpcClient<TestServiceClient>(o => { 
      o.Address = new Uri("https://localhost:5001");
      o.ChannelOptionsActions.Add(options =>
      {
          options.ServiceConfig = new ServiceConfig {MethodConfigs = {defaultMethodConfig}};
      });
  });

That will add the retry policy to your client.这会将重试策略添加到您的客户端。 One other thing that you might run into.您可能会遇到的另一件事。 I didn't realize this at the time, but in my service implementation, I was setting up errors something like this:当时我没有意识到这一点,但在我的服务实现中,我设置了如下错误:

var response = new MyServiceResponse()
// something bad happens
context.Status = new Status(StatusCode.Internal, "Something went wrong");
return response;

The retry logic will not kick in if you implement your service like that, you actually have to do something more like this:如果你像这样实现你的服务,重试逻辑将不会启动,你实际上必须做更多这样的事情:

// something bad happens
throw new RpcException(new Status(StatusCode.Internal, "Something went wrong"));

The retry logic you configured when registering your client will then work.然后,您在注册客户端时配置的重试逻辑将起作用。 Hope that helps.希望有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM