Muchas solicitudes UDP perdidas en el servidor UDP con Netty

Escribí un servidor UDP simple con Netty que simplemente imprime en los registros los mensajes (marcos) recibidos. Para hacer eso, creé un decodificador decodificador de cuadros simple y un controlador de mensajes simple. También tengo un cliente que puede enviar varias solicitudes de forma secuencial y/o en paralelo.

Cuando configuro mi probador de clientes para enviar, por ejemplo, cientos de solicitudes secuencialmente con un pequeño retraso entre ellas, mi servidor escrito con Netty las recibe todas correctamente. Pero en el momento en que aumento la cantidad de solicitudes simultáneas en mi cliente (100 por ejemplo) junto con las secuenciales y pocas repeticiones, mi servidor comienza a perder muchas solicitudes. Cuando envío 50000 solicitudes, por ejemplo, mi servidor solo recibe alrededor de 49000 cuando solo usa el ChannelHandler simple que imprime el mensaje recibido.

Y cuando agrego el decodificador de marco simple (que imprime el marco y lo copia en otro búfer) frente a este controlador, ¡el servidor solo maneja la mitad de las solicitudes!

Me di cuenta de que no importa la cantidad de trabajadores que especifique en la NioDatagramChannelFactory creada, siempre hay uno y solo un hilo que maneja las solicitudes (estoy usando el Executors.newCachedThreadPool() recomendado como el otro parámetro).

¡También creé otro servidor UDP simple similar basado en el DatagramSocket que viene con el JDK y maneja todas las solicitudes perfectamente con 0 (cero) perdidos! Cuando envío 50000 solicitudes en mi cliente (con 1000 subprocesos, por ejemplo), recibí 50000 solicitudes en mi servidor.

¿Estoy haciendo algo mal al configurar mi servidor UDP usando Netty? ¿O tal vez Netty simplemente no está diseñado para soportar tal carga? ¿Por qué solo se usa un subproceso en el grupo de subprocesos en caché dado (observé que solo se usa un subproceso y siempre el mismo al buscar en JMX jconsole y al verificar el nombre del subproceso en los registros de salida)? ¡Creo que si se usaran más subprocesos como se esperaba, el servidor podría manejar fácilmente esa carga porque puedo hacerlo sin ningún problema cuando no uso Netty!

Vea mi código de inicialización a continuación:

...

lChannelfactory = new NioDatagramChannelFactory( Executors.newCachedThreadPool(), nbrWorkers );
lBootstrap = new ConnectionlessBootstrap( lChannelfactory );

lBootstrap.setPipelineFactory( new ChannelPipelineFactory() {
    @Override
    public ChannelPipeline getPipeline()
    {
        ChannelPipeline lChannelPipeline = Channels.pipeline();
        lChannelPipeline.addLast( "Simple UDP Frame Dump DECODER", new SimpleUDPPacketDumpDecoder( null ) );            
        lChannelPipeline.addLast( "Simple UDP Frame Dump HANDLER", new SimpleUDPPacketDumpChannelHandler( lOuterFrameStatsCollector ) );            
        return lChannelPipeline;
    }
} );

bindChannel = lBootstrap.bind( socketAddress );

...

Y el contenido del método decode() en mi decodificador:

protected Object decode(ChannelHandlerContext iCtx, Channel iChannel, ChannelBuffer iBuffer) throws Exception
{
    ChannelBuffer lDuplicatedChannelBuffer = null;
    sLogger.debug( "Decode method called." );

    if ( iBuffer.readableBytes() < 8 ) return null;
    if ( outerFrameStatsCollector != null ) outerFrameStatsCollector.incrementNbrRequests();

    if ( iBuffer.readable() ) 
    {        
        sLogger.debug( convertToAsciiHex( iBuffer.array(), iBuffer.readableBytes() ) );                     
        lDuplicatedChannelBuffer = ChannelBuffers.dynamicBuffer( iBuffer.readableBytes() );            
        iBuffer.readBytes( lDuplicatedChannelBuffer );
    }

    return lDuplicatedChannelBuffer;
}

Y el contenido del método messageReceived() en mi controlador:

public void messageReceived(final ChannelHandlerContext iChannelHandlerContext, final MessageEvent iMessageEvent) throws Exception
{
    ChannelBuffer lMessageBuffer = (ChannelBuffer) iMessageEvent.getMessage();
    if ( outerFrameStatsCollector != null ) outerFrameStatsCollector.incrementNbrRequests();

    if ( lMessageBuffer.readable() ) 
    {        
        sLogger.debug( convertToAsciiHex( lMessageBuffer.array(), lMessageBuffer.readableBytes() ) );            
        lMessageBuffer.discardReadBytes();
    }
}

preguntado el 09 de marzo de 12 a las 16:03

Usted sabe que UDP no tiene garantías de entrega, ¿verdad? -

Sí, sé que no existen tales garantías de entrega, pero mis pruebas de carga se realizan localmente y no pierdo nada con mi servidor simple que usa DatagramSocket en lugar de Netty. Actualmente también estoy analizando las solicitudes con WireShark para validar que no se pierde nada en un caso (sin netty) y que se pierden paquetes al usar netty. -

@The4Summers Si puede ver que los paquetes se están perdiendo con Netty, Netty no está procesando los paquetes entrantes lo suficientemente rápido, o usted no lo está, por lo que el búfer de recepción del socket se está llenando, por lo que los paquetes entrantes se descartan, sin tener otro lugar para Vamos. Acelere su recepción o ralentice su envío. -

1 Respuestas

No ha configurado correctamente la instancia de ConnectionlessBootstrap.

  1. Tienes que configurar los seguidores con valores óptimos.

    Tamaño SO_SNDBUF, tamaño SO_RCVBUF y ReceiveBufferSizePredictorFactory

    lBootstrap.setOption("sendBufferSize", 1048576);
    
    lBootstrap.setOption("receiveBufferSize", 1048576);
    
    lBootstrap.setOption("receiveBufferSizePredictorFactory", 
     new AdaptiveReceiveBufferSizePredictorFactory(MIN_SIZE, INITIAL_SIZE, MAX_SIZE));
    

    verifique la clase DefaultNioDatagramChannelConfig para obtener más detalles.

  2. La tubería está haciendo todo usando el subproceso de trabajo de Netty. Si el subproceso de trabajo está sobrecargado, retrasará la ejecución del bucle de eventos del selector y habrá un cuello de botella en la lectura/escritura del canal. Debe agregar un controlador de ejecución de la siguiente manera en la canalización. Liberará el subproceso de trabajo para que haga su propio trabajo.

    ChannelPipeline lChannelPipeline = Channels.pipeline();
    
    lChannelPipeline.addFirst("execution-handler", new ExecutionHandler(
      new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576));
    
    //add rest of the handlers here
    

respondido 10 mar '12, 08:03

¡Gracias por la pista sobre las opciones de tamaño de búfer! Sin embargo, con respecto al controlador de ejecución, lo probé después de esta publicación y, sí, ayudó un poco, pero aún perdía la mitad de las solicitudes cuando se usaba un decodificador y un controlador. Estoy seguro de que aumentar el búfer ayudará mucho más. - los4veranos

Sin embargo, todavía no entiendo por qué debemos especificar un ejecutor de grupo de subprocesos y una cantidad de trabajadores al instanciar la clase NioDatagramChannelFactory si nunca se usan. - los4veranos

Me alegro de que te haya funcionado. Los ejecutores de grupos de subprocesos se utilizan para crear subprocesos de trabajo en canalizaciones de NioDatagramChannel. Se puede asignar un subproceso de trabajo a un DatagramChannel para realizar lectura/escritura sin bloqueo. Si su aplicación está escuchando en más de un puerto, se crearán muchos subprocesos de trabajo (el valor predeterminado es el tamaño de la CPU * 2) y se asignarán a DatagramChannel en forma rotativa. Si desea tener más subprocesos de trabajo, puede especificar en el constructor NioDatagramChannelFactory. - Jestan Nirojan

¡Todavía no puedo creer que la aplicación Netty esté perdiendo paquetes! ¿Está seguro de que su decodificador está funcionando correctamente? ¿Por qué no usa un decodificador de cuadros que viene con netty (FixedLengthFrameDecoder, LengthFieldBasedFrameDecoder ..) y cuenta la cantidad de mensajes en su controlador? - Jestan Nirojan

esta linea es correcta? si ( iBuffer.readableBytes() < 8 ) devuelve nulo; - Jestan Nirojan

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.