NodeJS Эффективное программирование Юра Богданов технический директор и соучредитель Eventr.

Презентация:



Advertisements
Похожие презентации
#html5camp JavaScript на сервере – node.js на Windows Гайдар Руководитель направления веб-технологий, Microsoft.
Advertisements

Tactoom.com изнутри Юра Богданов Техдир и соучредитель Tactoom.com.
Типичный стек технологий для использования с node.js Сергей Широков, Jensen Technologies, 2011.
YUI 3 - модульная архитектура и динамическая загрузка приложения от Yahoo! Пётр Мязин.
Пора ли отправлять С на свалку истории? Пишем демонов на PHP с использованием расширения libevent.
WEB- ТЕХНОЛОГИИ Лекция 5. Традиционное Web- программирование 1.
Что такое Google App Engine Сервис хостинга сайтов и web-приложений в инфраструктуре Google. PaaS Оплата только ресурсов Простота использования, поддержки.
WEB- ТЕХНОЛОГИИ Лекция 1. WEB- ПРИЛОЖЕНИЯ 1 Особый тип программ, построенных по архитектуре « клиент - сервер » Основа получение запросов от пользователя.
Лекция 6 Множественное распараллеливание на Linux кластере с помощью библиотеки MPI 1. Компиляция и запуск программы на кластере. 2. SIMD модель параллельного.
D7 – новая платформа разработки сайтов и порталов Тушинский Юрий Технический директор Битрикс.
Сервер приложений С++ Андрей Шетухин, Илья Космодемьянский SUP Fabrik.
Занятие Language Reflection Language Reflection – способность объектов к рефлексии, то есть умение давать информацию об исключительно языковых свойствах.
1 Учебный курс Основы Web-технологий Лекция 6 CGI и Perl. SSI и Cookies кандидат технических наук Павел Брониславович Храмцов
1 Учебный курс Введение в JavaScript и CGI Лекция 5 Основы CGI кандидат технических наук Павел Брониславович Храмцов
Frontik сервер-агрегатор на python. Зачем frontik? I.
DevCon12 // msdevcon.ru #msdevcon мая, 2012 г. Microsoft.
WebSharper веб-программирование без слёз Владимир Матвеев, IntelliFactory Антон Таяновский, IntelliFactory.
Обеспечение целостности данных Процедурное. Хранимые процедуры Хранимые процедуры пишутся на специальном встроенном языке программирования, они могут.
Сервер приложений С++ Андрей Шетухин Rambler Internet Holding.
Архитектура «D7»: модули, классы, жизненный цикл Кирсанов Алексей Ведущий разработчик 1C-Битрикс.
Транксрипт:

NodeJS Эффективное программирование Юра Богданов технический директор и соучредитель Eventr

NodeJS Цель проекта: «Предоставить естественную неблокирующую, событийно-ориентированную инфраструктуру для написания программ с высокой конкурентностью» (с) Ryan Dahl «to provide a purely evented, non-blocking infrastructure to script highly concurrent programs»

NodeJS NodeJS – серверная JavaScript платформа –Использует Google V8 (Chromium: Google Chrome, Chrome OS, etc.) –Превращает V8 в мощную машину для серверных приложений –Сливается в гармонии с философией JavaScript –Молодой, но живой Event loop - неблокирующий ввод/вывод –Все выполняется параллельно, кроме вашего кода

Для чего подходит NodeJS Много I/O + большая конкурентность –RIA «богатые» приложения –API –Proxy Realtime –Чаты –Онлайн игры –Трансляции –Publish/Subscribe

Event loop Это цикл (libev) Это один процесс, один поток Выполняет одну задачу на один момент времени Ожидает события параллельно (libeio, pooled threads) В каждой итерации последовательно запускает функции-колбэки из трех разных очередей: 1.nextTick функции 2.Таймеры (setTimeout, setInterval) 3.Сигналы ввода/вывода (libeio) Завершает работу, если все очереди пусты

Время CPU – процессорное время –Интерпретация кода –Бизнес-логика приложения, алгоритмы –Рендеринг шаблонов I/O – время ввода/вывода –Запросы в базу данных (network) –Чтение файлов –Чтение кэша I/O CPU

callback I/O CPU занятосвободно Blocking I/O Event Loop монолит А что если во время ожидания I/O заниматься друмиги полезными делами?

I/O Event loop Примерно так выглядит более реальный запрос: CPU+I/O

I/O CPU callback I/O CPU Event loop Примерно так выглядит более реальный запрос:

Event loop Примерно так выглядит более реальный запрос: I/O CPU Свободно для других задач callback I/O CPU

Event Loop mysql.query(SELECT count(*) FROM users, function(err, count) { console.log(There are %d users in db, count); }) console.log(Hello, ); Отправка запроса в БД

Event Loop mysql.query(SELECT count(*) FROM users, function(err, count) { console.log(There are %d users in db, count); }) console.log(Hello, ); Hello, Ожидание ответа БД...

Event Loop mysql.query(SELECT count(*) FROM users, function(err, count) { console.log(There are %d users in db, count); }) console.log(Hello, ); Hello, There are users in db Пришел ответ из БД

Первый запрос Blocking I/O, 1 процесс

Второй запрос, после 10ms ожидает выполнения первого Blocking I/O, 1 процесс

Третий запрос, после 50ms ожидает выполнения первого и второго Blocking I/O, 1 процесс

Event loop, 1 процесс

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 0ms Пришел первый запрос, Запрашиваем I/O, освобождаемся, Ждем других запросов Время: 0ms Пришел первый запрос, Запрашиваем I/O, освобождаемся, Ждем других запросов

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 10ms Пришел второй запрос, Запрашиваем I/O, освобождаемся, Ждем других запросов Время: 10ms Пришел второй запрос, Запрашиваем I/O, освобождаемся, Ждем других запросов

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 50ms Пришел третий запрос, Запрашиваем I/O, освобождаемся, Ждем других запросов Время: 50ms Пришел третий запрос, Запрашиваем I/O, освобождаемся, Ждем других запросов

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 70ms Пришел ответ на I/O первого запроса, запускаем callback1 Время: 70ms Пришел ответ на I/O первого запроса, запускаем callback1

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 80ms Пришел ответ на I/O второго запроса, callback2 ожидает своей очереди Время: 80ms Пришел ответ на I/O второго запроса, callback2 ожидает своей очереди

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 100ms сallback1 завершился, запускаем callback2 Время: 100ms сallback1 завершился, запускаем callback2

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 120ms Пришел ответ на I/O третьего запроса, callback3 ожидает своей очереди Время: 120ms Пришел ответ на I/O третьего запроса, callback3 ожидает своей очереди

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 130ms сallback2 завершился, запускаем callback3 Время: 130ms сallback2 завершился, запускаем callback3

Blocking I/O, 1 процесс Event loop, 1 процесс Время: 160ms callback3 завершился Ждем других запросов Время: 160ms callback3 завершился Ждем других запросов

Время CPU vs I/O RIA трэнд

Приложение my_app.js library / my_module.js node_modules / express sync narrow

Приложение my_app.js library / my_module.js node_modules / express sync narrow require.paths.unshift(./library) var MyModule = require(my_module) var Sync = require(sync)

Приложение my_app.js library / my_module.js node_modules / express sync narrow var MyModule = function() { // … } module.exports = MyModule

Приложение my_app.js library / my_module.js node_modules / express sync narrow require.paths.unshift(./library) var MyModule = require(my_module) var Sync = require(sync) console.log(my module:, MyModule);+

Приложение my_app.js library / my_module.js node_modules / express sync narrow $ node my_app.js my module: function (){}

Приложение my_app.js library / my_module.js node_modules / express sync narrow mongoose $ npm install mongoose

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at Подключаем HTTP модуль

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at Создаем HTTP сервер

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at «вешаем» сервер на 3080 порт

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at Сервер создан, выводим сообщение Сервер создан, выводим сообщение

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at Функция будет вызвана индивидуально для каждого запроса

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at Отправляем HTTP заголовок

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at Отправляем HTTP тело и закрываем сокет

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at $ node my_app.js

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at $ node my_app.js Server running at $ node my_app.js Server running at

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at $ node my_app.js Server running at $ node my_app.js Server running at $ curl

HTTP var http = require(http); http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello, World\n); }).listen(3080) console.log(Server running at $ node my_app.js Server running at $ node my_app.js Server running at $ curl Hello, World $ $ curl Hello, World $

HTTP var http = require(http); http.createServer(function(req, res){ setTimeout(function(){ res.end(World!\n); }, 1000); res.writeHead(200, { Content-Type : text/plain }); res.write(Hello,\n); }).listen(3080) console.log(Server running at Hello сразу, World – через 1 сек

HTTP var http = require(http); http.createServer(function(req, res){ setTimeout(function(){ res.end(World!\n); }, 1000); res.writeHead(200, { Content-Type : text/plain }); res.write(Hello,\n); }).listen(3080) console.log(Server running at $ node my_app.js Server running at $ node my_app.js Server running at $ curl Hello, $ curl Hello,

HTTP var http = require(http); http.createServer(function(req, res){ setTimeout(function(){ res.end(World!\n); }, 1000); res.writeHead(200, { Content-Type : text/plain }); res.write(Hello,\n); }).listen(3080) console.log(Server running at $ node my_app.js Server running at $ node my_app.js Server running at $ curl Hello, World! $ $ curl Hello, World! $ Через секунду

HTTP var http = require(http); var i = 0; http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(i = + i + \n); i++; }).listen(3080) console.log(Server running at Итератор, общий для всех запросов – javascript замыкание (closure)

HTTP var http = require(http); var i = 0; http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(i = + i + \n); i++; }).listen(3080) console.log(Server running at $ node my_app.js Server running at $ node my_app.js Server running at $ curl i = 0 $ $ curl i = 0 $

HTTP var http = require(http); var i = 0; http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(i = + i + \n); i++; }).listen(3080) console.log(Server running at $ node my_app.js Server running at $ node my_app.js Server running at $ curl i = 0 $ curl i = 1 $ $ curl i = 0 $ curl i = 1 $

Callback-driven парадигма Ломает мозг Синтаксический шум С ложно выполнить ряд действий в определенной последовательности Необходимость вручную «пробрасывать» ошибки Бесконечная индентация – aka «спагетти код»

Callback-driven парадигма function asyncFunction(arg1, arg2, argN, callback) { } Неблокирующая функция принимает callback последним аргументом

Callback-driven парадигма function asyncFunction(arg1, arg2, argN, callback) { } function callback(err, result1, result2, resultN) { } Неблокирующая функция принимает callback последним аргументом Callback принимает ошибку первым аргументом, остальные – результат

Callback-driven парадигма function sum(a, b) { if (a > b) { throw new Error('a cannot be greater than b'); } return a + b; }

Callback-driven парадигма function sum(a, b) { if (a > b) { throw new Error('a cannot be greater than b'); } return a + b; } function asyncSum(a, b, callback) { if (a > b) { return callback(new Error('a cannot be greater than b')); } callback(null, a + b); }

Callback-driven парадигма function sum(a, b) { if (a > b) { throw new Error('a cannot be greater than b'); } return a + b; } function asyncSum(a, b, callback) { if (a > b) { return callback(new Error('a cannot be greater than b')); } callback(null, a + b); } throw!

Callback-driven парадигма try { var result = sum(2, 3); console.log('result = %d', result); } catch (err) { console.error(err); }

Callback-driven парадигма try { var result = sum(2, 3); console.log('result = %d', result); } catch (err) { console.error(err); } asyncSum(2, 3, function(err, result){ if (err) return console.error(err); console.log('result = %d', result); })

Callback-driven парадигма function getUser(id, callback) { }

Callback-driven парадигма function getUser(id, callback) { } getUser(1234, function(err, user) { if (err) return console.error(err); console.log(user:, user); })

Callback-driven парадигма function getUser(id, callback) { readConfig(config.json, function(err, config){ if (err) return callback(err); }) }

Callback-driven парадигма function getUser(id, callback) { readConfig(config.json, function(err, config){ if (err) return callback(err); dbConnect(config.host, function(err, db){ if (err) return callback(err); }) }

Callback-driven парадигма function getUser(id, callback) { readConfig(config.json, function(err, config){ if (err) return callback(err); dbConnect(config.host, function(err, db){ if (err) return callback(err); db.getUser(id, function(err, user){ if (err) return callback(err); callback(null, user); }) }

Callback-driven парадигма function getUser(id, callback) { readConfig(config.json, function(err, config){ if (err) return callback(err); afterReadConfig(id, config, callback); }) } function afterReadConfig(id, config, callback) { dbConnect(config.host, function(err, db){ if (err) return callback(err); db.getUser(id, function(err, user){ … }) }

Callback-driven парадигма function getUser(id, callback) { readConfig(config.json, function(err, config){ if (err) return callback(err); afterReadConfig(id, config, callback); }) } function afterReadConfig(id, config, callback) { dbConnect(config.host, function(err, db){ if (err) return callback(err); afterDbConnect(id, db, callback); }) } function afterDbConnect(id, db, callback) … ++++

Callback-driven парадигма Error: User not found at afterDbConnect (/path/to/script.js:24:14) at /path/to/script.js:20:9 at dbConnect (/path/to/script.js:7:5) at afterReadConfig (/path/to/script.js:18:5) at /path/to/script.js:13:9 at readConfig (/path/to/script.js:3:5) at getUser (/path/to/script.js:11:5) at Object. (/path/to/script.js:28:1) at Module._compile (module.js:404:26) at Object..js (module.js:410:10)

Callback-driven парадигма Error: User not found at afterDbConnect (/path/to/script.js:24:14) at /path/to/script.js:20:9 at dbConnect (/path/to/script.js:7:5) at afterReadConfig (/path/to/script.js:18:5) at /path/to/script.js:13:9 at readConfig (/path/to/script.js:3:5) at getUser (/path/to/script.js:11:5) at Object. (/path/to/script.js:28:1) at Module._compile (module.js:404:26) at Object..js (module.js:410:10)

Callback-driven парадигма function getUser(id, callback) { readConfig(config.json, function(err, config){ if (err) return callback(err); afterReadConfig(id, config, callback); }) } function afterReadConfig(id, config, callback) { dbConnect(config.host, function(err, db){ if (err) return callback(err); afterDbConnect(id, db, callback); }) } function afterDbConnect(id, db, callback) … Синтаксический шум – плата за Evented I/O

Callback-driven парадигма function getUser(id, callback) { readConfig(config.json, function(err, config){ if (err) return callback(err); afterReadConfig(id, config, callback); }) } function afterReadConfig(id, config, callback) { dbConnect(config.host, function(err, db){ if (err) return callback(err); afterDbConnect(id, db, callback); }) } function afterDbConnect(id, db, callback) … Синтаксический шум – плата за Evented I/O PROFIT

node-sync Function.prototype.sync = function(context, arguments…) Использует сопрограммы (coroutines) с++ Основан на node-fibers Позволяет писать синхронно на nodejs 0 ctave/node-sync laverdet /node-fibers

var Sync = require(sync); function getUser(id, callback) { Sync(function(){ var config = readConfig.sync(null, config.json); var db = dbConnect.sync(null, config.host); var user = db.getUser.sync(db, id); return user; }, callback) } Запускаем новое «волокно» (Fiber) node-sync

var Sync = require(sync); function getUser(id, callback) { Sync(function(){ var config = readConfig.sync(null, config.json); var db = dbConnect.sync(null, config.host); var user = db.getUser.sync(db, id); return user; }, callback) } «Волокно» вернет значение или ошибку в callback node-sync

var Sync = require(sync); function getUser(id, callback) { Sync(function(){ var config = readConfig.sync(null, config.json); var db = dbConnect.sync(null, config.host); var user = db.getUser.sync(db, id); return user; }, callback) } Функция readConfig вызывается синхронно и возвращает значение node-sync

var Sync = require(sync); function getUser(id) { var config = readConfig.sync(null, config.json); var db = dbConnect.sync(null, config.host); var user = db.getUser.sync(db, id); return user; }.async() То же самое, только проще (коллбэка нет) То же самое, только проще (коллбэка нет) node-sync

var Sync = require(sync); function getUser(id) { var config = readConfig.sync(null, config.json); var db = dbConnect.sync(null, config.host); var user = db.getUser.sync(db, id); return user; }.async() node-sync getUser(1234, function(err, user) { if (err) return console.error(err); console.log(user:, user); })

var Sync = require(sync); function getUser(id) { var config = readConfig.sync(null, config.json); var db = dbConnect.sync(null, config.host); throw new Error(something went wrong); return user; }.async() node-sync getUser(1234, function(err, user) { if (err) return console.error(err); console.log(user:, user); })

var Sync = require(sync); function getUser(id) { var config = readConfig.sync(null, config.json); var db = dbConnect.sync(null, config.host); var user = db.getUser.future(db, id); var friends = db.getUserFriends.future(db, id); return { user : user.result, friends : friends.result }; }.async() node-sync getUser и getUserFriends выполняются параллельно getUser и getUserFriends выполняются параллельно

var Sync = require(sync); function getUser(id) { var config = readConfig.sync(null, config.json); var db = dbConnect.sync(null, config.host); var user = db.getUser.future(db, id); db.getUserFriends(id, friends = new Sync.Future()); return { user : user.result, friends : friends.result }; }.async() node-sync другой способ получения «тикета» future

Callback-driven парадигма $pages = $db->fetchRows(SELECT * FROM pages); foreach ($pages as $page) { $contents = fetchUrl($page->url); }

Callback-driven парадигма $pages = $db->fetchRows(SELECT * FROM pages); foreach ($pages as $page) { $contents = fetchUrl($page->url); } OK

Callback-driven парадигма $pages = $db->fetchRows(SELECT * FROM pages); foreach ($pages as $page) { $contents = fetchUrl($page->url); } db.fetchRows(SELECT * FROM pages, function(err, pages){ pages.forEach(function(page){ fetchUrl(page.url, function(err, contents) { }) }); OK

Callback-driven парадигма $pages = $db->fetchRows(SELECT * FROM pages); foreach ($pages as $page) { $contents = fetchUrl($page->url); } db.fetchRows(SELECT * FROM pages, function(err, pages){ pages.forEach(function(page){ fetchUrl(page.url, function(err, contents) { }) }); x 100,000 OK

Callback-driven парадигма db.fetchRows(SELECT * FROM pages, function(err, pages){ var narrow = new Narrow(10, function(page, callback){ fetchUrl(page.url, function(err, contents) { }) narrow.pushAll(pages); });

Callback-driven парадигма db.fetchRows(SELECT * FROM pages, function(err, pages){ var narrow = new Narrow(10, function(page, callback){ fetchUrl(page.url, function(err, contents) { }) narrow.pushAll(pages); }); x10 - OK x 100,000 - OK

node-sync - использует сопрограммы (coroutines) streamline.js – транслирует код node-async – целый инструментарий для асинхронного программирования Ваша собственная flow-control библиотека :) Callback-driven решения? 0 ctave/node-sync

Масштабирование Nodejs – is just node (c) Ryan Dahl

Масштабирование Nodejs – is just node (c) Ryan Dahl 1 ядро CPU = 1 nodejs процесс

Масштабирование node-cluster Расширяемый Поддержка POSIX сигналов «Горячая» перезагрузка (zero-downtime) «Аккуратное» завершение (graceful shutdown) Автоматом перезапускает мертвые процессы Не оставляет «зомби» Автоматом определяет соличество ядер CPU Поддержка REPL Статистика PID файлы Логи

HTTP Cluster var http = require(http), cluster = require(cluster); var server = http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello from + process.pid + \n); }); cluster(server).listen(3080); console.log(Server at (pid: %d), process.pid); Подключаем модуль cluster

HTTP Cluster var http = require(http), cluster = require(cluster); var server = http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello from + process.pid + \n); }); cluster(server).listen(3080); console.log(Server at (pid: %d), process.pid); Оборачиваем http сервер в кластер и «вешаем» на 3080 порт Оборачиваем http сервер в кластер и «вешаем» на 3080 порт

HTTP Cluster var http = require(http), cluster = require(cluster); var server = http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello from + process.pid + \n); }); cluster(server).listen(3080); console.log(Server at (pid: %d), process.pid); Дополнительно выводим PID

HTTP Cluster var http = require(http), cluster = require(cluster); var server = http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello from + process.pid + \n); }); cluster(server).listen(3080); console.log(Server at (pid: %d), process.pid); $ node my_app.js Server at (pid: 9254) Server at (pid: 9255) Server at (pid: 9256) $ node my_app.js Server at (pid: 9254) Server at (pid: 9255) Server at (pid: 9256)

HTTP Cluster var http = require(http), cluster = require(cluster); var server = http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello from + process.pid + \n); }); cluster(server).listen(3080); console.log(Server at (pid: %d), process.pid); $ node my_app.js Server at (pid: 9254) Server at (pid: 9255) Server at (pid: 9256) $ node my_app.js Server at (pid: 9254) Server at (pid: 9255) Server at (pid: 9256) $ curl Hello from 9255 $ $ curl Hello from 9255 $

HTTP Cluster var http = require(http), cluster = require(cluster); var server = http.createServer(function(req, res){ res.writeHead(200, { Content-Type : text/plain }); res.end(Hello from + process.pid + \n); }); cluster(server).listen(3080); console.log(Server at (pid: %d), process.pid); $ node my_app.js Server at (pid: 9254) Server at (pid: 9255) Server at (pid: 9256) $ node my_app.js Server at (pid: 9254) Server at (pid: 9255) Server at (pid: 9256) $ curl Hello from 9255 $ curl Hello from 9256 $ $ curl Hello from 9255 $ curl Hello from 9256 $

NodeJS + Много I/O Много Запросов + Event Loop = PROFIT

Юра Богданов 0 ctave Github: yuriybogdanov Twitter: bogdanov About.me: in/yuriybogdanov LinkedIn: Спасибо за внимание.