Tengo un servicio que transfiere mensajes a una velocidad bastante alta.
Actualmente es atendido por akka-tcp y genera 3,5 millones de mensajes por minuto. Decidí probar grpc. Desafortunadamente, resultó en un rendimiento mucho menor: ~ 500k mensajes por minuto e incluso menos.
¿Podría recomendarme cómo optimizarlo?
Mi configuración
Hardware : 32 núcleos, montón de 24 Gb.
versión de grpc: 1.25.0
Formato del mensaje y punto final
El mensaje es básicamente un blob binario. El cliente transmite 100K - 1M y más mensajes en la misma solicitud (asíncronamente), el servidor no responde con nada, el cliente usa un observador no operativo
service MyService {
rpc send (stream MyMessage) returns (stream DummyResponse);
}
message MyMessage {
int64 someField = 1;
bytes payload = 2; //not huge
}
message DummyResponse {
}
Problemas: la tasa de mensajes es baja en comparación con la implementación de akka. Observo un bajo uso de la CPU, por lo que sospecho que la llamada grpc en realidad está bloqueando internamente a pesar de que dice lo contrario. Las llamadas onNext()
no vuelven de inmediato, pero también hay GC sobre la mesa.
Traté de generar más remitentes para mitigar este problema, pero no obtuve muchas mejoras.
Mis hallazgos Grpc en realidad asigna un búfer de bytes de 8 KB en cada mensaje cuando lo serializa. Ver el stacktrace:
java.lang.Thread.State: BLOCKED (en el monitor de objetos) en com.google.common.io.ByteStreams.createBuffer (ByteStreams.java:58) en com.google.common.io.ByteStreams.copy (ByteStreams.java: 105) en io.grpc.internal.MessageFramer.writeToOutputStream (MessageFramer.java:274) en io.grpc.internal.MessageFramer.writeKnownLengthUncompressed (MessageFramer.java:230) en io.grpc.internal.MessageFramer.wpressed.MessageFramer.vader. : 168) en io.grpc.internal.MessageFramer.writePayload (MessageFramer.java:141) en io.grpc.internal.AbstractStream.writeMessage (AbstractStream.java:53) en io.grpc.internal.ForwardingClientStream.writeMessageS (ForwardingClientStream.writeMessageS. java: 37) en io.grpc.internal.DelayedStream.writeMessage (DelayedStream.java:252) en io.grpc.internal.ClientCallImpl.sendMessageInternal (ClientCallImpl.java:473) en io.grpc.internal.ClientCallImpl.sendMessage (ClientCallImpl.java:457) en io.grpc.ForwardingClientCall.sendMessage (ForwardingClientCall.javagr37) en io.gr. (ForwardingClientCall.java:37) en io.grpc.stub.ClientCalls $ CallToStreamObserverAdapter.onNext (ClientCalls.java:346)
Se agradece cualquier ayuda con las mejores prácticas en la construcción de clientes grpc de alto rendimiento.
scalapb
. Probablemente este stacktrace fue de hecho al código generado por scalapb. Eliminé todo lo relacionado con scalapb pero no ayudó mucho el rendimiento de wrt.Respuestas:
Resolví el problema creando varias
ManagedChannel
instancias por destino. A pesar de que los artículos dicen que unManagedChannel
puede generar suficientes conexiones en sí mismo, por lo que una instancia es suficiente, no fue cierto en mi caso.El rendimiento está a la par con la implementación de akka-tcp.
fuente
Interesante pregunta. Los paquetes de red de computadoras se codifican usando una pila de protocolos , y dichos protocolos se construyen sobre las especificaciones del anterior. Por lo tanto, el rendimiento (rendimiento) de un protocolo está limitado por el rendimiento del utilizado para construirlo, ya que está agregando pasos adicionales de codificación / decodificación sobre el subyacente.
Por ejemplo,
gRPC
está construido encima deHTTP 1.1/2
, que es un protocolo en la capa de aplicación , oL7
, y como tal, su rendimiento está limitado por el rendimiento deHTTP
. AhoraHTTP
se construye encima deTCP
, que está en la capa de transporte , oL4
, por lo que podemos deducir que elgRPC
rendimiento no puede ser mayor que un código equivalente servido en laTCP
capa.En otras palabras: si su servidor es capaz de manejar
TCP
paquetes en bruto , ¿cómo agregar nuevas capas de complejidad (gRPC
) mejoraría el rendimiento?fuente
gRPC
que también paga una vez para establecer una conexión, pero que ha añadido la carga adicional de analizar protobuf. De todos modos, es difícil hacer conjeturas sin demasiada información, pero apuesto a que, en general, dado que está agregando pasos adicionales de codificación / decodificación en su canalización, lagRPC
implementación sería más lenta que la del socket web equivalente.Estoy bastante impresionado con lo bien que Akka TCP se ha desempeñado aquí: D
Nuestra experiencia fue un poco diferente. Estábamos trabajando en instancias mucho más pequeñas usando Akka Cluster. Para el control remoto de Akka, cambiamos de Akka TCP a UDP usando Artery y logramos una tasa mucho más alta + un tiempo de respuesta más bajo y más estable. Incluso hay una configuración en Artery que ayuda a equilibrar el consumo de CPU y el tiempo de respuesta desde un arranque en frío.
Mi sugerencia es usar un marco basado en UDP que también se encargue de la confiabilidad de la transmisión para usted (por ejemplo, ese Artery UDP), y simplemente serializar usando Protobuf, en lugar de usar gRPC completo. El canal de transmisión HTTP / 2 no es realmente para fines de alto rendimiento y bajo tiempo de respuesta.
fuente