Пристально смотрю на Node.JS

в 0:49, , рубрики: Без рубрики

Регулярно слышу фразу типа «Node.js не подходит для хайлоада».
Захотелось самому посмотреть.
Хотел написать комментарий к той статье, но передумал и написал больше.
Автору той статьи, большое спасибо за интересный топик, задело.
В интернете кто-то не прав. Наших бъют! Обидно, да.
Для себя я выводы сделал, но это мои субъективные цифры и немножко GITа.
Что думать — не знаю, пишите пожалуйста в каментах своё мнение.

Я — шаман! Тихо беру в руки бубен и расчехляю варган

Не люблю ресурсы, т.к. понимаю, что в случае ЧС их у меня не будет.
Поэтому у меня они весьма скромны: AMD C-60 / 4 Gb / SSD.

Всё это запаковано в нетбук, весит около килограмма и управляется Linux Mint.
Предвижу вопросы, поэтому отвечу сразу.
Да, бывает и такое что я готовлю. Ещё я крестиком вышивать умею как кот Матроскин. Пою на гитаре как Элвис. По дому там могу гвоздь забить пассатижами. В общем подарок семье — незаменимый специалист широкого профиля.

А увлекаюсь я программированием на JavaScript.
Нет, я могу читать Python, Ruby, PHP, Perl, VB, C#, Java, etc.
Но писать не могу, «не лежит душа».
И образования у меня нет профильного, ИТ-шного, поэтому пользуюсь банальной логикой и тем, что от природы иногда предпочитаю думать своей собственной головой, а не чъей-нибудь чужой.
Тем и живу.

Поставили мне Заказчики задачу сферического сравнительыного анализа Node.JS и Nginx.
Т.к. мерять нужно было потенциальную производительность самой ноды, то задачу поставили хитро:

— NGINX отдаёт только свою дефолтную статическую страницу index.html.
— Сервер Node должен вести подсчёт соединений и выдавать в каждом ответе результат, так же считать и максимальное количество.
— Клиентское ПО должно считать количество обработанных запросов в секунду, т.е. удачно завершившихся со статусом 200 OK.

Понятно, что nginx выиграет. Но суть не в нём, а в сравнении.
Т.е. именно во сколько раз интерпретатор ноды хуже чем статика Nginx.

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

Ссылка на GIT будет в конце. Use Case тем кто «в танке» будет там же.

Расскажу немного как я до этого докатился, чтобы, так сказать, развеять сомнения.

Все мы знаем, что современные процессоры работают последовательно.
Знаем то все, но вот понимаем как хотим, а не так как правильно!

Скажите, Вы можете думать над несколькими вещами сразу?
А если в этот момент зазвонит телефон и Вам нужно будет ответить на звонок?
А если параллельно с телефоном к Вам подойдёт человек-коллега, к котор[ой||му] у Вас симпатия и задаст какой-нибудь очень важный вопрос? А если при этом ещё вдруг мимо будет проходить человек-БОСС, заглянет в экран на котором в несвёрнутом окне браузера красуется лурк или ещё чего веселей?

Вот то то же — обработка прерываний и переполнение стека.

Казалось бы — при чём здесь асинхронность?

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

У Вас, например, максимально мозг может удерживать 8 оперативных задач, по аналогии с 8-ю ядрами проца.
Остальные — они где-то там, когда-то будут, до них ещё далеко.

Сами думайте как это представить, поиграйте в Загадку Энштейна, чтоль, получается у Вас там всю многопоточность только в голове удержать (как в L1-L2 Cache), всмысле, да — без листочка (Mem)?


Вот теперь давайте считать.

Запускаете Вы свою приложуху на ноде, там у Вас стандартный код, может быть отсюда .

var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
...

for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
...

Или что-нибудь похожее, с многопоточностью.

Как Вы думаете, кто будет потоками рулить?

Правильно — Operating System.
А в системе кто ими рулит?

Правильно — Linux Kernel Windows Core — Hardware Abstraction Layer (hal.dll, наверное, могу ошибаться).

Пока вроде бы, ничего такого подозрительного нет.

Но давайте всё-таки чуточку подумаем ещё!
Пусть кроме ноды у нас больше вообще ничего не установлено.

Что ещё есть на этом «железе» кроме неё?
Правильно — Operating System.

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

Итак, каждое из железных ядер процессора считает последовательно.
Над ними исполняемый код ядра, который типа пытается ВСЁ представить как параллельное мультизадачное и прочие рюшечки.
Внутри, под управлением ядра крутится однопоточный код нодовых процессов, которых у нас 8 штук, по количеству ядер.

Что будет с полимерами когда к нам поступит «IO» в ядро по сетевому стеку, например по HTTP?
Вроде пока ядро OS справляется…

А если нода уже всё утилизировала на всех 8-ми ядрах?
Вроде пока ядро OS тоже справляется…

Но здесь я бы уже хотел посмотреть на бенчмарк.

Давайте так и поступим!

1. Сравним ноду с Nginx, как описано в задачке выше.
2. Сравним отзывчивость ноды с полной загрузкой CPU и без неё.

Сразу скажу, что все тесты я крутил минут по 10, занимаясь при этом обычными задачами типа кода и сёрфинга.

Да, для того, чтобы заставить процесс работать лишь на определённых ядрах в Linux существует taskset.

В ноде я использую его так:

  var PID = process.pid;
  exec('taskset -pc ' + affinity + ' ' + PID);

ПО для запросов я тоже написал на ноде через request.js, важно было не забыть про http.agent.maxSockets. Можно было, конечно, CURL, но хотелось самому всё посчитать.

Итак, первый тест: сервера на Nginx и Node, max 10000 открытых запросов.
Можно и больше запросов, но я мерил разницу, а не производительность.

На моём железе нода выдержала больше 1200 ответов в секунду.
А nginx выдержал их около 3500.

Я понимаю, что такой хлам кому-то — не показатель.
Но мне очень даже показатель, учитывая что сервер ноды без всякого кластера крутился на нулевом ядре, а запросы на первом.

Т.е. разница даже не на порядки, а всего лишь в разы!

Интересно, что первое время отдача постепенно возрастает, потом стабилизируется итоговой цифре.

Второй тест был для самой ноды:

  Режим ~RPS ~PWait ~Mem
1. Когда сервер ноды и ПО запросов не получили affinity вообще. 1400 6.5 95M
2. Cервер и Запросы с affinity из теста с nginx. 1300 ~5.2 85M
3. Сервер с affinity, запросы без. 1100 ~5.5 100M
4. Сервер без affinity, запросы с ним. 1200 ~6. 90M

Все замеры цифры после 3 минут работы, когда всё стабилизируется.
Интересно, что память сначала «жрётся», потом чуть чуть падает, видимо сборщик отрабатывает.

~RPS — примерно Requests Per Second.
~PWait — примерное время ожидания страницы в реальном браузере. Косвенный показатель нагруженности системы когда что-то делаешь ещё кроме смотрения на бегущие циферки.

За памятью я поглядывал через htop.

Эмм — то есть всё как бы в пределе статистической погрешности.

Просто представим, что там кроме ноды ещё БД, redis, HaProxy и Nginx.
+ Бизнес логика реальная, а не пара сумматоров.
У кого будут «тапки»?

Мои выводы для меня:
0. Node — вполне себе хорош.
1. Оставьте одно ядро самой системе, если это возможно.
2. Оставьте ещё ядер системе, если Вам есть что «крутить».
3. Остальное отдайте Node серверам.

GIT с тестом.

Если кто не в курсе, установка зависимостей производится как npm install.
Если ничего не менять, то после node test_requester.js нужно нажать Enter один раз, чтобы запустить запросы.
Остановка по Ctrl + C.

Да, и не делайте тестов, если не понимаете архитектуры систем на железном уровне.
Хотите понять — курите мануалы, например Ч. Петцольда — КОД.
Там всё подробненько изложено на весьма доступном языке, у меня её дедушка читал, дедушке 75.

cat /etc/sysctl.conf

fs.file-max=65535

kernel.max_lock_depth = 4096
fs.mqueue.queues_max = 1024
fs.mqueue.msg_max = 2048
fs.mqueue.msgsize_max = 16384
fs.inotify.max_user_watches = 1048576
fs.inotify.max_queued_events = 65536
fs.inotify.max_user_instances = 16384
net.ipv4.tcp_fin_timeout = 150
kernel.sem = 1000 32000 128 2048

cat /etc/security/limits.conf

root hard msgqueue 131072
root soft msgqueue 131072
root hard sigpending 131072
root soft sigpending 131072
root hard nproc 131072
root soft nproc 131072
root hard core 131072
root soft core 131072
root hard nofile 131072
root soft nofile 131072

* hard msgqueue 131072
* soft msgqueue 131072
* hard sigpending 131072
* soft sigpending 131072
* hard nproc 131072
* soft nproc 131072
* hard core 131072
* soft core 131072
* hard nofile 131072
* soft nofile 131072

Автор: wentout

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js