Eu tenho um serviço que transfere mensagens a uma taxa bastante alta.
Atualmente, é atendido pelo akka-tcp e gera 3,5 milhões de mensagens por minuto. Decidi experimentar o grpc. Infelizmente, resultou em uma taxa de transferência muito menor: ~ 500 mil mensagens por minuto e até menos.
Você poderia recomendar como otimizá-lo?
Minha configuração
Hardware : 32 núcleos, pilha de 24 GB.
versão grpc: 1.25.0
Formato da mensagem e ponto final
A mensagem é basicamente um blob binário. O cliente transmite de 100K a 1M e mais mensagens na mesma solicitação (de forma assíncrona), o servidor não responde com nada, o cliente usa um observador não operacional
service MyService {
rpc send (stream MyMessage) returns (stream DummyResponse);
}
message MyMessage {
int64 someField = 1;
bytes payload = 2; //not huge
}
message DummyResponse {
}
Problemas: a taxa de mensagens é baixa em comparação com a implementação do akka. Observo baixo uso da CPU e, portanto, suspeito que a chamada grpc esteja realmente bloqueando internamente, apesar de dizer o contrário. Ligar de onNext()
fato não retorna imediatamente, mas também há GC na mesa.
Tentei gerar mais remetentes para mitigar esse problema, mas não obtive muitas melhorias.
Minhas descobertas O Grpc realmente aloca um buffer de 8 KB em cada mensagem quando o serializa. Veja o stacktrace:
java.lang.Thread.State: BLOCKED (no monitor de objeto) em com.google.common.io.ByteStreams.createBuffer (ByteStreams.java:58) em com.google.common.io.ByteStreams.copy (ByteStreams.java: 105) em io.grpc.internal.MessageFramer.writeToOutputStream (MessageFramer.java:274) em io.grpc.internal.MessageFramer.writeKnownLengthUncompressed (MessageFramer.java:230) em io.grpc.internal.MessageFramer.writeUncom : 168) em io.grpc.internal.MessageFramer.writePayload (MessageFramer.java:141) em io.grpc.internal.AbstractStream.writeMessage (AbstractStream.java:53) em io.grpc.internal.ForwardingClientStream.writeMessage (ForwardingClientStream. java: 37) em io.grpc.internal.DelayedStream.writeMessage (DelayedStream.java:252) em io.grpc.internal.ClientCallImpl.sendMessageInternal (ClientCallImpl.java:473) em io.grpc.internal.ClientCallImpl.sendMessage (ClientCallImpl.java:457) em io.grpc.ForwardingClientCall.sendMessage (ForwardingClientCall.java:c.): (ForwardingClientCall.java:37) em io.grpc.stub.ClientCalls $ CallToStreamObserverAdapter.onNext (ClientCalls.java:346)
Qualquer ajuda com as melhores práticas na criação de clientes grpc de alto rendimento é apreciada.
scalapb
. Provavelmente, esse rastreamento de pilha foi realmente do código gerado pelo scalapb. Eu removi tudo relacionado ao scalapb, mas não ajudou muito no desempenho.Respostas:
Resolvi o problema criando várias
ManagedChannel
instâncias por destino. Apesar dos artigos dizerem que umManagedChannel
pode gerar conexões suficientes por si só, uma instância é suficiente, não era verdade no meu caso.O desempenho está em paridade com a implementação do akka-tcp.
fonte
Pergunta interessante. Os pacotes de rede de computadores são codificados usando uma pilha de protocolos , e esses protocolos são criados com base nas especificações do anterior. Portanto, o desempenho (taxa de transferência) de um protocolo é limitado pelo desempenho do usado para construí-lo, uma vez que você está adicionando etapas extras de codificação / decodificação sobre o subjacente.
Por exemplo,
gRPC
é construído em cima deHTTP 1.1/2
, que é um protocolo na camada Aplicativo ouL7
, e, como tal, seu desempenho é limitado pelo desempenho deHTTP
. Agora,HTTP
ele próprio é construído em cima deTCP
, que está na camada Transporte , ouL4
, portanto, podemos deduzir que agRPC
taxa de transferência não pode ser maior que um código equivalente exibido noTCP
camada.Em outras palavras: se o servidor é capaz de lidar com
TCP
pacotes brutos , como adicionar novas camadas de complexidade (gRPC
) melhoraria o desempenho?fonte
gRPC
você também paga uma vez para estabelecer uma conexão, mas você adicionou a carga extra de análise protobuf. De qualquer forma, é difícil fazer suposições sem muita informação, mas eu apostaria que, em geral, como você está adicionando etapas extras de codificação / decodificação ao seu pipeline, agRPC
implementação seria mais lenta que a do soquete da Web equivalente.Estou bastante impressionado com o desempenho do Akka TCP aqui: D
Nossa experiência foi um pouco diferente. Estávamos trabalhando em instâncias muito menores usando o Akka Cluster. Para o Akka remoting, mudamos de Akka TCP para UDP usando o Artéria e alcançamos uma taxa muito mais alta + tempo de resposta mais baixo e mais estável. Existe até uma configuração no Artéria que ajuda a equilibrar entre o consumo da CPU e o tempo de resposta desde o início a frio.
Minha sugestão é usar alguma estrutura baseada em UDP que também cuide da confiabilidade da transmissão para você (por exemplo, a UDP da artéria) e apenas serialize usando o Protobuf, em vez de usar o gRPC completo. O canal de transmissão HTTP / 2 não é realmente para fins de alto rendimento e baixo tempo de resposta.
fonte