Чуть меньше года назад я написал заметку о попытке создать инструмент нагрузочного тестирования на Node.js используя встроенные возможности (модули cluster и net). В комментариях справедливо указали на необходимость анализа RPS и сравнении с другими бенчмарками. В результате сравнения я пришел к естественному выводу, что многопроцессовый сервис никогда не сравнится по производительности с многопоточным из-за очень дорогих издержек на обмен данными (позже мы убедимся в этом на примере)
Процессы или потоки?
В первого взгляда существенной разницы нет, и то и то обеспечивает параллельность запросов, разница только в shared memory для потоков и сам процесс создания процессов чуть более дорогой. Но ведь мы можем создать все процессы заранее и просто передавать им задачи. Но теперь нам нужен канал общения. Давайте посмотрим какие имеются способы межпроцессорного взаимодействия:
Сигналы
Быстрый и универсальный (поддерживается почти всеми ОС) метод общения системы и процессов. Проблема в исключительной негибкости, да и не для того они создавались, чтобы слать по ним JSON.
Сокеты
Именно так реализован cluster в Node. Метод process.send вызывает отправку данных другому процессу по TCP. Что значит сокеты? Это значит новый дескриптор на каждый вызов, куча I/O и CPU вхолостую.
Есть еще несколько способов, но все они либо не кроссплатформенны либо также зависят от I/O.
А теперь посмотрим на состояние системы, когда мы создаем 100 процессов:
И 100 потоков:
Очевидно, что теперь CPU занят лишь тем, чтобы поддерживать связь между этими примитивными процессами (каждый должен выполнять по одному запросу, иначе V8 поставит их в синхронный event loop). CPU utilization падает и каждый запрос выполняется дольше (не дай бог еше память кончится и пойдет файл подкачки)
Но ведь нода однопоточна, что делать?
Делать нативный аддон на С++, используя multithreading. Nan, Node-gyp, POSIX threads и в итоге аддон стал похож на ab — на вход поступает concurrency, на выходе — результаты тестирования. Только в отличии от ab мы можем пользоваться всеми возможностями js для анализа результатов:
[ { time: 80,
body: '<!doctype html><html itemscope=...',
headers: 'HTTP/1.1 200 OKrnDate: Mon, 28 Dec 2015 10:37:35 GMTrnConnection: closernrn' },
....
]
Поддерживаются дополнительные хедеры, POST payload и поскольку это POSIX, то к сожалению только Linux/Mac.
При желании можно считывать только хедеры, обычно этого достаточно, тогда можно сэкономить
еще немного времени на процессинге.
В итоге по производительности nnb сравнялся с ab, выдавая на разных машинах и сетях до 3000 RPS.
Зачем это нужно?
Есть JMeter, есть Tsung, есть масса других платных и бесплатных бенчмарков, но причина по которой многие из них не приживаются в арсенале разработчиков — перегруженный функционал и в итоге все равно недостаточная гибкость. На основне nnb же можно создать свой инструмент для специфических целей или просто скрипт из 10 строчек, который делает только что вам нужно на одном из самых популярных языков.
К примеру stress, который можно запустить с дефолтовым конфигом и смотреть в лайве что происходит с RPS гугла при росте нагрузки (спойлер: ничего) прямо в браузере и на любой unix машине.
Здесь по оси абсцисс кол-во отправленных запросов, по оси ординат — время отклика сервера в миллисекундах. На втором графике запросы в секунду. В конце видно замедление, это вероятно меня начал резать
К сожалению, с доступными мне машинами достичь больше 5000 RPS пока не удалось. Обычно все упирается в ограничения сети. При этом CPU и память почти не нагружены. Stress кстати поддерживает и Node.js cluster и мультипоточность через nnb. Можно играться и с тем и тем, предварительно выставив ulimit -u (максимальное количество процессов запущенное пользователем) и ulimit -n (максимальное количество дескрипторов).
Надеюсь, статья была полезной. По-прежнему рад сотрудничеству со всеми, кому интересна эта тема и, конечно, с наступающим!
Автор: yarax