GRPC:在Java / Scala中成为高吞吐量客户端

时间:2019-11-08 10:35:32

标签: java scala grpc

我有一项服务,可以以很高的速率传输邮件。

当前,它由akka-tcp服务,每分钟发送350万条消息。我决定尝试一下grpc。 不幸的是,它导致吞吐量大大降低:每分钟约50万条消息,甚至更低。

请介绍如何对其进行优化?

我的设置

硬件:32核,24Gb堆。

grpc版本:1.25.0

消息格式和终结点

消息基本上是一个二进制blob。 客户端将100K-1M和更多消息流传输到同一请求中(异步),服务器不响应任何内容,客户端使用无操作观察者

service MyService {
    rpc send (stream MyMessage) returns (stream DummyResponse);
}

message MyMessage {
    int64 someField = 1;
    bytes payload = 2;  //not huge
}

message DummyResponse {
}

问题: 与akka实施相比,邮件速率较低。 我观察到CPU使用率较低,因此我怀疑grpc调用实际上在内部阻塞,尽管它另有说明。确实不会立即返回调用onNext(),但表上还存在GC。

我试图产生更多的发件人来缓解此问题,但并没有太大的改进。

我的发现 Grpc序列化时实际上为每个消息分配8KB字节的缓冲区。查看堆栈跟踪:

  

java.lang.Thread.State:已阻止(在对象监视器上)           在com.google.common.io.ByteStreams.createBuffer(ByteStreams.java:58)           在com.google.common.io.ByteStreams.copy(ByteStreams.java:105)           在io.grpc.internal.MessageFramer.writeToOutputStream(MessageFramer.java:274)           在io.grpc.internal.MessageFramer.writeKnownLengthUncompressed(MessageFramer.java:230)           在io.grpc.internal.MessageFramer.writeUncompressed(MessageFramer.java:168)           在io.grpc.internal.MessageFramer.writePayload(MessageFramer.java:141)           在io.grpc.internal.AbstractStream.writeMessage(AbstractStream.java:53)           在io.grpc.internal.ForwardingClientStream.writeMessage(ForwardingClientStream.java:37)           在io.grpc.internal.DelayedStream.writeMessage(DelayedStream.java:252)           在io.grpc.internal.ClientCallImpl.sendMessageInternal(ClientCallImpl.java:473)           在io.grpc.internal.ClientCallImpl.sendMessage(ClientCallImpl.java:457)           在io.grpc.ForwardingClientCall.sendMessage(ForwardingClientCall.java:37)           在io.grpc.ForwardingClientCall.sendMessage(ForwardingClientCall.java:37)           在io.grpc.stub.ClientCalls $ CallToStreamObserverAdapter.onNext(ClientCalls.java:346)

在构建高吞吐量grpc客户方面的最佳实践方面的任何帮助都受到赞赏。

3 个答案:

答案 0 :(得分:4)

我通过在每个目的地创建多个ManagedChannel实例解决了该问题。尽管有文章说ManagedChannel本身可以产生足够的连接,所以一个实例就足够了,在我看来,这不是真的。

性能与akka-tcp实现相当。

答案 1 :(得分:0)

有趣的问题。计算机网络软件包使用stack of protocols进行编码,并且这些协议是在前一个协议的规范之上构建的。因此,由于要在基础协议之上添加额外的编码/解码步骤,因此协议的性能(吞吐量)受用于构建协议的性能的限制。

例如,gRPC构建在HTTP 1.1/2之上,而L7 Application层HTTP上的协议,因此其性能为受HTTP的性能约束。现在TCP本身是建立在L4之上的,而gRPC位于传输层TCP,因此我们可以推断出TCP的吞吐量< strong>不能大于gRPC层中提供的等效代码。

换句话说:如果您的服务器能够处理原始DbContext包,那么增加新的复杂性层(DbContext)会如何提高性能?

答案 2 :(得分:0)

我对Akka TCP在这里的出色表现印象深刻:D

我们的经验略有不同。我们正在使用Akka Cluster处理更小的实例。对于Akka远程处理,我们使用Artery从Akka TCP更改为UDP,并实现了更高的速率+更低的响应时间和更稳定的响应时间。 Artery中甚至有一个配置,有助于在CPU消耗和冷启动响应时间之间取得平衡。

我的建议是使用一些基于UDP的框架,该框架也为您提供传输可靠性(例如,Artery UDP),并且仅使用Protobuf进行序列化,而不是使用完整的gRPC。 HTTP / 2传输通道并不是真正用于高吞吐量,低响应时间的目的。