Harmonica : [одному из трех мужчин] Ты Фрэнк?Snaky : Фрэнк послал нас.Harmonica : Вы привели для меня коня?Snaky : Кажется… кажется у нас не хватает одного коня.Harmonica : Вы привели на двух коней больше. (Once Upon a Time in the West, 1968) Меня зовут Алексей Новохацкий, я – Software Engineer. Сейчас работаю над архитектурой высоконагруженных систем, провожу технические собеседования, воплощаю в жизнь собственные проекты. Как известно, Node.js хорошо справляется с I/O intensive задачами. А вот для решения CPU bound мы имеем несколько вариантов – child processes/cluster, worker threads. Также есть возможность использовать другой язык программирования (C, C++, Rust, Golang) в качестве отдельного сервиса/микросервиса или через WebAssembly скрипты. В данной обзорной статье будут описаны подходы к использованию Golang в разработке Node.js приложений для запуска некоторых CPU intensive задач (простой суммы чисел, последовательности Фибоначчи, а также для таких хеш-функций как md5 и sha256). 1. Попытаться решить CPU bound задачи только с помощью Node.js 2. Создать отдельный сервис, написанный на Golang и "общаться" с нашим приложением с помощью запросов/брокера сообщений и т.д. (в данной статье будут использованы обычные http запросы) 3. Использовать Golang для создания wasm файла, что позволит использовать дополнительные методы в Node.js Я фан старых добрых спагетти вестернов, особенно Хороший, плохой, злой. В этой статье 3 подходы к решению задач, а в этом фильме 3 совсем разных героя, которые очень хорошо характеризуют эти подходы. Так что… давайте погрузимся в атмосферу тех времен, когда скорость и деньги решали все… Дикий Запад Достоинства: 1. Один и тот же язык (JavaScript) на фронтенде и бэкенде 2. Мастер I/O операции - имеет супербыстрый event loop 3. Самый большой арсенал оружия - npm Достоинства: 1. Разработан в Google 2. Поддерживается почти на всех OS 3. Горутины – специальные функции Golang, которые отрабатывают конкурентно с другими функциями и методами (хорошо справляются с CPU bound задачами) 4. Простой синтаксически – имеет только 25 ключевых слов 1. Доступный везде 2. Дополняет JavaScript 3. Дает возможность писать код на разных языках и использовать .wasm скрипты в JavaScript Немного подробнее о последнем подходе. Код, написанный на Golang может быть преобразован в .wasm файл с помощью нижеприведенной команды, если установить Operating System как “js” и Architecture как “wasm” (список возможных значений GOOS и GOARCH находится здесь): Для запуска скомпилированного кода Go необходимо связать его через специальный интерфейс wasm_exec.js. Содержимое по ссылке: Для использования WebAssembly я применил @assemblyscript/loader и создал модуль nodejs-golang (кстати, @assemblyscript/loader - это единственная зависимость данного модуля). Этот модуль помогает создавать, билдить и запускать отдельные wasm скрипты или функции, которые могут быть использованы в JavaScript коде Кстати, другие языки аналогично могут быть использованы для создания .wasm файла. "Вы не можете сказать, насколько хорош человек или арбуз, пока по нему не постучите." - Roy Bean Для этого мы создадим 2 простых сервера 1. Golang сервер 2. Node.js сервер Мы будем измерять время выполнения каждой задачи – для Golang сервера это будет время непосредственного выполнения функции + сетевая задержка запроса. В то время как для Node.js и WebAssembly - это будет только время выполнения функции. Node.js Golang nodejs-golang Результат: Node.js Golang nodejs-golang Результат: Node.js Golang nodejs-golang Результат: Node.js Golang nodejs-golang Результат: Node.js Golang nodejs-golang Результат: 1. Существует Node.js, который хорошо выполняет свою работу 2. Существует Golang, который хорошо выполняет свою работу 3. Существует WebAssembly (а теперь и мой модуль nodejs-golang), который хорошо выполняет свою работу 4. Golang можно использовать как: самостоятельное приложение, сервис/микросервис, источник для wasm скрипта (который затем можно использовать в JavaScript) 5. Node.js и Golang имеют готовые механизмы использования WebAssembly в JavaScript “Скорость – это хорошо, но точность – это все.” - Wyatt Earp 1. Не запускать CPU-bound задачи с Node.js (если есть возможность) 2. На самом деле лучше не делать никакой задачи, если это возможно 3. Если вам нужно запустить CPU-bound задачу, в Node.js приложении – попробуйте сделать это с помощью Node.js. (будет не так плохо) 4. Между производительностью (с использованием других языков) и удобством чтения (продолжая сохранять только код JavaScript в команде, поддерживающей только JavaScript), лучше выбрать читабельность 5. "Хороший архитектор отталкивается от того, что решение еще не принято, и формирует систему таким образом, что эти решения все еще могут быть изменены или отложены как можно дольше. Хороший архитектор максимизирует количество не принятых решений" - Clean Architecture by Robert C. Martin 6. Лучше "держать отдельно отдельно". Создайте сервис/микросервис для тяжелых вычислений – при необходимости будет легко масштабироваться 7. WebAssembly полезен прежде всего для браузеров. Бинарник Wasm меньше и проще для парсинга, чем код JS и т.д. Спасибо за прочтение. Надеюсь, вам понравилось. Для получения дополнительной информации и возможности проверить результаты, пожалуйста, посетите мой github. Модуль, написанный специально для статьи - nodejs-golang До новых приключений! *Примечание:Благодаря внимательным читателям в тексте было обнаружено несколько неточностей. В частности, исправлен вызов синхронных функций (md5/sha256) с помощью async/await (Node.js). Прошу прощения за допущенную ошибку в написании кода. Результаты замеров проверены - отклонения не имеют значительного влияния на исследование в целом и составляют ±5-10% для вышеупомянутых функций. Полная статистическая оценка не была проведена, так как это выходило за рамки цели данной статьи - показать разницу между использованием Golang для написания отдельного сервиса и для создания wasm файла.Спасибо всем за плодотворное обсуждение. Software Engineer