Подтвердить что ты не робот

Netty медленнее, чем Tomcat

Мы только что закончили создание сервера для хранения данных на диск и вышли с ним Netty. Во время тестирования нагрузки мы наблюдали масштабирование Netty до 8000 сообщений в секунду. Учитывая наши системы, это выглядело очень низким. Для эталона мы написали интерфейс Tomcat и выполнили те же тесты нагрузки. С помощью этих тестов мы получали примерно 25 000 сообщений в секунду.

Вот спецификации для нашей машины тестирования нагрузки:

  • MacBook Pro Quad core
  • 16 ГБ оперативной памяти
  • Java 1.6

Вот настройка загрузки для Netty:

  • 10 потоков
  • 100 000 сообщений на поток
  • Код сервера Netty (довольно стандартный) - наш конвейер Netty на сервере - это два обработчика: FrameDecoder и SimpleChannelHandler, которые обрабатывают запрос и ответ.
  • Клиентский JIO с использованием Commons Pool для объединения и повторного использования соединений (пул был таким же, как и количество потоков)

Вот тестовая установка загрузки для Tomcat:

  • 10 потоков
  • 100 000 сообщений на поток
  • Tomcat 7.0.16 с настройкой по умолчанию с использованием сервлета для вызова кода сервера
  • Клиентская сторона использует URLConnection без объединения.

Мой главный вопрос - почему такие огромные различия в производительности? Есть ли что-то очевидное в отношении Netty, которое может заставить его работать быстрее, чем Tomcat?

Изменить: Вот главный код сервера 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);

Наши обработчики выглядят так:

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());
  }
}

ОБНОВЛЕНИЕ:

Мне удалось получить решение Netty в пределах 4000/сек. Несколько недель назад я тестировал клиентскую сторону PING в моем пуле соединений как безопасную защиту от простаивающих сокетов, но я забыл удалить этот код, прежде чем начать нагрузочное тестирование. Этот код эффективно PINGed сервер каждый раз, когда Socket был извлечен из пула (используя Commons Pool). Я прокомментировал этот код, и теперь я получаю 21 000/секунд с Netty и 25 000 в секунду с Tomcat.

Хотя это отличная новость на стороне Netty, я по-прежнему получаю 4,000/second меньше с Netty, чем с Tomcat. Я могу опубликовать свою клиентскую сторону (что, по-моему, я исключаю, но, по-видимому, нет), если кто-то заинтересован в этом.

4b9b3361

Ответ 1

Метод messageReceived выполняется с использованием рабочего потока, который, возможно, блокируется RequestHandler#handle, который может быть занят выполнением некоторых операций ввода-вывода. Вы можете попробовать добавить в конвейер канала OrderdMemoryAwareThreadPoolExecutor (рекомендуется) для выполнения обработчиков или, альтернативно, попытаться отправить работу обработчика на новый ThreadPoolExecutor и передачу ссылки на канал сокета для последующего написания ответа клиенту. Пример:.

@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());
} 

Ответ 2

Существует хорошая серия статей в блоге о создании HTTP-приложения на основе Netty: http://thesoftwarekraft.blogspot.ca/2010/07/how-jboss-netty-helped-me-build-highly.html

Лучше всего также посмотреть в правой боковой части страницы, где есть серия из пяти статей под названием "Нам действительно нужны контейнеры сервлетов?" который включает некоторые тесты производительности и объяснение того, как были достигнуты результаты Netty.

Я не эксперт Netty, но учитывая, что он полагается на Native I/OI, подозревают, что он не предлагает такую ​​же многопоточную модель, как JVM (на которую полагается Tomcat), и поэтому вы можете видеть либо 1. все работает на одно ядро ​​или 2. большая часть времени теряется в блокировке, разблокировке и ожидании блокировок.