К релизу нового корпоративного решения ONLYOFFICE Enterprise Edition мы переписали код серверной части наших онлайн-редакторов на Node.JS и теперь, собственно, хотим поделиться опытом освобождения от ASP.Net, в ловушке которого мы оказались еще пять лет назад.
Переход на Node.JS стал логичным продолжением развития облачного офиса на Linux. Первая версия для него появилась почти год назад — тогда мы приняли решение использовать проект Mono. О проблемах, возникших при портировании на Mono системы для совместной работы, мы уже рассказывали. На тот момент работа над редакторами для Linux'а только начиналась. Сначала вышла бета-версия ONLYOFFICE Document Server, также написанная с использованием Mono. Сейчас она доступна в open source версии 3.0.
В новое серверное решение ONLYOFFICE Enterprise Edition мы включили обновленные редакторы ONLYOFFICE Document Editors 3.5, уже на Node.JS. Почему, как и что получилось расскажем далее.
Мотивы ухода от Mono
1. Снижение числа багов
Использование чужого продукта, пусть и хорошего (сейчас мы говорим о Mono), чревато размножением багов в геометрической прогрессии. Мы запускаем свой код, условно говоря, в «чёрный ящик», в котором с ним происходит почти всё что угодно. Число наших багов умножается на число багов чужого продукта и, как результат, начинается бесконечная борьба за стабильность, которую часто приходится начинать заново с выходом очередных (и внеочередных) обновлений.
2. Кроссплатформенность
Нам был нужен кроссплатформенный код для десктопных редакторов, работающих на всех ОС, в том числе и на мобильных. Совсем недавно вышло приложение ONLYOFFICE Documents для iOS с возможностью редактирования текстов и просмотра таблиц, pdf-файлов и презентаций. Останавливаться на этом мы не планируем, однако о планах позднее.
В общем и целом, мы осознали, что ASP.Net и Mono перестали отвечать нашим потребностям, и задались вопросом: на чем писать дальше?
Почему Node.JS?
Рассмотрев несколько вариантов (среди которых Ruby, Java, Python, Node.JS), мы выбрали последний. Причины выбора достаточно очевидны: у нас уже был неплохой опыт работы с этой платформой — на Node.JS написаны серверные части редакторов: службы совместного редактирования (CoAuthoring) и проверки орфографии (SpellChecker).
В процессе работы мы старались заложить в архитектуру возможности более устойчивой работы, масштабирования и кластеризации. Кроме того, пришлось столкнуться с некоторыми трудностями, связанными с особенностями Node.JS. Об этом расскажем подробнее.
Сложности при переходе на Node.JS
1. Проблема с битовыми операциями
Что было: В JavaScript перед битовыми операциями число преобразуется в 32-bit signed int
Например, у нас был C# код:
Проблема: В Node.JS после цикла переменная buffer становится 2^40 и содержит некорректное число.
Решение в Node.JS: Чтобы избежать «превращений», мы переписали подобные места без битовых операций
2. Проблема со скачиванием файла
Что было: Файлы скачиваются по ссылке при конвертации документов. На C# для скачивания использовался класс System.Net.HttpWebRequest.
Проблема: В Node.JS есть два стандартных модуля для веб-запросов — http и https. Интерфейсы модулей абсолютно одинаковы, и разработчику необходимо вручную выбирать, какой модуль использовать в каждом конкретном случае. При этом стандартные модули не поддерживают автоматический переход по адресу, указанному в заголовке переадресации, если сервер вернул статус 301 или 302.
Решение в Node.JS: Мы использовали дополнительный модуль, который оборачивает стандартные модули и автоматически решает проблему с выбором между http и https и редиректами. В Node.JS есть несколько подобных модулей — из них наш выбор пал на «request».
3. Проблема с обработкой запросов
Что было: В C# для обработки запросов мы создавали класс-наследник от IHttpAsyncHandler или IHttpHandler. Таким образом, вся обработка запросов была «из коробки».
Проблема: В Node.JS нет встроенных модулей для обработки запросов, поэтому её пришлось собирать «под себя».
Решение в Node.JS: Для обработки запросов в Node.JS мы используем модуль «express», а чтобы иметь доступ к телу POST запроса — модуль «body-parser».
Для обработки POST c multipart/form-data мы выбирали из множества модулей с похожей функциональностью. Возможные варианты (multiparty, busboy, formidable) отличаются интерфейсом, настройками, возможностью отключить запись временного файл в temp и поддержкой потоков. Мы выбрали «multiparty» — с его помощью можно напрямую записывать данные как поток в хранилище Amazon S3.
4. Проблема с особенностями языка и стандартных библиотек
Что было: Одним из главных плюсов C# является большая стандартная библиотека и разнообразный синтаксис.
Например, в C# можно передать параметр в функцию по ссылке (ref)
Проблема: Если переписать эту функцию на JavaScript “в лоб”, то строка, переданная в качестве аргумента, будет скопирована. При работе с большими строками это приведет к лишней трате процессорного времени на их копирование и увеличению потребления памяти.
Решение в Node.JS: Чтобы предотвратить копирование больших строк при передаче в функцию, пришлось либо переводить их в бинарный формат (Buffer), либо оборачивать в объект и передавать его.
Кроме того, в Node.JS отсутствуют «из коробки» функции для удаления непустых директорий, создания директорий без родительских поддиректорий, работа с xml и т.д. Их пришлось дописывать руками.
Результаты и планы
Мы получили новый сервер, не использующий ASP.Net, способный выдерживать большие нагрузки. Плюсы для конечного пользователя: он не падает и не висит. Плюсы для нас как разработчиков: он не падает и не висит.
Но, естественно, всё затевалось не только ради серверов на Node.JS — нам нужен был кроссплатформенный код, и мы его получили. Теперь наши редакторы смогут работать на любых ОС.
Сейчас готовятся к выпуску наши десктопные редакторы под Windows, MacOS и Linux (да, работа в браузерах также связана с некоторыми ограничениями, которые мы хотим преодолеть). Кроме того, мы продолжаем улучшать наши онлайн-редакторы. Например, совсем скоро ONLYOFFICE Documents для iOS, о котором мы писали выше, появятся редактор таблиц (сейчас в стадии тестирования) и редактор презентаций (там же). Редакторы под Android выйдут в 2016 году.
Переход на Node.JS стал логичным продолжением развития облачного офиса на Linux. Первая версия для него появилась почти год назад — тогда мы приняли решение использовать проект Mono. О проблемах, возникших при портировании на Mono системы для совместной работы, мы уже рассказывали. На тот момент работа над редакторами для Linux'а только начиналась. Сначала вышла бета-версия ONLYOFFICE Document Server, также написанная с использованием Mono. Сейчас она доступна в open source версии 3.0.
В новое серверное решение ONLYOFFICE Enterprise Edition мы включили обновленные редакторы ONLYOFFICE Document Editors 3.5, уже на Node.JS. Почему, как и что получилось расскажем далее.
Мотивы ухода от Mono
1. Снижение числа багов
Использование чужого продукта, пусть и хорошего (сейчас мы говорим о Mono), чревато размножением багов в геометрической прогрессии. Мы запускаем свой код, условно говоря, в «чёрный ящик», в котором с ним происходит почти всё что угодно. Число наших багов умножается на число багов чужого продукта и, как результат, начинается бесконечная борьба за стабильность, которую часто приходится начинать заново с выходом очередных (и внеочередных) обновлений.
2. Кроссплатформенность
Нам был нужен кроссплатформенный код для десктопных редакторов, работающих на всех ОС, в том числе и на мобильных. Совсем недавно вышло приложение ONLYOFFICE Documents для iOS с возможностью редактирования текстов и просмотра таблиц, pdf-файлов и презентаций. Останавливаться на этом мы не планируем, однако о планах позднее.
В общем и целом, мы осознали, что ASP.Net и Mono перестали отвечать нашим потребностям, и задались вопросом: на чем писать дальше?
Почему Node.JS?
Рассмотрев несколько вариантов (среди которых Ruby, Java, Python, Node.JS), мы выбрали последний. Причины выбора достаточно очевидны: у нас уже был неплохой опыт работы с этой платформой — на Node.JS написаны серверные части редакторов: службы совместного редактирования (CoAuthoring) и проверки орфографии (SpellChecker).
В процессе работы мы старались заложить в архитектуру возможности более устойчивой работы, масштабирования и кластеризации. Кроме того, пришлось столкнуться с некоторыми трудностями, связанными с особенностями Node.JS. Об этом расскажем подробнее.
Сложности при переходе на Node.JS
1. Проблема с битовыми операциями
Что было: В JavaScript перед битовыми операциями число преобразуется в 32-bit signed int
Например, у нас был C# код:
var byteCount = Math.Min(5, data.Length - i);
ulong buffer = 0;
for (var j = 0; j < byteCount; ++j)
{
buffer = (buffer << 8) | data[i + j];
}
Проблема: В Node.JS после цикла переменная buffer становится 2^40 и содержит некорректное число.
Решение в Node.JS: Чтобы избежать «превращений», мы переписали подобные места без битовых операций
buffer = (buffer * 256) + data[i + j];
2. Проблема со скачиванием файла
Что было: Файлы скачиваются по ссылке при конвертации документов. На C# для скачивания использовался класс System.Net.HttpWebRequest.
Проблема: В Node.JS есть два стандартных модуля для веб-запросов — http и https. Интерфейсы модулей абсолютно одинаковы, и разработчику необходимо вручную выбирать, какой модуль использовать в каждом конкретном случае. При этом стандартные модули не поддерживают автоматический переход по адресу, указанному в заголовке переадресации, если сервер вернул статус 301 или 302.
Решение в Node.JS: Мы использовали дополнительный модуль, который оборачивает стандартные модули и автоматически решает проблему с выбором между http и https и редиректами. В Node.JS есть несколько подобных модулей — из них наш выбор пал на «request».
3. Проблема с обработкой запросов
Что было: В C# для обработки запросов мы создавали класс-наследник от IHttpAsyncHandler или IHttpHandler. Таким образом, вся обработка запросов была «из коробки».
Проблема: В Node.JS нет встроенных модулей для обработки запросов, поэтому её пришлось собирать «под себя».
Решение в Node.JS: Для обработки запросов в Node.JS мы используем модуль «express», а чтобы иметь доступ к телу POST запроса — модуль «body-parser».
Для обработки POST c multipart/form-data мы выбирали из множества модулей с похожей функциональностью. Возможные варианты (multiparty, busboy, formidable) отличаются интерфейсом, настройками, возможностью отключить запись временного файл в temp и поддержкой потоков. Мы выбрали «multiparty» — с его помощью можно напрямую записывать данные как поток в хранилище Amazon S3.
4. Проблема с особенностями языка и стандартных библиотек
Что было: Одним из главных плюсов C# является большая стандартная библиотека и разнообразный синтаксис.
Например, в C# можно передать параметр в функцию по ссылке (ref)
private int CreateIndexByOctetAndMovePosition(ref string data, int currentPosition, ref int[] index)
Проблема: Если переписать эту функцию на JavaScript “в лоб”, то строка, переданная в качестве аргумента, будет скопирована. При работе с большими строками это приведет к лишней трате процессорного времени на их копирование и увеличению потребления памяти.
Решение в Node.JS: Чтобы предотвратить копирование больших строк при передаче в функцию, пришлось либо переводить их в бинарный формат (Buffer), либо оборачивать в объект и передавать его.
Кроме того, в Node.JS отсутствуют «из коробки» функции для удаления непустых директорий, создания директорий без родительских поддиректорий, работа с xml и т.д. Их пришлось дописывать руками.
Результаты и планы
Мы получили новый сервер, не использующий ASP.Net, способный выдерживать большие нагрузки. Плюсы для конечного пользователя: он не падает и не висит. Плюсы для нас как разработчиков: он не падает и не висит.
Но, естественно, всё затевалось не только ради серверов на Node.JS — нам нужен был кроссплатформенный код, и мы его получили. Теперь наши редакторы смогут работать на любых ОС.
Сейчас готовятся к выпуску наши десктопные редакторы под Windows, MacOS и Linux (да, работа в браузерах также связана с некоторыми ограничениями, которые мы хотим преодолеть). Кроме того, мы продолжаем улучшать наши онлайн-редакторы. Например, совсем скоро ONLYOFFICE Documents для iOS, о котором мы писали выше, появятся редактор таблиц (сейчас в стадии тестирования) и редактор презентаций (там же). Редакторы под Android выйдут в 2016 году.
Envy
В случае Node.JS вы не запускаете свой код в чёрном ящике? А учитывая уход от статическтой типизации.
Как мне видится, на всех платформах, на которых вы планируете разрабатывать полностью присутствует Mono.
Я так понимаю в угоду моды ваши приложения теперь научнут работать с бОльшим откликом для пользователя. Спасибо.
xkorolx
Любой framework можно отчасти считать «чёрным ящиком». Но тут ситуация такая: изначально код писался под ASP.Net («черный ящик»), его портировали с помощью Mono («черный ящик»). В итоге получаем произведение «черных ящиков».
Да, можно было править баги в самом Mono. Но когда выходит его новая версия и появляются новые?
По поводу статической типизации — хотелось бы, но с другой стороны у нас основная часть редакторов была на JS (клиентская), поэтому привыкли.
impwx
А заюзать typescript для поддержки статической типизации не планируете?
xkorolx
В ближайшее время не планируем.
k12th
Точно так же, как с request и express, все это есть в npm. Почему дописывать руками?
xkorolx
Собственно мы и используем пакеты из npm (mkdirp, например).
Про «дописывать руками» имелось ввиду, что «в коробке» c Node.JS не идет.
k12th
Понятно. Просто неудачно сформулировано, написали бы уж, что третьесторонние модули еще были для того и этого.
Gorniv
Я понимаю, что Вы уже давно это переписывали, но Вы смотрели в сторону ASP.NET 5 (Vnext)?
Там должно быть меньше багов, ведь реализация идет от Microsoft?
Razaz
Там еще и производительность обещают огогошеньки какую https://github.com/aspnet/benchmarks.
xkorolx
Пробовали. На начало перехода все было достаточно сыро.
Мы же не просто так переделывали, но и добавляли необходимый функционал. Например, заложили возможность масштабирования.
Razaz
В смысле заложили?
xkorolx
Например, в предыдущей реализации серверов мы не могли просто взять и поднять ещё один сервер совместного редактирования по необходимости.
Теоретически можно было только распределять по нескольким серверам по определенному правилу, но с существенным ограничением: все пользователи одного документа должны быть на одном сервере.
Ниже в комментарии я приводил схему текущей реализации.
Razaz
Вы стэйт в памяти держали? Ну это и на Asp.Net можно было легко решить :)
xkorolx
Что-то держали в памяти, что-то в базе. Но самое главное, что мы держали большое число соединений (изначально использовали Node.JS на сервере совместного редактирования) и не могли перекидывать сообщения для пользователей одного документа между серверами. Теперь можем, благодаря Rabbit.
И да, сколько одновременных соединений может держать Asp.Net?
Gorniv
А каких конкретно соединениях идет речь?
И почему нельзя использовать Rabbit с ASP.NET так же как Node.JS?
xkorolx
Речь о concurrent connections. Вот, например, статья на эту тему
Я и не говорил, что Rabbit нельзя использовать с ASP.NET.
Я уже писал, что у нас изначально было два сервера:
— совместного редактирования, изначально написанный на Node.JS
— вторая часть, которая конвертировала, собирала, раздавала документы (он был на ASP.NET)
Так вот, мы не просто так транслировали на другой язык, но и добавляли необходимый функционал.
RaveNoX
В моём проекте мы держим порядка 20к на 4-ядерном xeon + HT, 8GB ram, ASP.Net MVC + Signalr
Razaz
Кто мешает попробовать на Asp.Net 5? Opt-In Async/IOCP(Привет неблокирующий IO) и похудевший HttpContext(Раньше был около 30 кб минимум, а сейчас минимум 2кб), плюс хостить можете очень гибко(Тут уже кто-то биндинги для proxygen пилит). Выше уже скидывал бенчмарки. Думаю с релизом кто-нибудь попробует сделать аналог этого коня в вакууме.
С приходом Owin Asp.Net превратился в ту же Ноду на C#. Только шансов, что кто-то психанет, форкнет и оттянет часть ресурсов разработки меньше :) Странно, что вы его не иcпользовали. Могли бы на Nancy сделать бодрый сервер поверх HttpListener.
Это конечно ваш выбор, но аргументы какие-то натянутые в статье. Тот же SO как-то тянет нагрузку вполне небольшим кол-вом серверов.
PQR
Статья хорошая, всё по полочкам, но аргументы не показались мне достаточными, чтобы сменить .NET/Mono на Node.js. Вы жаловались на баги и «чёрный ящик» платформы, но Node.js тоже не идеален — это такой же «чёрный ящик» со своими утечками, багами и однопоточностью.
Плюс вы потеряли статическую типизацию и богатую и удобную библиотеку .NET (о чём сами и написали). Знаю, что статическую типизацию в какой-то степени можно вернуть с помощью TypeScript, а .NET библиотеку заменить отдельными npm пакетами, но используете ли вы TypeScript? И, опять же, на сколько используемые npm пакеты хороши и содержат меньше багов по сравнению с .NET/Mono?
Как отметил Envy в первом комментарии, складывается впечатление о погоне за модой, а не о хладнокровном расчёте.
Из оставшихся упомянутых вариантов:
Ruby — медленно и динамически типизированно;
Java — те же яйца, что и .NET/Mono, только в профиль, хотя, может меньше багов чем в Mono?
Python — медленно и динамически типизированно;
Можно было бы расширить список кросплатформенных языков, которые на слуху:
Go — быстро, статически типизированно, но сыро на мобильных
Rust — быстро, статически типизированно, но сложно и не знаю, работает ли оно на мобильных вообще?
C++ — быстро, статически типизированно, работает везде, но сложно++
На мой вкус, выходит либо Java/Go — если хочется попроще, либо Rust/C++ если команда готова на это.
Ваши конкуренты из Мой Офис потянули и сделали всё кросплатформенно на C++/Qt habrahabr.ru/company/ncloudtech/blog/263719
Интересно было бы обсудить эти или другие варианты — комментируйте!
xkorolx
Да, потеряли типизацию. Но как я уже писал, у нас основная часть редакторов была на JS (клиентская), поэтому привыкли. TypeScript — не используем, пишем на чистом JS.
Наши тестеры могли бы рассказать о том, как они вздохнули после перехода…
Да, рассматривали ваши варианты серверов. Но как я уже писал выше, нам хотелось объединить сервера. Поэтому еще одним необходимым критерием была поддержка большого числа одновременных соединений.
justmara
Автор плачется про "баги" и "чёрный ящик" в контексте проекта, в котором нет ни одного теста. Да что там тестов — комментариев даже нет!
Это хорошо, конечно, что вы такой развёрнутый ответ написали. Вот только не по адресу, боюсь.
Marazmatik
От лица отдела тестирования хочу отметить, что у нас около 30 тысяч тестов, которые лежат в отдельном репозитории. Комменты просто вырезали для публикации
justmara
т.е. вы в опенсорс выкладываете код, но не выкладываете тесты и комментарии? какой-то хреновенький опенсорс получается. вы б ещё обфусцировали перед заливкой на гитхаб.
alemiks
можно было ничего не делать всё это время, а немного подождать, пока ms выкатит production ready своего кроссплатформенного аспнета ) Как раз ваш релиз совпал по времени с их
impwx
Ждем статью «От Node.JS обратно к ASP.NET vNext» :)
leschenko
Уже выпустили. ASP.NET 5 еще в статусе RC, но с пометной о том, что уже можно на продакшен.
RaveNoX
Ага и mvc в нём под Linux не работает
Razaz
А что там не работает? Там вроде на красношапке был какой-то косяк временный.
RaveNoX
github.com/aspnet/Home/issues/1093 проблема с OSX и Linux
По поводу красношапки — она вообще в RC1 не поддерживается, о чём написано в информации по релизу
Razaz
Про нее как раз и читал. Так какая-то фигня с пакетами. А этот ишью уже пофиксили вроде github.com/aspnet/KestrelHttpServer/commit/e4fd91bb68f535801ca8a79aa453ea3fb3f448fe
justmara
Как мы писали-писали, нишмагли, чот взгрустнулось и мы решили переписать на модном.
Аргументы против .net указаны просто смешные. Т.е. реально абсурдные. По некоторым косвенным признакам сложилось впечатление, что в c# у вас творился тот ещё говнокод и вместо вдумчивого рефакторинга вы решили переписать на модном-стильном-молодёжном.
Чтож, увеличение энтропии — тоже похвальное занятие.
xkorolx
Самый главный аргумент — это кроссплатформенность. Да, согласен, портирование простого проекта и Mono потянет. На что-то сложное его не хватает.
Наш C# код открытый, можно посмотреть вот тут — github.com/ONLYOFFICE/DocumentServer
perfectdaemon
Судя по всему, ваши C#-программисты раньше вовсю писали на C/C++. Венгерская нотация, «безопасные» и бесполезные в C# сравнения наоборот: if(true == oReader.Read()).
Тем удивительнее выбор JS.
Sevlyar
Раза 4 перечитал это место, так и не понял где «собака зарыта». В JS строки неизменяемые и передаются в функции по ссылке. При этом никакого копирования самого значения строки не происходит. Для чего их «оборачивать в объект»?
olen
Я тоже на этом моменте затормозил.
> CreateIndexByOctetAndMovePosition(ref string data…
Насколько я понял из статьи, ref используется для быстродействия? Возможно, я сейчас торможу, но, по моему, без ref не будет никакого копирования строки (речь про C#), т.к. строки являются классами. Разница только в том, что в функции с ref можно присвоить параметру другое значение.
olen
А как команда (программисты) приняли идею перехода с C# на JS? Ведь речь идет не про небольшой проект на другой технологии, которым интересно заняться ради общего развития. Тут получается, что переходишь с одной технологии на другую.
P.S. Я так понял, что весь код портировали руками? Или есть какие-то тулзы?
impwx
Тулзы есть, но они подразумевают, что проект продолжает развиваться в виде .net-приложения, а js-версия является портом. Получаемый в результате JS-код очень сложно дебажить и абсолютно не резонно поддерживать.
У меня на одном проекте потребовалось переписать приложение с Silverlight на HTML5, максимально сохранив исходную структуру кода. В итоге я потратил день на написание конвертера с помощью Roslyn, который обрабатывал все описания (классы, поля, сигнатуры методов и т.д.) — тела методов портировались все равно вручную. Это оказалось наиболее разумным компромиссом.
xkorolx
Не просто портировали, а переписывали руками. И это было достаточно волевым решением, несмотря на то, что наша команда и на C#, и на C++, и на JS умеет писать.
Shablonarium
Сколько программистов повесилось в процессе переезда?
xkorolx
Ни одного программиста не пострадало.