我们已经在体系结构中广泛使用了双向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包进行测试。