Профилирование производительности Netty

Я пишу приложение Netty. Приложение работает в 64-битном восьмиядерном ядре Linux

Приложение Netty - это простой маршрутизатор, который принимает запросы (входящий конвейер), считывает некоторые метаданные из запроса и пересылает данные в удаленную службу (исходящий конвейер).

Эта удаленная служба вернет один или несколько ответов на исходящий конвейер. Приложение Netty направит ответы обратно исходному клиенту (входящему конвейеру)

Будут тысячи клиентов. Будут тысячи удаленных сервисов.

Я занимаюсь небольшим тестированием (десять клиентов, десять пультов), и я не вижу суб 10-миллисекундной производительности, которую ожидаю на уровне 99,9 процентиля. Я измеряю задержку как со стороны клиента, так и с сервера.

Я использую полностью асинхронный протокол, похожий на SPDY. Я фиксирую время (я просто использую System.nanoTime()), когда мы обрабатываем первый байт в FrameDecoder. Я останавливаю таймер перед вызовом channel.write(). Я измеряю субмиллисекундное время (99,9 процентиля) от входящего трубопровода до исходящего трубопровода и наоборот.

Я также измерил время от первого байта в FrameDecoder до того, как был вызван обратный вызов ChannelFutureListener на (выше) message.write(). Время было высоким десятки миллисекунд (99,9 процентиля), но мне не удалось убедить себя, что это полезные данные.

Моя первоначальная мысль заключалась в том, что у нас были медленные клиенты. Я просмотрел channel.isWritable() и зарегистрировался, когда это вернуло false. Этот метод не возвращал false при нормальных условиях

Некоторые факты:

  • Мы используем фабрики NIO. Мы не настроили рабочий размер
  • У нас отключен Nagel (tcpNoDelay = true)
  • Мы включили keep keep (keepAlive = true)
  • CPU не работает 90%% времени
  • Сеть простаивает
  • GC (CMS) вызывается каждые 100 секунд или около того в течение очень короткого промежутка времени.

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

Похоже, что channel.write() добавляет сообщение в очередь, и мы (разработчики приложений, использующие Netty) не имеют прозрачности в этой очереди. Я не знаю, является ли очередь очередью Netty, очередью ОС, очередью сетевой карты или чем. В любом случае, я просматриваю примеры существующих приложений, и я не вижу никаких анти-шаблонов, которые я следую за

Спасибо за любую помощь/понимание

Ответ 1

Netty создает Runtime.getRuntime(). availableProcessors() * 2 рабочих по умолчанию. 16 в вашем случае. Это означает, что вы можете обрабатывать до 16 каналов одновременно, другие каналы будут ждать до тех пор, пока вы не освободите обработчики ChannelUpstreamHandler.handleUpstream/SimpleChannelHandler.messageReceived, поэтому не делайте тяжелых операций в этих (IO) потоках, иначе вы можете застрять другие каналы.

Ответ 2

Вы не указали свою версию Netty, но это похоже на Netty 3. Netty 4 теперь стабилен, и я бы посоветовал вам как можно скорее обновить его. Вы указали, что хотите сверхбыстрые задержки, а также десятки тысяч клиентов и сервисов. Это не очень хорошо сочетается. NIO по своей сути достаточно скрыт, в отличие от OIO. Тем не менее, здесь возникает проблема, что OIO, вероятно, не сможет достичь числа клиентов, на которые вы надеетесь. Тем не менее я бы использовал цикл событий OIO/ factory и посмотрел, как это происходит.

У меня сам есть TCP-сервер, который занимает около 30 мс на локальном хосте для отправки и получения и обработки нескольких TCP-пакетов (измеряется от клиента времени, который открывает сокет до тех пор, пока сервер не закроет его). Если вам действительно нужны такие низкие задержки, я предлагаю вам отказаться от TCP из-за спама SYN/ACK, который необходим для открытия соединения, это будет использовать большую часть ваших 10 мс.

Ответ 3

Время измерения в многопоточной среде очень сложно, если вы используете простые вещи, такие как System.nanoTime(). Представьте следующее в 1-й системе:

  • Thread A проснулся и начинает обработку входящего запроса.
  • Thread B пробуждается и начинает обработку входящего запроса. Но поскольку мы работаем над 1-й машиной, это в конечном итоге требует, чтобы Thread A был включен в паузу.
  • Thread B выполняется и выполняется очень быстро.
  • Thread A возобновляет и заканчивает, но занимает вдвое больше, чем Thread B. Потому что вы на самом деле измерили время, необходимое для завершения Thread A + Thread B.

В этом случае есть два подхода к правильному измерению:

  • Вы можете обеспечить, чтобы во всех случаях использовался только один поток.
    Это позволяет измерять точную производительность операции, , если ОС не мешает.. В приведенном выше примере поток B также может быть вне вашей программы. В этом случае общий подход заключается в медиане интерференции, которая даст вам оценку скорости вашего кода.
    Однако вы можете предположить, что в противном случае бездействующая многоядерная система, будет другое ядро ​​для обработки фоновых задач, поэтому ваше измерение, как правило, не прерывается. Установка этого потока на высокий приоритет также помогает.

  • Вы используете более сложный инструмент, который подключается к JVM для фактического измерения атомных исполнений и времени, которое требуется для тех, которые практически полностью удаляют внешние помехи. Один инструмент будет VisualVM, который уже интегрирован в NetBeans и доступен как плагин для Eclipse.

В качестве общего совета: не рекомендуется использовать больше потоков, чем ядра, если вы не знаете, что эти потоки будут часто блокироваться некоторой операцией. Это не тот случай, когда используется неблокирующий NIO для операций ввода-вывода, поскольку блокировка отсутствует.

Поэтому в вашем конкретном случае вы фактически снижаете производительность для клиентов, как объяснялось выше, потому что связь будет приостановлена ​​до 50% времени при высокой нагрузке. В худшем случае это может привести к тому, что клиент даже запустится в таймаут, так как нет гарантии, что поток фактически возобновлен (если вы явно не запрашиваете справедливое планирование).