Эволюция сервера приложений на NodeJS

В нашей системе мирно сосуществуют 2 сервера. Основной сервер (ядро), написанный на JAVA и сервер приложений — NodeJS, именно ему и посвящена данная статья.
Изначально у сервера приложений существовало 2 фундаментальные задачи:

1) проксирование запросов к основному серверу для того, чтобы уменьшить неспецифичную нагрузку и сэкономить ресурсы для решения более важных задач;
2) реализация client-specific функционала для того, чтобы не пришлось вносить изменения в код ядра при появлении клиентских «хотелок».

Строго говоря, наличие сервера приложений вовсе не обязательно для функционирования системы, т.к. ядро имеет полноценное REST API, реализующее весь основной функционал системы. Несколько слов о протоколе. RTLSCP (real track location system communication protocol) — протокол, работающий поверх HTTP и позволяющий получать данные и выполнять базовые операции с системой RealTrac с использованием запросов и ответов в формате JSON/KML/PNG.
Базовая часть RTLSCP делится на 3 подмножества:

● RTLSCP REST API (синхронные ресурсы)
● RTLSCP WebSocket API (потоковые данные)
● RTLSCP Asynchronous API (асинхронные команды)

Таким образом, любой желающий может запилить свой сервер приложений или подключаться к ядру напрямую и пользоваться базовыми возможностями системы. Но таких энтузиастов оказалось мало. Поэтому, в качестве четвертого подмножества выступает RTLSCP Ext — расширение протокола, реализуемое сервером приложений со специфичным для клиента функционалом.
Большую часть клиентов интересовала не только информация, поступающая в режиме реального времени, ведь для использования в бизнес процессах довольно часто используются массив данных за определенный промежуток времени, или проще говоря, история. В связи с этим значительную часть функционала RTLSCP Ext составляла именно работа с сохранением и пред/постобработкой данных под нужды конкретного заказчика. Необходимость хранить историю привела к появлению БД на стороне сервера приложений. Время шло, внутренние структуры данных менялись, появлялись новые. Изменялся формат хранения данных, клиентские потребности росли. Ведь всем хочется, чтобы отчеты собирались быстрее и при этом занимали меньше места. Все это приводило к усложнению структуры базы данных, архитектуры сервера и усложнению ее поддержки. Повышение объемов обрабатываемых данных приводило к увеличению нагрузки на сервер, а однопоточность NodeJS приводила к замедлению работы сервера, и никакая асинхронность тут уже не спасала.
На первых порах проблемы производительности решались путем запуска дополнительных воркеров под определенные задачи. Для этого использовался стандартный модуль cluster. Поначалу это помогало и какое-то время все были довольны.
Позже пришло понимание того, что этого нам мало и появилось желание распараллеливать выполнение ряда задач. Но существующая архитектура не позволяла сделать подобное нахаляву. Также, частенько стали возникать ситуации, в которых приходилось заботиться о синхронизации данных между запускаемыми воркерами, да и обмен большими объемами данных проходил не очень гладко… Поддерживать все это разношерстное и весьма громоздкое «добро» было очень лениво накладно.
В связи с этими и некоторыми другими фундаментальными проблемами, не так давно было принято решение изменить архитектуру системы в целом и сервера приложений в частности.
Исходя из сложившейся ситуации к новой архитектуре были предъявлены следующие требования:

1. простота распараллеливания выполняемых задач,
2. модульная архитектура системы.

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

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

Философия или модель акторов предполагает, что все вокруг является актором, что частично схоже в моделью ООП, но предполагается, что последовательность создания акторов или обмена сообщениями между ними не определена.
В процессе своей жизнедеятельности актор, на основании поступивших данных, производит некоторые вычисления и продуцирует новую порцию данных, в этом они больше похожи на «чистые функции», чем на объекты. Основное достоинство акторов ˗ это конечность действий и параллелизм. В идеале актор является сущностью, которая ни от чего не зависит и ничего не знает об окружении, в котором работает.
Обмен данными между акторами происходит посредством обмена сообщениями через общую шину. Сообщения могут быть адресными, для этого актор должен знать реквизиты адресата, или широковещательными.
Архитектура, управляемая событиями, экстремально слабо связана и хорошо распределена. При этом имеет асинхронную природу, поскольку порядок обработки или доставки сообщений никак не регламентируется.

Выводы.
Какие плюсы и минусы в боевых условиях мы получим от этого пока не ясно, поскольку новая архитектура еще не полностью реализована, но уже сейчас очевидно, что код стал чище, прозрачнее и проще. Добавление нового функционала свелось к добавлению пары новых событий и их обработчиков. Сервер стал по-настоящему модульным. Первые нагрузочные тесты также обнадеживают.

PS: Как по мне, микромодульная архитектура является перспективным подходом для реализации проектов на Node.js и не только. Данный подход позволяет писать более простой и прозрачный код. Не стоит также забывать, что использование небольших модулей позволяет упростить процедуру написания тестов и ускорить локализацию ошибок.

© Habrahabr.ru