Разрабатывая приложение на node.js столкнулся с необходимости обмена сообщениями между процессами одной машины. В такой ситуации обычно я применял redis Pub/Sub, бонусом получая возможность масштабирования на несколько серверов. Но сейчас встал вопрос именно о локальном обмене и его производительности.
Я решил исследовать существующие варианты обмена сообщениями. Задача эта достаточно стандартна и известна как IPC (Inter Process Communications). Но что можно сделать на js и на какую производительность при этом рассчитывать?
Проведя серию тестов и получив результаты решил поделиться ими с читателим. Заинтересованных прошу под кат.
Варианты
Итак, 1й вариант, он же точка отсчета — redis Pub/Sub. В процессе тестирования redis-а выявилась достаточно приличная разница между tcp и unix socket режимами. Выбран был более производительный unix socket.
Далее стандартный api ChildProcess.send и process.on('message'). Работает этот механизм используя каналы (pipе) между мастер процессом и запускаемым node процессом. Таким образом следующий вариант — pipe.
Следующие 2 варианта — сокеты: tcp и unix.
Методика и условия тестирования
Изначально мне нужно было отправить сообщение в центральный процесс и получить на него ответ. Таким образом, производительность далее будет измеряться именно в таких парах in-out сообщений. Каждый тестовый скрипт создает 1 мастер процесс, и W дочерних (workers). Во всех тестах совокупное количество процессов меньше доступных ядер на тестовой машине.
Каждый worker запускает C асинхронных циклов последовательной отправки и ожидания ответа фиксированного количества сообщений — симуляция конкурентных запросов в рамках 1 процесса.
В каждое сообщение помимо поле необходимых для теста, добавляется дополнительная строка paylod размером P.
Нативный ChildProcess.send использует JSON для сериализации сообщений. Поэтому тесты unix и tcp сокетов были так же построены с использованием JSON. Также был добавлен тест unix сокетов с использованием сериализации с помощью склеивания и ручной разборки строк.
Использовался node v4.4.5 и redis 3.0.
Результаты
По оси X описание теста вида WxC, payload=P. Например: 2x4 payload=100 — 2 worker-а, в каждом по 4 потока, размер дополнительных данных каждого сообщения — 100 байт.
Комментарии к результатам и выводы
Между pipe и unix сокетами разницы в производительности практически нет, tcp на 20-40% медленнее.
При использовании лишь одного конкурентного потока redis идет вровень с tcp, но при повышении конкурентности производительность не растет. Возможно все упирается в 1 общий для всех процессов входящий канал. Так или иначе, при выполнении тестов redis потреблял до 50% cpu 1 ядра, поэтому даже при попытках оптимизации на значительный прирост производительности рассчитывать не приходится.
Чем больше 1 сообщение, тем более оправдан отказ от JSON, но для относительно небольших сообщений и нагрузок разница не значительна.
Кстати, попытка использования для сериализации Buffer показала производительность не большую, а местами и меньшую, чем использование просто строк. Судя по всему, Buffer будет оправдан при преимущественно числовых полях в сообщении.
Итого. Если не нужно выжимать максимум производительности, встроенного ChildProcess.send вполне достаточно.
Возможный минус — вся нагрузка ложиться на мастер процесс, доходя до 90% cpu 1 ядра. Сокеты: более сложная реализация, но возможно получить большую производительность и вынести нагрузку на отдельный процесс. Так же возможен доступ из других программ. При использовании tcp сокетов можно выйти за пределы 1 машины. Предположу, что примерно такую максимальную производительность можно выжать при использовании ZeroMQ и тому подобных решений.
Исходные коды тестов доступны тут.
Автор: sh84