tuning java tomcat io netty

java - tuning - netty http



Netty más lento que Tomcat (1)

El método messageReceived se ejecuta utilizando un hilo de trabajo que posiblemente se bloquee mediante RequestHandler#handle que puede estar ocupado haciendo algún trabajo de E / S. Podría intentar agregar en el canal un OrderdMemoryAwareThreadPoolExecutor ( recomendado ) para ejecutar los manejadores o, alternativamente, intente enviar el trabajo de su manejador a un nuevo ThreadPoolExecutor y pasar una referencia al canal de socket para luego escribir la respuesta nuevamente al cliente. Ex.:

@Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { executor.submit(new Runnable() { processHandlerAndRespond(e); }); } private void processHandlerAndRespond(MessageEvent e) { ChannelBuffer in = (ChannelBuffer) e.getMessage(); in.readerIndex(4); ChannelBuffer out = ChannelBuffers.dynamicBuffer(512); out.writerIndex(8); // Skip the length and status code boolean success = handler.handle(new ChannelBufferInputStream(in), new ChannelBufferOutputStream(out), new NettyErrorStream(out)); if (success) { out.setInt(0, out.writerIndex() - 8); // length out.setInt(4, 0); // Status } Channels.write(e.getChannel(), out, e.getRemoteAddress()); }

Acabamos de terminar de construir un servidor para almacenar datos en el disco y afrontarlo con Netty. Durante la prueba de carga vimos que Netty escalaba a aproximadamente 8,000 mensajes por segundo. Teniendo en cuenta nuestros sistemas, esto parecía realmente bajo. Para un punto de referencia, escribimos un front-end de Tomcat y ejecutamos las mismas pruebas de carga. Con estas pruebas obtuvimos aproximadamente 25,000 mensajes por segundo.

Estas son las especificaciones para nuestra máquina de prueba de carga:

  • Macbook Pro Quad core
  • 16GB de RAM
  • Java 1.6

Aquí está la configuración de la prueba de carga para Netty:

  • 10 hilos
  • 100,000 mensajes por hilo
  • Código de servidor de Netty (bastante estándar): nuestra interconexión de Netty en el servidor es de dos manejadores: un FrameDecoder y un SimpleChannelHandler que maneja la solicitud y la respuesta.
  • Lado del cliente JIO utilizando Commons Pool para agrupar y reutilizar las conexiones (el grupo se clasificó del mismo modo que el número de subprocesos)

Aquí está la configuración de la prueba de carga para Tomcat:

  • 10 hilos
  • 100,000 mensajes por hilo
  • Tomcat 7.0.16 con configuración predeterminada usando un servlet para llamar al código del servidor
  • El lado del cliente usando URLConnection sin ningún agrupamiento

Mi pregunta principal es ¿por qué una diferencia tan grande en el rendimiento? ¿Hay algo obvio con respecto a Netty que pueda hacer que funcione más rápido que Tomcat?

Editar: Aquí está el código principal del servidor Netty:

NioServerSocketChannelFactory factory = new NioServerSocketChannelFactory(); ServerBootstrap server = new ServerBootstrap(factory); server.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() { RequestDecoder decoder = injector.getInstance(RequestDecoder.class); ContentStoreChannelHandler handler = injector.getInstance(ContentStoreChannelHandler.class); return Channels.pipeline(decoder, handler); } }); server.setOption("child.tcpNoDelay", true); server.setOption("child.keepAlive", true); Channel channel = server.bind(new InetSocketAddress(port)); allChannels.add(channel);

Nuestros manejadores se ven así:

public class RequestDecoder extends FrameDecoder { @Override protected ChannelBuffer decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) { if (buffer.readableBytes() < 4) { return null; } buffer.markReaderIndex(); int length = buffer.readInt(); if (buffer.readableBytes() < length) { buffer.resetReaderIndex(); return null; } return buffer; } } public class ContentStoreChannelHandler extends SimpleChannelHandler { private final RequestHandler handler; @Inject public ContentStoreChannelHandler(RequestHandler handler) { this.handler = handler; } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { ChannelBuffer in = (ChannelBuffer) e.getMessage(); in.readerIndex(4); ChannelBuffer out = ChannelBuffers.dynamicBuffer(512); out.writerIndex(8); // Skip the length and status code boolean success = handler.handle(new ChannelBufferInputStream(in), new ChannelBufferOutputStream(out), new NettyErrorStream(out)); if (success) { out.setInt(0, out.writerIndex() - 8); // length out.setInt(4, 0); // Status } Channels.write(e.getChannel(), out, e.getRemoteAddress()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { Throwable throwable = e.getCause(); ChannelBuffer out = ChannelBuffers.dynamicBuffer(8); out.writeInt(0); // Length out.writeInt(Errors.generalException.getCode()); // status Channels.write(ctx, e.getFuture(), out); } @Override public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) { NettyContentStoreServer.allChannels.add(e.getChannel()); } }

ACTUALIZAR :

Logré obtener mi solución Netty a 4.000 / segundo. Hace unas semanas estaba probando un PING del lado del cliente en mi grupo de conexiones como protección segura contra tomas inactivas, pero olvidé eliminar ese código antes de comenzar las pruebas de carga. Este código efectivamente PINGed el servidor cada vez que se sacó un zócalo del grupo (utilizando Commons Pool). Comenté ese código y ahora obtengo 21,000 / segundo con Netty y 25,000 / segundo con Tomcat.

Aunque, esta es una gran noticia en el lado de Netty, sigo recibiendo 4.000 / segundo menos con Netty que con Tomcat. Puedo publicar mi lado del cliente (que pensé que había descartado, pero aparentemente no) si alguien está interesado en ver eso.