...

четверг, 26 декабря 2013 г.

[Из песочницы] Бенчмарк HTTP-серверов (С/C++) в FreeBSD


Проведено сравнение производительности ядер HTTP-серверов, построенных с использованием семи C/C++ библиотек, а также (в познавательных целях) — других готовых решений в этой области (nginx и node.js).


HTTP-сервер — это сложный и интересный механизм. Есть мнение, что плох программист, не написавший свой компилятор, я бы заменил «компилятор» на «HTTP-сервер»: это и парсер, и работа с сетью, и асинхронность с многопоточностью и много чего еще....


Тесты по всем возможным параметрам (отдача статики, динамики, всевозможные модули шифрования, прокси и т.п.) — задача не одного месяца кропотливой работы, поэтому задача упрощена: будем сравнивать производительность ядер. Ядро HTTP-сервера (как и любого сетевого приложения) — это диспетчер событий сокетов и некий первичный механизм их обработки (реализованный в виде пула потоков, процессов и т.п.). Сюда же можно отнести парсер HTTP-пакетов и генератор ответов. На первый взгляд, все должно свестись к тестированию возможностей того или иного системного механизма обработки асинхронных событий (select, epoll и т.п.), их мета-обёрток (libev, boost.asio и др.) и ядра ОС, однако конкретная реализация в виде готового решения дает существенную разницу в производительности.


Был реализован свой вариант HTTP-сервера на libev. Конечно, реализована поддержка небольшого подмножества требований пресловутого rfc2616 (вряд ли ее полностью реализует хоть один HTTP-сервер), лишь необходимый минимум для соответствия требованиям, предъявляемым к участникам данного тестирования,




  1. Слушать запросы на 8000-ом порту;

  2. Проверять метод (GET);

  3. Проверять путь в запросе (/answer);

  4. Ответ должен содержать:

    HTTP/1.1 200 OK
    Server: bench
    Connection: keep-alive
    Content-Type: text/plain
    Content-Length: 2
    42




  5. На любой другой метод\путь — должен возвращаться ответ с кодом ошибки 404 (страница не найдена).




Как видите — никаких расширений, обращений к файлам на диске, интерфейсов шлюза и т.п. — все максимально упрощено.

В случаях, когда сервер не поддерживает keep-alive соединения (кстати, этим отличился только cpp-netlib), тестирование проводилось в соотв. режиме.

Предыстория




Изначально стояла задача реализовать HTTP-сервер с нагрузкой в сотни миллионов обращений в сутки. Предполагалось, что будет относительно небольшое кол-во клиентов, генерирующих 90% запросов, и большое число клиентов, генерирующих оставшиеся 10%. Каждый запрос нужно отправлять дальше, на несколько других серверов, собирать ответы и возвращать результат клиенту. От скорости и качества ответа зависел весь успех проекта. Поэтому я не мог просто взять и использовать первое попавшееся готовое решение. Нужно было получить ответы на следующие вопросы:


  1. Стоит ли изобретать свой велосипед или же использовать существующие решения?

  2. Подходит ли node.js для высоконагруженных проектов? Если да, то выкинуть заросли С++ кода и переписать все в 30 строк на JS




На повестке стояли и менее значимые вопросы, например, влияет ли HTTP keep-alive на производительность? (спустя год ответ был озвучен здесь — влияет, и весьма существенно).

Разумеется, сначала был изобретён свой велосипед, затем появился node.js (узнал про него два года назад), ну а потом захотелось узнать: насколько существующие решения эффективнее собственного, не зря ли было потрачено время? Собственно, так и появился данный пост.


Подготовка




Железо


  • Процессор: CPU: AMD FX(tm)-8120 Eight-Core Processor

  • Cеть: localhost (почему — см. в TODO)




Софт


  • ОС: FreeBSD 9.1-RELEASE-p7




Тюнинг

Обычно в нагрузочном тестировании сетевых приложений принято изменять следующий стандартный набор настроек:

/etc/sysctl.conf

kern.ipc.somaxconn=65535

net.inet.tcp.blackhole=2

net.inet.udp.blackhole=1

net.inet.ip.portrange.randomized=0

net.inet.ip.portrange.first=1024

net.inet.ip.portrange.last=65535

net.inet.icmp.icmplim=1000





/boot/loader.conf

kern.ipc.semmni=256

kern.ipc.semmns=512

kern.ipc.semmnu=256

kern.ipc.maxsockets=999999

kern.ipc.nmbclusters=65535

kern.ipc.somaxconn=65535

kern.maxfiles=999999

kern.maxfilesperproc=999999

kern.maxvnodes=999999

net.inet.tcp.fast_finwait2_recycle=1





Однако в моем тестировании они не приводили к повышению производительности, а в некоторых случаях даже приводили к значительному замедлению, поэтому в финальных тестах никаких изменений настроек в системе не проводилось (т.е. все настройки по умолчанию, ядро GENERIC).

Участники




Библиотечные


























































ИмяВерсияСобытияПоддержка keep-aliveМеханизм
cpp-netlib 0.10.1Boost.Asioнетмногопоточный
hand-made1.11.30libevдамногопроцессный (один поток на процесс), асинхронный
libevent 2.0.21libeventдаоднопоточный*, асинхронный
mongoose 4.1**pollдамногопоточный (отдельный поток для входящих соединений), с очередью (подробнее)
onion 0.5epollдамногопоточный
Pion Network Library 0.5.4Boost.Asioдамногопоточный
POCO C++ Libraries 1.4.3epoll/selectдамногопоточный (отдельный поток для входящих соединений), с очередью (подробнее)



Готовые решения























ИмяВерсияСобытияПоддержка keep-aliveМеханизм
Node.js 0.10.17libuvдамодуль cluster (многопроцессная обработка)
nginx 1.4.4epoll, select, kqueueдамногопроцессная обработка



*для тестов переделан по схеме «многопроцессный — один процесс один поток»

**Авторы mongoose буквально на днях выкатили 5 версию, в которой практически с нуля переделана архитектура. Как раз когда все тесты с 4.2 были закончены. Но новая версия все ещё сырая, keep-alive поломан, поэтому решено было остаться на 4.2. Но не тут то было. Создатели решили убрать отовсюду версию 4.2. Максимально доступная стабильная версия стала 4.1. Пришлось откатиться (и переделывать под нее код).

Дисквалифицированы



































ИмяПричина
nxweb только Linux
g-wan только Linux (и вообще...)
libmicrohttpd постоянные падения при нагрузках
yield ошибки компиляции
EHS ошибки компиляции
libhttpd синхронный, HTTP/1.0, не дает поменять заголовки
libebb ошибки компиляции, падения



В качестве клиента использовалось приложение от разработчиков lighttpd — weighttpd. Изначально планировалось использовать httperf, как более гибкий инструмент, но он постоянно падает. Кроме того, weighttpd основан на libev, который гораздо лучше подходит для FreeBSD, чем httperf с его линуксовым select-ом. В качестве главного тестового скрипта (обертки над weighttpd с подсчётом расхода ресурсов и пр.) рассматривался gwan-овский ab.c, переделанный под FreeBSD, но в последствии был переписан с нуля на Пайтоне (bench.py в приложении).

Клиент и сервер запускались на одной и той же физической машине.

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



  • Количество серверных потоков (1 и 8).

    Исключение — mongoose. У него кол-во потоков сервера всегда равно количеству открытых соединений. При попытках задать макс. кол-во потоков больше 50 (значение по умолчанию) — mongoose начинал падать.

  • Количество параллельно открытых запросов клиентов (10, 50, 100, 200, 400, 800)




В каждой конфигурации выполнялось по 5 итераций (всего — 2*6*5=60 итераций для каждого сервера). Ожидалось, что разные сервера покажут себя лучше в разных конфигурациях.

Результаты




Как и ожидалось, разные сервера показали лучший результат в разной комбинации вышеназванных параметров. После недолгих раздумий, в итоговую таблицу вошли результаты лучшей комбинации для данного сервера. В приложении вы сможете найти результаты для всей матрицы значений.

Для 1 млн. запросов имеем:













































































































МестоИмяПотоковВремяЗапросов
СерверныхКлиентскихПольз.Сист.Успешных (в сек.)Неуспешных (%)
1hand-made820023.829878.130921689470
2mongoose502008.69087814.8121371543400
3libevent840024.1585914.8273791397700
4nginx81008.3793239.8611541364850
5POCO85040.92895615.933258867160
6onion8509.550056.030781644030
7pion840081.54361417.370284483590
8node.js8100121.47498112.551338465950
9cpp-netlib110087.59046729.244296166730



График потерь (запросы, завершенные ошибками либо закрытием соединения для таблицы выше):


cpp-netlib показал странный результат: производительность одного серверного потока оказалась выше, чем восьми. Мало того что он единственный не поддерживал HTTP keep-alive соединения, так ещё и падал где-то в недрах boost-а, было проблематично выполнить все 60 итераций подряд, но в итоге получилось. Однозначно, решение сырое, документация — устаревшая. Законное последнее место.


С победителями (первые 4 места показали приблизительно одинаковую скорость) не все так однозначно. Если посмотреть подробный отчёт запусков (bench_res.csv в приложении) то увидим следующее:



  • В hand-made решении в режиме «1 поток сервера на 800 потоков клиента» (самый «экстремальный» режим работы) процент потерянных запросов колеблется от 15% до 18%. Это плохо.

  • У mongoose-а проблема с большим количеством соединений (вызвано ограничением макс. кол-ва открытых соединений сервера — 50). Потери доходят до 58%. И это наблюдается практически во всех режимах «400 клиентских потоков и выше».

  • У libevent и nginx потерь нет. На все запросы всегда приходят ответы.




Сравним libevent и nginx. Несмотря на то, что по скорости они практически одинаковы, nginx значительно проигрывает libevent-у в режимах с 400 клиентскими потоками и незначительно в режимах с 800 клиентскими потоками. Поэтому, именно libevent (доработанный до схемы «создаём несколько процессов на одном порте») я бы назвал победителем соревнований, но решать вам.

Не буду хвалить свою реализацию — в общем зачёте она держит 10 первых мест. Несмотря на этот факт, это лишь наколенная имитация HTTP-сервера. Данная реализация более 2-х лет обрабатывает сотни миллионов HTTP запросов в день от реальных HTTP-клиентов, собирает ответы с более сотни различных HTTP-серверов и не нуждается в оправданиях и попытках приравнять ее к полноценным решениям.


node.js, откровенно говоря, разочаровал (не так категорично, как выразился товарищ здесь, но V8 еще пилить и пилить. Что это за high-load решение, которое даже без полезной нагрузки так жадно потребляет ресурсы и выдаёт 1/4 производительности топовых участников тестирования? Можно добавить, что при создании полезной нагрузки (например, использования модуля async) процессорное время начинает убиваться ещё в 9000 раз больше, при этом памяти каждый процесс из cluster-а отъедает более 500 Мб. Возможно, это проявляется лишь на FreeBSD, но я уверен, что проблема в node.js.


По-поводу HTTP keep-alive on/off: если в посте разница доходила до x2 раз, то в моих тестах разница доходит до х10.


TODO





  • бенчмарк в режиме «клиент и сервер на разных машинах». Многие придерживаются теории, что де именно такой режим покажет реальную картину. С другой стороны очевидно, что все упрётся в сетевые железки, причём не только модели сетевых карт, а свичей, роутеров и т.п. — всю инфраструктуру между реальными машинами. В подключении же напрямую смысла не больше, чем через localhost;

  • тестирование клиентской HTTP API (организовать в виде сервера и прокси). Проблема в том, что далеко не все библиотеки предоставляют API для реализации HTTP-клиента. С другой стороны, некоторые популярные библиотеки (libcurl, например) предоставляют исключительно клиентский набор API;

  • использование других HTTP-клиентов. httperf не использовался по указанным выше причинам, ab — по многим отзывам устарел и не держит реальных нагрузок. Но здесь представлены пара десятков решений, какие-то из них стоило бы сравнить;

  • аналогичный бенчмарк в Linux-среде. Вот это должна быть интересная тема (как минимум — новая волна для холиварных обсуждений);

  • прогнать тесты на топовом Intel Xeon с кучей ядер.




По последним двум пунктам — хотел попросить сообщество предоставить доступ к подобному готовому стенду.

Ссылки




Stress-testing httperf, siege, apache benchmark, and pronk — HTTP-клиенты для нагрузочного тестирования серверов.

Performance Testing with Httperf — советы и рекомендации о проведении бенчмарков.

ApacheBench & HTTPerf — описание процесса бенчмарка от G-WAN.

Warp — еще один high-load HTTP-сервер с претензией, Haskell.

Приложение




В приложении вы найдёте исходники и результаты всех итераций тестирования, а также подробные сведения по сборке и установке HTTP-серверов.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий