Некоторые стресс-тесты производительности агентов уровней 4 и 7 на основе Netty

Netty

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

  • Простейшая программа для пересылки HTTP-запросов на основе Netty, разница между производительностью четырех слоев и семи слоев
  • Разница в производительности трех моделей прокси-потоков будет подробно объяснена ниже.
  • Разница в производительности ByteBuffer в пуле и без пула

Код, используемый в этом тесте:GitHub.com/Джозеф З Тайгер19…

В коде мы реализуем два набора агентов:

image_1demdig32ia6184m64sppm8vp90.png-55.9kB

Конфигурация компьютера, использованная для теста, (Alibaba Cloud ECS):

image_1dembkev02d2sll1ijc18fl4r48j.png-91.9kB
Всего три машины:

  • На серверном сервере установлен nginx в качестве бэкенда.
  • На клиентском сервере установлен wrk в качестве клиента стресс-теста.
  • Прокси-сервер установил наш тестовый код (прокси)

Серверная часть Nginx

nginx настраивает тестовую страницу по умолчанию (некоторый контент удален, чтобы уменьшить пропускную способность интрасети):

image_1dembfnk81i9m19tkvli148c13h86.png-122.8kB
Количество запросов в секунду, измеренное непосредственно против nginx, составляет 266 000:
image_1delvmebjcpe39n1hdni41hss13.png-55.2kB

О четвертом и седьмом этажах

Для четырехуровневого прокси мы просто используем Netty для пересылки ByteBuf. Семиуровневый прокси будет иметь больше дополнительных накладных расходов, в основном кодирование и декодирование запросов Http и агрегирование запросов Http.Сервер:

image_1demdm2m82vg1i6b4ng1uitjcp9d.png-136.8kB

Клиент:

image_1demdoius2ekjds1kbr5a1vld9q.png-63.2kB

Здесь мы можем думать, что производительность четырехслойного прокси определенно намного лучше, чем у семислойного из-за отсутствия процесса кодирования и декодирования данных Http.Мы можем видеть результаты тестирования.

О потоковой модели

Мы знаем, что в качестве прокси нам нужно запустить сервер, чтобы получить запрос от восходящего потока, а затем переслать запрос нисходящему потоку в качестве клиента, получить ответ от нисходящего потока и вернуть его восходящему. И нашему серверу, и клиенту нужны рабочие потоки для обработки запросов ввода-вывода.Есть три подхода;

  • A: Независимый пул потоков NioEventLoopGroup клиента Bootstrap и сервера ServerBootstrap, именуемый IndividualGroup.
  • B: клиент и сервер совместно используют набор пулов потоков, называемый ReuseServerGroup.
  • C: клиент напрямую повторно использует серверный поток EventLoop, называемый ReuseServerThread.

В качестве примера возьмем код семислойного прокси:

image_1demdqavbn5i19ff1g1hrp2gbsan.png-98.4kB

В следующем тесте мы проверим эти три модели потоков. Естественно предположить, что производительность плана А является лучшей, поскольку независимые пулы потоков не влияют друг на друга. Давайте посмотрим на результаты далее.

Четырехуровневый агент + потоковая модель ReuseServerThread

Layer4ProxyServer Started with config: ServerConfig(type=Layer4ProxyServer, serverIp=172.26.5.213, serverPort=8888, backendIp=172.26.5.214, backendPort=80, backendThreadModel=ReuseServerThread, receiveBuffer=10240, sendBuffer=10240, allocatorType=Unpooled, maxContentLength=2000)

image_1delvsom6v03e5pngacv714901g.png-54kB

Четырехуровневый агент + модель потоков IndividualGroup

Layer4ProxyServer Started with config: ServerConfig(type=Layer4ProxyServer, serverIp=172.26.5.213, serverPort=8888, backendIp=172.26.5.214, backendPort=80, backendThreadModel=IndividualGroup, receiveBuffer=10240, sendBuffer=10240, allocatorType=Unpooled, maxContentLength=2000)

image_1dem04l2alqs1l4u1ripg9a1fcu1t.png-54.8kB

Четырехуровневый агент + модель потока ReuseServerGroup

Layer4ProxyServer Started with config: ServerConfig(type=Layer4ProxyServer, serverIp=172.26.5.213, serverPort=8888, backendIp=172.26.5.214, backendPort=80, backendThreadModel=ReuseServerGroup, receiveBuffer=10240, sendBuffer=10240, allocatorType=Unpooled, maxContentLength=2000)

image_1dem0br3r1rr3qmj1mk519nn111v2a.png-55.2kB

Видя, что здесь уже есть результаты, ReuseServerThread имеет лучшую производительность, за ней следует ReuseServerGroup, а худшая — IndividualGroup, что не согласуется с нашим предположением.

Диаграмма мониторинга четырехуровневой системы

Как видно из пропускной способности сети, протестированный ReuseServerThread сначала работал с максимальной пропускной способностью (последние три пика представляют три теста соответственно):

image_1dem0chjrimkn5va5810dk1vk62n.png-52.8kB
Из мониторинга ЦП видно, что наиболее производительный ReuseServerThread использует наименьшее количество ресурсов ЦП (последние три пика представляют три теста соответственно):
image_1dem0ekoq1l59ju1vvn1lp575u34.png-32.5kB

Семиуровневый агент + потоковая модель ReuseServerThread

Layer7ProxyServer Started with config: ServerConfig(type=Layer7ProxyServer, serverIp=172.26.5.213, serverPort=8888, backendIp=172.26.5.214, backendPort=80, backendThreadModel=ReuseServerThread, receiveBuffer=10240, sendBuffer=10240, allocatorType=Unpooled, maxContentLength=2000)

image_1dem0mduhkdc11hc2ue12rd433h.png-55kB

Семиуровневый агент + модель потоков IndividualGroup

Layer7ProxyServer Started with config: ServerConfig(type=Layer7ProxyServer, serverIp=172.26.5.213, serverPort=8888, backendIp=172.26.5.214, backendPort=80, backendThreadModel=IndividualGroup, receiveBuffer=10240, sendBuffer=10240, allocatorType=Unpooled, maxContentLength=2000)

image_1dem0tgtv13ev3h9sl51appi083u.png-55.2kB

Семиуровневый агент + модель потока ReuseServerGroup

Layer7ProxyServer Started with config: ServerConfig(type=Layer7ProxyServer, serverIp=172.26.5.213, serverPort=8888, backendIp=172.26.5.214, backendPort=80, backendThreadModel=ReuseServerGroup, receiveBuffer=10240, sendBuffer=10240, allocatorType=Unpooled, maxContentLength=2000)

image_1dem14prr1e7kr0gi1ggiqu7l4b.png-55kB

В заключение ReuseServerThread имеет лучшую производительность, за ней следует ReuseServerGroup, а худшая — IndividualGroup. Я думаю, что это имеет смысл следующим образом:

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

Схема мониторинга семиуровневой системы

Ниже приведены графики мониторинга пропускной способности сети и ЦП:

image_1dem1fh7m1f0cl8s1d1ic7563765.png-39.3kB
image_1dem1e3g01asrq8r9u16ce5e94r.png-60.1kB
Видно, что семислойный прокси потребляет больше ресурсов, но пропускная способность меньше, чем у четырехслойного (намного меньше QPS). Исходящий трафик чуть больше входящего, что должно быть вызвано дополнительными заголовками запросов в коде:
image_1demf0bhrikp1rh0r5i1q3c1iltc1.png-150.8kB

Попробуйте HttpObjectAggregator установить большую maxContentLength

Layer7ProxyServer Started with config: ServerConfig(type=Layer7ProxyServer, serverIp=172.26.5.213, serverPort=8888, backendIp=172.26.5.214, backendPort=80, backendThreadModel=ReuseServerThread, receiveBuffer=10240, sendBuffer=10240, allocatorType=Pooled, maxContentLength=100000000)

image_1dem1qe4v1ddd1c2311pjej81bf16v.png-54.9kB

Попробуйте пуледбайтбуфаллокатор

Layer7ProxyServer Started with config: ServerConfig(type=Layer7ProxyServer, serverIp=172.26.5.213, serverPort=8888, backendIp=172.26.5.214, backendPort=80, backendThreadModel=ReuseServerThread, receiveBuffer=10240, sendBuffer=10240, allocatorType=Pooled, maxContentLength=2000)

image_1dem1ifds1hoi1lkka691vekmlt6i.png-54.8kB

Вы можете видеть, что распределитель по умолчанию был установлен на PooledByteBufAllocator в Netty 4.1.

image_1demg35il1ambhdb1o3m42c1j9ce.png-43.9kB

Суммировать

Вот краткое изложение таблицы, коэффициент потери производительности основан на первой строке, напрямую использующей Nginx в качестве эталона:

image_1demepcbume4eoacntrb11mh2b4.png-39.1kB

Вывод таков:

  • Nginx очень хорош. На самом деле, конфигурация машины не очень хороша. Его можно запустить на физическом сервере с лучшей конфигурацией. Nginx не проблема для одной машины.
  • Netty очень хороша, ведь это Java-сервер, четырехуровневый форвардинг теряет всего 3% QPS.
  • Будь то семь слоев или четыре уровня, способ мультиплексирования потоков, очевидно, имеет наилучшую производительность и занимает меньше всего ресурсов процессора.
  • Из-за переключения контекста использование Netty для разработки сетевых агентов должно повторно использовать потоки ввода-вывода.
  • Потребление семи слоев гораздо больше, чем четырех, даже Netty не может этого избежать, это проблема с HTTP-протоколом.
  • Производительность PooledByteBufAllocator лучше, чем у UnpooledByteBufAllocator (около 3%).
  • Если HttpObjectAggregator задает большую максимальную длину содержимого, это немного повлияет на производительность.

Причина, по которой я написал эту статью для этого анализа, заключается в том, что недавно я занимался оптимизацией производительности и нагрузочным тестированием нашего шлюза собственной разработки.GitHub.com/spring-av и…. Я обнаружил, что есть некоторые другие прокси-проекты на основе Netty с открытым исходным кодом, которые не используют соединения повторно. Возможно, автор не осознал эту проблему. Я посмотрел код Zuul, и он также был повторно использован.