GRPC和Polly- .NET Core 6

发布于 2025-01-26 09:17:26 字数 3624 浏览 3 评论 0原文

我正在尝试将Polly用作我的.NET Core 6项目中GRPC的重试策略处理程序。我注意到retryfunc永远不会被调用。我从这个项目开始 grpc & ASP.NET Core 3.1:具有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);
        }
    }
}

,但最终WaitandRetryAsync从未被调用。

我在

我的测试很简单。我在没有聆听后端的情况下启动客户端,期望从console.writeline读取输出的3倍($“请求”在控制台上失败了{grpcstatus}。retry”); 。但是政策处理程序从未解雇。我有以下例外,而

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.

没有任何重试。

I'm trying to use Polly as retry policy handler for grpc in my .net core 6 project. I noticed that the retryFunc is never invoked. I started from this project 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(
quot;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(
quot;Found country {x.CountryName} ({x.CountryId}) {x.Description}"));

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

but at the end WaitAndRetryAsync is never called.

I created a small project available on github in order to reproduce it.

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"); 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我们只是彼此的过ke 2025-02-02 09:17:26

这对您不起作用,因为重试现在已内置在GRPC中。为了进行此工作,请按以下方式注册您的服务:

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}};
      });
  });

将重试策略添加到您的客户。您可能会遇到的另一件事。当时我没有意识到这一点,但是在我的服务实施中,我设置了这样的错误:

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

如果您这样实施您的服务,重试逻辑将不会启动,那么您实际上必须做更多这样的事情:

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

您在注册客户时配置的重试逻辑将工作。希望有帮助。

This is not working for you because Retry is now built into 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.

清醇 2025-02-02 09:17:26

在@petercsala的帮助下,我尝试了一些修复程序。

作为第一次尝试,我在没有依赖性注射的情况下尝试了,以下方式注册了策略

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()));

然后,我回到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()));

,但仍然无法正常工作。

最后,AddPolicyHandler似乎不适合GRPC客户?

With the help of @PeterCsala I tried some fix.

As a first attempt I tried without DependencyInjection, registering the policy as follows

var policy = Policy
    .Handle<Exception>()
    .RetryAsync(3, (exception, count) =>
    {
        Console.WriteLine(
quot;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

IAsyncPolicy<HttpResponseMessage> policy = 
Policy<HttpResponseMessage>.Handle<Exception>().RetryAsync(3, (exception, count) =>
{
    Console.WriteLine(
quot;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?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文