如何在grpc中控制写缓冲区大小(或:如何在grpc中处理慢速流读取器)?

时间:2019-02-15 14:53:21

标签: c# grpc

我们已经在体系结构中广泛使用了双向grpc流,但是当一端的CPU负载不允许它读取数据的速度与另一端发送数据的速度一样时,就会遇到问题。我一直在努力理解grpc应该如何处理这种情况。我已经编写了一个测试程序,可以以一种简单的方式模拟这种情况(使用C#和我们专门使用的异步API)。服务器端无法跟上来自客户端的数据速率。结果是客户端的内存使用率不断攀升,直到最终对计算机进行OOM。我的理解是,grpc应该对此具有某种保护作用。

我发现名为GRPC_ARG_HTT2_WRITE_BUFFER_SIZE的通道参数认为这可能会限制客户端的内存使用(一旦缓冲区被填充,就会导致阻塞或客户端异常)。该测试程序正在发送10000字节消息,并将写缓冲区大小设置为11000,但似乎设置该参数无效。

我的问题是:在此示例中grpc是否行为正确,如果是,我在做什么错误?为什么GRPC_ARG_HTT2_WRITE_BUFFER_SIZE似乎没有效果(也许是预期的效果)?

使用以下消息/服务编写示例:

syntax = "proto3";
package test;
option csharp_namespace = "Test";

service TestService {
  rpc Publish(stream TestMsg) returns (stream TestMsg);
}

message TestMsg {
  string value = 1;
  bytes dummy = 2;
}

和C#程序在这里:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Core;

namespace Test
{
  class Program
  {
    static void Main( string[] args )
    {
      if ( args[0] == "client" )
      {
        using ( new Client() )
          Console.ReadKey();
      }
      else if ( args[0] == "server" )
      {
        using ( new Server() )
          Console.ReadKey();
      }
    }
  }

  public class Server : TestService.TestServiceBase, IDisposable
  {
    private readonly Grpc.Core.Server _server;

    public Server()
    {
      _server = new Grpc.Core.Server
      {
        Services = { TestService.BindService( this ) },
        Ports = { new ServerPort( "localhost", 1234, ServerCredentials.Insecure ) }
      };
      _server.Start();
      Console.WriteLine( "Server started" );
    }

    public void Dispose()
    {
      _server.ShutdownAsync();
    }

    public override async Task Publish(
      IAsyncStreamReader<TestMsg> requestStream,
      IServerStreamWriter<TestMsg> responseStream,
      ServerCallContext context )
    {
      try
      {
        Console.WriteLine( "Client connected" );

        for (; ; )
        {
          var requestTask = requestStream.MoveNext();
          await requestTask;

          if ( requestTask.Status == TaskStatus.Canceled )
            break;
          if ( !requestTask.Result )
            break;

          var request = requestStream.Current;
          if ( request != null )
          {
            try
            {
              Console.Write( request.Value + ".." );
              // We're really working hard.
              Thread.Sleep( 1000 );
            }
            catch ( Exception ex )
            {
              await responseStream.WriteAsync( new TestMsg { Value = ex.Message } );
              Console.WriteLine( ex );
            }
          }
        }
      }
      finally
      {
        Console.WriteLine( "Client disconnected" );
      }
    }
  }

  public class Client : IDisposable
  {
    private bool _isSending;
    private readonly TestService.TestServiceClient _client;
    private readonly Channel _channel;
    private readonly IClientStreamWriter<TestMsg> _requestStream;
    private readonly Timer _timer;
    private int _i;

    public Client()
    {
      Console.WriteLine( "Client started" );

      var options = new List<ChannelOption>();
      options.Add( new ChannelOption( "GRPC_ARG_HTTP2_WRITE_BUFFER_SIZE", 11000 ) );

      _channel = new Channel( "localhost:1234", ChannelCredentials.Insecure, options );
      _client = new TestService.TestServiceClient( _channel );
      var call = _client.Publish();
      _requestStream = call.RequestStream;

      _timer = new Timer( OnTimerTick, null, 0, 1000 / 25 );
    }

    public void Dispose()
    {
      _channel.ShutdownAsync();
      _timer.Dispose();
    }

    private async void OnTimerTick( object state )
    {
      // Skip timer ticks if the previous isn't done yet.
      if ( _isSending )
        return;

      try
      {

        _isSending = true;
        var bytes = new byte[10000];
        var msg = new TestMsg { Value = _i.ToString(), Dummy = ByteString.CopyFrom( bytes ) };

        Console.Write( _i + ".." );
        await _requestStream.WriteAsync( msg );
        ++_i;
      }
      catch ( Exception e )
      {
        Console.WriteLine( e );
      }
      finally
      {
        _isSending = false;
      }
    }
  }
}

注意:我正在使用grpc.core.1.18.0 nuget包进行测试。

0 个答案:

没有答案