RUVDS.com corporate blog
Website development
JavaScript
Node.JS
Go
12 February

Всегда ли Node.js будет медленнее, чем Golang?

Original author: Alex Hultman
Translation
Возникает такое ощущение, что буквально каждую неделю появляется новый «веб-фреймворк» для Node.js, который называют чем-то таким, что работает быстрее, чем всё, что было до него. Всем известно, что Express — это медленно, но способен ли очередной фреймворк по-настоящему улучшить производительность подсистемы ввода-вывода Node.js? Единственное, что он может — это устранить чрезмерную нагрузку на систему, создаваемую Express. Об улучшении чего-то фундаментального речи не идёт. Собственно говоря, для того, чтобы кардинальным образом улучшить ситуацию, нужно работать на более глубоком уровне, а не добавлять новые абстракции поверх Node.js.

image

Что нужно для того, чтобы на платформе Node.js можно было бы создавать серверные приложения, работающие гораздо быстрее чем всё то, что есть сегодня?

Анализ ситуации


Express — один из старейших веб-фреймворков для Node.js. Он основан на стандартных возможностях этой платформы, давая разработчикам удобный интерфейс, построенный вокруг концепции приложения, и позволяя управлять URL-маршрутами, параметрами, методами и прочим подобным.

Express прост, он помогает программистам быстро разрабатывать приложения. Единственное, чего ему не хватает — это производительность. Постоянно появляющиеся проекты, вроде Fastify, стремятся к тому, чтобы дать разработчикам те же возможности, что и Express, но с меньшими потерями производительности. Но они сами являются тем, что создаёт дополнительную нагрузку на систему и плохо влияют на производительность. Они жёстко ограничены тем, что может дать платформа Node.js. А дать она, в сравнении с конкурентами, может не так уж и много.


Количество HTTP-запросов, обрабатываемых разными серверами в секунду

Обратите внимание на красную линию. Это — максимум платформы Node.js. Фреймворки для неё, независимо от того, есть ли в их названиях слово «fast» или нет, эту линию пересечь не в состоянии. На самом деле это — очень низкий предел производительности, если сравнивать платформу Node.js с её популярными альтернативами наподобие Golang.

К счастью Node.js поддерживает C++-аддоны, биндинги Google V8, которые позволяют связывать JavaScript и C++, и позволяют вызывать из JavaScript любые механизмы, даже в том случае, если эти механизмы предоставляются чем-то, отличающимся от платформы Node.js.

Это делает возможным расширение и улучшение возможностей JavaScript-приложений, позволяет выйти на новые уровни производительности. Это позволяет JavaScript-программам выжать всё возможное из движка Google V8, не ограничиваясь тем, что разработчики Node.js сочли вполне достаточным.

О выходе µWebSockets.js


В начале этого месяца я выпустил новый проект — µWebSockets.js. В качестве хостинга для его кода используется GitHub, а не npm, но установить его для Node.js средствами npm можно так:

npm install uNetworking/uWebSockets.js#v15.0.0

Для работы с µWebSockets.js не нужен компилятор. Поддерживаются Linux, macOS и Windows. Исходной версией системы является 15.0.0, нумерация версий осуществляется по правилам семантического версионирования.

µWebSockets.js — это альтернативный веб-сервер для бэкенд-приложений, написанных на JS. Он состоит из примерно 6 тысяч строк C и C++-кода и значительно обходит по производительности лучшие решения, написанные на Golang. Так, биржа bitfinex.com уже портировала оба своих торговых API (REST и WebSocket) на µWebSockets.js и постепенно вводит их в продакшн. Паоло Ардоино из Bitfinex отмечает, что это — замечательный проект. Мне же хотелось бы сказать, что тому, что у меня появилась возможность выпустить µWebSockets.js, я всецело обязан поддержке, оказанной мне BitMEX, Bitfinex и Coinbase.

Особенности µWebSockets.js


µWebSockets.js — это новый проект, выпущенный под лицензией Apache 2.0, который является продолжением того, что известно как «uws». Данный проект представляет собой полный стек для Google V8, начинающийся на уровне ядра операционной системы, полностью заменяющий стандартные возможности Node.js и представляющий собой стабильную, безопасную, соответствующую стандартам, быструю и легковесную подсистему ввода-вывода для Node.js. Вот как выглядит взаимодействие JS-приложения с операционной системой с применением µWebSockets.js.


Взаимодействие JS-приложения с ОС c применением µWebSockets.js

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

Надо отметить, что слой µSockets сам состоит из трёх подслоёв, представляющих собой механизмы для работы с событиями и с сетью, а также инструменты для защиты данных. Это позволяет, при необходимости, заменять части решения, добавлять в систему альтернативные реализации тех или иных возможностей, не сталкиваясь при этом с необходимостью изменения кода, находящегося на более высоком уровне.

Например, если нужно заменить на что-нибудь OpenSSL, для этого достаточно поменять на то, что нужно, файл ssl.c с его шестью сотнями строк кода. При этом другие слои системы даже не знают о том, что такое SSL. Такой подход, помимо удобства замены одних частей системы на другие, ведёт и к упрощению процесса обнаружения ошибок.


Внутренние подслои µSockets

Представленная здесь архитектура серьёзно отличается от той, монолитной, которая используется в Node.js, где в одном и том же файле исходного кода можно встретить и вызовы libuv, и команды для работы с системой, и обращения к OpenSSL и V8. В Node.js всё это смешано, никто не задавался целью изолировать отдельные части этой платформы. Это значительно усложняет процесс внесения серьёзных изменений в Node.js.

О разработке для µWebSockets.js


Вот чрезвычайно упрощённый и сокращённый пример работы с µWebSockets.js, главная задача которого — продемонстрировать базовые возможности системы.

/* SSL-приложение с маршрутами */
uWS.SSLApp({
    key_file_name: 'misc/key.pem',
    cert_file_name: 'misc/cert.pem',
    passphrase: '1234'
}).get('/hello', (res, req) => {
    /* Очень сильно упрощено */
    res.end('Hello World!');
}).ws('/*', {
    open: (ws, req) => {
        console.log('A WebSocket connected via URL: ' + req.getUrl() + '!');
    },
    message: (ws, message, isBinary) => {
        /* OK имеет значение false при переполнении
         * рекомендовано дождаться сброса буфера */
        let ok = ws.send(message, isBinary);
    },
    drain: (ws) => {
        console.log('WebSocket backpressure: ' + ws.getBufferedAmount());
    },
    close: (ws, code, message) => {
        console.log('WebSocket closed');
    }
}).listen(port, (token) => {
    if (token) {
        console.log('Listening to port ' + port);
    }
});

В определённом смысле, можно говорить о том, что µWebSockets.js с применением SSL может обойти Gorilla WebSocket, реализацию протокола WebSocket на Go, без SSL. То есть оказывается, что JS-код может обмениваться сообщениями с использованием SSL даже быстрее, чем, при определённых условиях, код, написанный на Go без SSL. Полагаю, что это — отличный результат.

Быстрая реализация протокола WebSocket


Socket.IO, во многих отношениях, можно считать эквивалентом Express, работающим в режиме реального времени. Оба эти проекта появились достаточно давно, работать с ними просто, они пользуются популярностью. Но они, кроме прочего, ещё и медленны.


Различные реализации WebSocket

Задачи, которые помогает решить разработчику Socket.IO, сводятся к реализации функционала публикации сообщений и подписки на них, к возможностям отправки и получения сообщений.

При этом надо отметить бесполезность использования неких запасных механизмов для работы с протоколом WebSocket, так как браузеры уже давно поддерживают эту технологию. SSL-трафик не может интерпретироваться корпоративными прокси-серверами, он проходит через них точно так же, как проходит любой HTTP-трафик, в результате использование протокола WebSocket поверх SSL не приводит к блокировке соответствующего трафика. Запасные механизмы для поддержки WebSocket предусмотреть можно, но в их использовании нет никакого смысла. Они лишь неоправданно увеличивают сложность решений.

Одна из целей µWebSockets.js заключается в том, чтобы дать разработчикам возможности, похожие на те, которые есть в Socket.IO, для того, чтобы µWebSockets.js мог бы полностью заменить Socket.IO без необходимости использования каких-либо обёрток более высокого уровня. Это возможно в том случае, если не используется какой-нибудь особенный нестандартный протокол.

Многие компании сталкиваются с проблемами публикации сообщений и подписки на них при работе с WebSocket. Надо отметить, что в описываемом релизе µWebSockets.js этим возможностям не было уделено особенного внимания, но сейчас над ними ведётся серьёзная работа. То, что получится в результате, будет очень быстрым (тесты показывают, что µWebSockets.js уже оказывается быстрее Redis). Поэтому следите за новостями.

Итоги


В настоящее время µWebSockets.js развивается, в проект добавляются новые возможности, исправляются ошибки. Некоторое время уйдёт на то, чтобы избавиться от тех мелких недостатков, которых характерны для первых релизов новых программ. Учитывайте то, что речь идёт о большом проекте, состоящем из многих тысяч строк кода, написанного на C и C++, который хранится в трёх репозиториях. Здесь лежит JavaScript-обёртка — uWebSockets.js. Вот веб-сервер, написанный на C++ — uWebSockets. А вот — базовая библиотека, написанная на C — uSockets.

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

Уважаемые читатели! Планируете ли вы использовать µWebSockets.js в своих проектах?


+53
23k 147
Comments 116