Tactoom.com изнутри Юра Богданов Техдир и соучредитель Tactoom.com
Юра Богданов Техдир и соучредитель Tactoom.com 9 лет в сфере веб-разработки Последние три года работаю над своими проектами Люблю экспериментировать с новыми технологиями Использую NodeJS с самых ранних версий (более 2х лет) на production
Tactoom.com Блоггинг на основе интересов
NodeJS Серверный JavaScript на базе Google V8 Событийный ввод/вывод (Evented I/O) Идеально подходит для realtime приложений
NodeJS для чего подходит лучше всего Много I/O + большая конкурентность RIA «богатые» приложения API Proxy Realtime Чаты Онлайн игры Трансляции Publish/Subscribe, COMET
NodeJS для чего подходит лучше всего Много I/O + большая конкурентность RIA «богатые» приложения API Proxy Realtime Чаты Онлайн игры Трансляции Publish/Subscribe, COMET Блог-платформы :)
Приятные особенности
Общий код для сервера и клиента Общие «виды» (views) Общие модули типа sprintf, crc32 Markdown процессинг на сервере и на клиенте Возможность писать unit-тесты сразу на два фронта Легко работать с одним языком, даже не смотря на то, что принцип работы совсем разный Приятные особенности
ExpressJS web framework Sinatra inspired Легкий Быстрый Гибкий, модульный, расширяемый (connect) Приятные особенности var app = express.createServer(); app.get('/', function(req, res){ res.send('Hello World'); }); app.listen(3000);
COMET Сервис мгновенных сообщений о событиях пользователю Очень лаконичная реализация на NodeJS – спасибо коллегам из Geometria.ru за Beseda Приятные особенности
Неприятные особенности
Callback-driven парадигма Любой ввод/вывод – неблокирующий Следовательно, каждая операция ввод/вывода требует callback, а значит, новый контекст Нельзя использовать try/throw/catch Вместо этого, используется конвенциональное решение для обработки ошибок Следовательно, много служебного шума в вашем коде Спагетти код и необходимость изменения привычного подхода к дизайну Неприятные особенности
node-sync Function.prototype.sync = function(context, arguments…) Использует сопрограммы (coroutines) с++ Основан на node-fibers Позволяет писать на nodejs без коллбэков Позволяет использовать родные try/throw/catch Ввод-вывод можно использовать внутри циклов 0 ctave/node-sync Callback-driven парадигма tactoom
0 ctave/node-sync node-sync, synopsis: fs.readFile(file.txt, function(err, content) { if (err) return console.error(err); }); callback-driven: try { var content = fs.readFile.sync(fs, file.txt); } catch(e) { console.error(e); } node-sync: tactoom
Очень легко налажать JavaScript супер гибкий, что не всегда является преимуществом для серверной технологии Существует много неочевидных нюансов самой технологии, о которых нужно всегда помнить При отсутствии четкой конвенции становится сложно проектировать большое приложение Неприятные особенности
Очень легко налажать JavaScript на front-end писать на много легче: Нет многопользовательсвого аспекта Управление памятью не так важно Меньше операций ввода/вывода «Взрослые» фреймворки и стандарты Неприятные особенности
Плохая отказоустойчивость Один процесс обслуживает множество запросов одновременно Следовательно, ошибка в одном запросе может: привести к потере данных привести к зависанию сотен других запросов нарушить целостность состояния системы Нет гарантированной изоляции стэка для каждого запроса/сессии (как, например, в erlang) Неприятные особенности
Плохая отказоустойчивость В Tactoom проблема частично решена: node-sync повышает надежность благодаря родному try/throw/catch node-sync изолирует каждый запрос в новом «волокне» (fiber) запускаем больше процессов Неприятные особенности
Очень сложно искать утечки памяти Замыкания и долго живущие процессы приводят к неоднозначности процедуры сборки мусора Сложно не допускать утечек памяти, особенно с использованием сторонних библиотек Нет нормальных средств для отладки, тем более на production Неприятные особенности
Очень сложно искать утечки памяти В Tactoom: При хабра/mail эффекте один процесс ест 1ГБ за 10 минут Проблема «решена» написанием плагина к node- cluster, который перезапускает «зажравшиеся» процессы Неприятные особенности
Странное open-source сообщество Каждый делает по своему, поскольку общепринятый стандарт только на стадии зарождения Очень много споров вокруг технологии, и все по своему правы, нет единого (авторитетного) мнения Неприятные особенности
Странное open-source сообщество Ввиду очень стремительного развития технологии, в основном, все плюют на обратную совместимость Для многих NodeJS – не более чем игрушка, по этому большинство модулей не поддерживаются Tactoom: До сих пор работает на node Переход на >= 0.5.x не представляется реальным Неприятные особенности
Заключение Цитируя лозунг создателей NodeJS: «Because nothing blocks, less-than-expert programmers are able to develop fast systems.» «Из-за того, что ничего не блокируется, менее-чем-эксперты могут разрабатывать быстрые системы.» Мой вывод: Для того, чтобы разрабатывать быстрые и надежные системы на NodeJS, нужно быть экспертом. Tactoom: С точки зрения бизнеса, NodeJS как технология, себя не оправдала. Для реализации большинства задач платформы, NodeJS скорее являлась препятствием, нежели эффективным решением.
Другие технологии Redis Key-value store Сервер очередей Кросс-серверный mutex Кэш Publish/Subscribe MongoDB Elasticsearch Быстрый поиск интересов Capistrano Rackspace Erlang/Webmachine Загрузка и обработка аудио
Юра Богданов Github: yuriybogdanov Twitter: About.me: in/yuriybogdanov LinkedIn: Спасибо за внимание. Tactoom: Habrahabr:
Appendix
Особенности NodeJS NodeJS – один процесс, один поток, одно ядро CPU 100% утилизация ядра CPU Нет гонок (race conditions) Почти нет головняка с параллельным доступом к данным Меньше переключений контекста CPU Как масштабировать? Количество процессов = количество ядер CPU node-cluster Меняется парадигма доступа к данным
Масштабирование CPU x4 Node
Масштабирование CPU x4 Node CPU x4 Node1 node-cluster Node2 Node3 Node4
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(3000); Масштабирование 1 процесс: var http = require('http'), cluster = require('cluster'); var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }); cluster(server).listen(3000); node-cluster (много процессов):
Масштабирование > 1 сервера CPU x4 Node1 node-cluster Node2 Node3 Node4
Масштабирование > 1 сервера Сервер1 Node1 Node2 Node3 Node4 node-cluster Сервер2 Node1 Node2 Node3 Node4 node-cluster ?
Масштабирование > 1 сервера Сервер1 Node1 Node2 Node3 Node4 node-cluster Сервер2 Node1 Node2 Node3 Node4 node-cluster (upstream) nginx
Масштабирование > 1 сервера Сервер1 Node1 Node2 Node3 Node4 node-cluster Сервер2 Node1 Node2 Node3 Node4 node-cluster upstream servers { server server1:3000; server server2:3000; } server { location / { proxy_pass }
Декомпозиция Географическая сервера в разных зонах приоритезация зон перераспределение функций по зонам перераспределение функций в зависимости от характеристик серверов Функциональная вынесение ресурсоемких операций на другие сервера («фон»)
Географическая декомпозиция Сервер1 JS node-cluster Сервер2 JS node-cluster DB CDN ФранцияRackspace
Географическая декомпозиция Сервер1 JS node-cluster Сервер2 JS node-cluster DB CDN ФранцияRackspace
Географическая декомпозиция Сервер1 JS node-cluster Сервер2 JS node-cluster DB CDN ФранцияRackspace
Географическая декомпозиция upstream servers { server server1:3000; server server2:3000; } server { location / { proxy_pass }
Географическая декомпозиция upstream main { server server1:3000 weight=10; server server2:3000 weight=1; } upstream fastcdn { server server1:3000 weight=1; server server2:3000 weight=10; } server { location / { proxy_pass } location /upload_avatar { proxy_pass }
Функциональная декомпозиция Сервер1 JS Сервер2 JS «фоновые» операции
Функциональная декомпозиция Сервер1 JS Сервер2 JS Сервер3 JS Q Q Сервер очередей «фоновые» операции
Функциональная декомпозиция Сервер1 JS Сервер2 JS Q Q Сервер очередей Сервер3 JS Сервер4 JS «фоновые» операции
Функциональная декомпозиция Сервер3 JS Redis – Kue – node-cluster – «фоновые» операции
Redis Очереди lists LPUSH RPOP, BRPOP Кэш GET/SET EXPIRE Кросс-серверный mutex Для изоляции участков кода между процессами Publish/Subscribe Для общения между процессами Redis is an open source, advanced key-value store.
MongoDB SchemaLess Less I/O Convenient and flexible ReplicaSets & Automatic failover Native JSON, data-driven queries Easy migrations Atomic operations MongoDB is a scalable, high- performance, open source NoSQL database. Written in C++.
ElasticSearch SchemaLess JSON RESTful Scalable Fast A lot of features RESTful opensource search backend written on top of Apache Lucene.
ElasticSearch nginx СерверX (NodeJS) СерверX (NodeJS) JS ElasticSearch СерверX (NodeJS) JS СерверX (NodeJS) JS MongoDB