Aplikacje DIRT
w NodeJS
Warsztaty
Artur Trzęsiok
Aleksander Kasiuk
http://slides.com/
arturtrzesiok/
dirtyjs-workshop/live
Plan
- Czym jest aplikacja DIRT?
- Websockety w Node.js
- "Tsunami warning system"
- Asynchroniczność w JavaScript (async)
- [1 przerwa]
- Asynchroniczność w JavaScript (promises)
- Ciekawostki
- [2 przerwa - obiadowa]
- Strumienie
- Fork'owanie
- [3 przerwa]
- Node-gyp
- Podsumowanie
Czym jest aplikacja
DIRT?
DIRT
Data Intensive Real Time
- Czas jest krytyczny
- Stare dane są bezwartościowe
- Nowe dane muszą być przetwarzane tak szybko jak to tylko możliwe
Przykłady aplikacji typu DIRT
- Serwer IM
- Streaming danych finansowych
- Telefonia
- Agregatory danych (monitoring)
- Awionika
Hard real-time
Firm real-time
Soft real-time
Funkcja zysku

Funkcja zysku

Funkcja zysku

Funkcja zysku



Hard real-time
Firm real-time
Soft real-time
Ściąga
- npm init
- npm install [package]
- npm install [package] --save
Websockety w Node.js
Prosta komunikacja
var io = require('socket.io-client');
var client = io.connect('http://localhost:9090');
client.on('random', function (data) {
console.log(data);
});
var io = require('socket.io');
var server = io(9090);
var counter = 0;
server.on('connection', function (socket) {
var clientId = ++counter;
console.log('Client ' + clientId + ' connected');
socket.emit('random', Math.random());
socket.on('disconnect', function () {
console.log('Client ' + clientId + ' disconnected');
});
});
Prosty czat
Serwer
var io = require('socket.io');
var server = io(9090);
server.on('connection', function (socket) {
var name = null;
socket.on('register', function (data) {
console.log('Registered ' + data);
name = data;
});
socket.on('message', function (message) {
console.log('Message received: ' + message);
if (name !== null) {
console.log('Message from ' + name + ' broadcasted');
server.emit('message', {
user: name,
message: message
});
}
});
});
Prosty czat
Klient
var readline = require('readline');
var io = require('socket.io-client');
var client = io.connect('http://localhost:9090');
var username = process.argv[2];
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', function (line) {
// ...
});
// ...
Implementacja klienta czatu
Tsunami warning system v3.1415
Wejście do gry

Zapoznanie się z kodem aplikacji
Asynchroniczność
w JavaScript
Teoria
Klasyczny kod
<?php
$result = query($sql)
doSomething($result);
echo "Next command";
"use strict";
query(sql, function(result) {
doSomething(result);
});
console.log("Next command");
Siła tkwi w tym, że w obrębie JS to jest naturalne
Gdzie szukamy zysku?
- O ile moglibyśmy zmniejszyć czas odpowiedzi na pojedyncze zapytanie zrównoleglając ten kod, który odpytuje zewnętrzne systemy?
- Wciąż obecne podejście mikrooptymalizacji pomimo używania wysokopoziomowych języków
- Kupujemy 100 cykli procesora równocześnie marnując 10 mln obok
Anatomia żądania
Obsłużenie zapytania
Odpytanie bazy danych
Przetworzenie wyniku
Przygotowanie odpowiedzi
Wysłanie
odpowiedzi
Jak to skalować?
Wielowątkowość
Wątek 1
Wątek 2
Wątek 3
Wątek 4
Musimy brać pod uwagę narzut ze zmianą kontekstu oraz zwiększony poziom skomplikowania kodu jaki to generuje
Wiele procesów równolegle
Proces 1
Proces 2
Proces 3
Proces 4
Musimy brać pod uwagę wysoki koszt pamięci
Pętla zdarzeń
Proces 1
System plików
Sieć
Baza danych
Inne
Pula wątków
Kolejka zdarzeń
Pętla zdarzeń
Biblioteka
async
Jak wygląda kod w js?
callback hell!
fs.readdir(source, function(err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function(filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function(err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function(width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
Rozwiązanie
var async = require('async');
async.map(['file1','file2','file3'], fs.stat, function(err, results){
// results is now an array of stats for each file
});
async.filter(['file1','file2','file3'], fs.exists, function(results){
// results now equals an array of the existing files
});
async.parallel([
function(){ ... },
function(){ ... }
], callback);
async.parallelLimit([
function(){ ... },
function(){ ... }
], 4, callback);
async.series([
function(){ ... },
function(){ ... }
]);
Rozwiązanie - praktyka
var async = require('async');
var request = require('request');
var c = 0;
function get(cb) {
request('http://www.onet.pl', function (error, response, data) {
console.log(++c);
cb(error, data);
});
}
var tasks = [];
for (var i = 0; i < 100000; i++) {
tasks.push(get);
}
async.parallelLimit(tasks, 5, function (err, results) {
//results[0]
//results[1]
//results[2]
//...
console.log(results.length);
//output: 100000
});
Rozwiązanie - parallel
async.parallel({
one: function(callback){
setTimeout(function(){
callback(null, 1);
}, 200);
},
two: function(callback){
setTimeout(function(){
callback(null, 2);
}, 100);
}
},
function(err, results) {
// results is now equals to: {one: 1, two: 2}
});
Implementacja async.parallel
Plan
Czym jest aplikacja DIRT?Websockety w Node.js"Tsunami warning system"Asynchroniczność w JavaScript (async)- [1 przerwa]
- Asynchroniczność w JavaScript (promises)
- Ciekawostki
- [2 przerwa - obiadowa]
- Strumienie
- Fork'owanie
- [3 przerwa]
- Node-gyp
- Podsumowanie
Promises
Bluebird
Zanim o promisach - Async.auto
async.auto({
get_data: function(callback){
console.log('in get_data');
callback(null, 'data', 'converted to array');
},
make_folder: function(callback){
console.log('in make_folder');
callback(null, 'folder');
},
write_file: ['get_data', 'make_folder', function(callback, results){
console.log('in write_file', JSON.stringify(results));
callback(null, 'filename');
}],
email_link: ['write_file', function(callback, results){
console.log('in email_link', JSON.stringify(results));
callback(null, {'file':results.write_file, 'email':'user@example.com'});
}]
}, function(err, results) {
console.log('err = ', err);
console.log('results = ', results);
});
Implementacja
async.auto
Promises

Promises - bluebird
var Promise = require('bluebird');
var request = require('request');
var c = 0;
function getUrl(url) {
return new Promise(function (resolve, reject) {
request(url, function (error, response, data) {
console.log(++c);
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
getUrl('http://onet.pl').then(function (data) {
console.log(data);
});
Promises - bluebird
var Promise = require('bluebird');
var request = require('request');
var c = 0;
function getUrl(url) {
return new Promise(function (resolve, reject) {
request(url, function (error, response, data) {
console.log(++c);
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
var getAll = Promise.join(getUrl('http://onet.pl'), getUrl('http://wp.pl'), getUrl('http://interia.pl'),
function (onet, wp, interia) {
return onet + wp + interia;
}
);
getAll.then(function (result) {
console.log("OK", result);
}).error(function (err) {
console.log("ERROR", err);
})
Implementacja
promises
Ciekawostki
Test na spostrzegawczość
function add(x, y) {
// Addition is one of the four elementary
// mathematical operation of arithmetic with the other being subtraction,
// multiplication and division. Addition of two whole numbers is the total
// amount of those quantitiy combined. For example in the picture on the right,
// there's a combination of three apples and two apples together making a total
// of 5 apples. This observation's equivalent to the mathematical expression
// "3 + 2 = 5"
// Besides counting fruit, addition can also represent combining other physical object.
return(x + y);
}
for(var i = 0; i < 500000000; i++) {
if (add(i, i++) < 5) { /* */ }
}
function add(x, y) {
// Addition is one of the four elementary
// mathematical operation of arithmetic with the other being subtraction,
// multiplication and division. Addition of two whole numbers is the total
// amount of those quantitiy combined. For example in the picture on the right,
// there's a combination of three apples and two apples together making total
// of 5 apples. This observation's equivalent to the mathematical expression
// "3 + 2 = 5"
// Besides counting fruit, addition can also represent combining other physical object.
return(x + y);
}
for(var i = 0; i < 500000000; i++) {
if (add(i, i++) < 5) { /* */ }
}
Rozwiązanie?
node --max-inlined-source-size=610 index.js
Pula wątków
UV_THREADPOOL_SIZE=10 node index.js
Obietnica niskopoziomowej ciekawostki
Plan
Czym jest aplikacja DIRT?Websockety w Node.js"Tsunami warning system"Asynchroniczność w JavaScript (async)[1 przerwa]Asynchroniczność w JavaScript (promises)Ciekawostki- [2 przerwa - obiadowa]
- Strumienie
- Fork'owanie
- [3 przerwa]
- Node-gyp
- Podsumowanie
Strumienie
curl http://server/measuringBuoyData
Analiza kodu
Co nam ta wiedza daje?
Analiza kodu
Co nam ta wiedza daje?
var lastBuoyDataResult = '';
for (var i = 0; i < buoyDayaChunkCount; ++i) {
buoyDataChunk = buoyData.substr(i * BUOY_DATA_CHUNK_SIZE, BUOY_DATA_CHUNK_SIZE);
lastBuoyDataResult = calculator.differentiateBuoyData(buoyDataChunk, lastBuoyDataResult);
}
// Notes:
//
// var f = calculator.differentiateBuoyData
// var result = f(....., f(chunk3, f(chunk2, f(chunk1, ""))));
Strumienie w NodeJS
var request = require('request');
var fs = require('fs');
var stream = request('http://cdn.streamonline.biz/video/pl/www_transmisje.mp4');
var file = fs.openfi
stream.on('data', function (chunk) {
console.log(chunk.length);
fs.appendFile('film.mp4', chunk);
});
stream.on('end', function () {
process.exit(-1);
});
stream.on('error', function (error) {
console.log('error');
process.exit(-1);
});
Implementacja
przetwarzania strumienia
function calculateStreamingBuoyData(stream) {
return new Promise(function (resolve, reject) {
var buffer = '';
var buoyDataChunk;
var lastBuoyDataResult = '';
stream.on('data', function (chunk) {
buffer += chunk;
while (buffer.length >= BUOY_DATA_CHUNK_SIZE) {
buoyDataChunk = buffer.substr(0, BUOY_DATA_CHUNK_SIZE);
buffer = buffer.substr(BUOY_DATA_CHUNK_SIZE);
lastBuoyDataResult = calculator.differentiateBuoyData(buoyDataChunk,
lastBuoyDataResult);
}
});
stream.on('end', function () {
resolve(calculator.analyzeBuoyData(lastBuoyDataResult));
});
stream.on('error', function (error) {
reject(error);
});
});
}
Nasze rozwiązanie
Fork
cluster
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}
child_process
spawn
var childProcess = require('child_process'),
var ls = childProcess.spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
ls.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
ls.on('close', function (code) {
console.log('child process exited with code ' + code);
});
child_process
fork
var childProcess = require('child_process');
var child = childProcess.fork('forked', ['param']);
child.on('message', function (data) {
console.log('message: ' + data);
});
child.send('giveData');
process.once('message', function (message) {
if (message === 'giveData') {
process.send(process.argv);
}
});
Implementacja child_process.fork()
Plan
Czym jest aplikacja DIRT?Websockety w Node.js"Tsunami warning system"Asynchroniczność w JavaScript (async)[1 przerwa]Asynchroniczność w JavaScript (promises)Ciekawostki[2 przerwa - obiadowa]StrumienieFork'owanie- [3 przerwa]
- Node-gyp
- Podsumowanie
Node-gyp
Pierwszy natywny moduł
// addon.cc
#include "concat.h"
NAN_MODULE_INIT(InitAll) {
Nan::Set(target, Nan::New("concat").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concat)).ToLocalChecked());
}
NODE_MODULE(addon, InitAll)
// concat.h
#ifndef CONCAT_H
#define CONCAT_H
#include <nan.h>
NAN_METHOD(concat);
#endif
Pierwszy natywny moduł
// concat.cc
#include <string>
#include "concat.h"
NAN_METHOD(concat) {
v8::Local<v8::String> a = info[0].As<v8::String>();
v8::Local<v8::String> b = info[1].As<v8::String>();
std::string nativeA = std::string(*v8::String::Utf8Value(a));
std::string nativeB = std::string(*v8::String::Utf8Value(b));
std::string result = nativeA + nativeB;
info.GetReturnValue().Set(Nan::New(result).ToLocalChecked());
}
binding.gyp
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc", "concat.cc" ],
"include_dirs" : [
"<!(node -e \"require('nan')\")"
]
}
]
}
Test natywnego modułu
var addon = require('./build/Release/addon');
var result = addon.concat('Hello ', 'World!');
console.log(result);
Czas na obiecana ciekawostkę
Nan::AdjustExternalMemory(1000000);
Implementacja MD5 jako natywny moduł
Free For All
Baza wiedzy
Podsumowanie
Dzięki!
DIRTy Application in NodeJs - Workshop
By Artur Trzesiok
DIRTy Application in NodeJs - Workshop
- 1,293