Aplikacje DIRT 
w NodeJS

Warsztaty

 

 

Artur Trzęsiok

Aleksander Kasiuk

http://slides.com/
arturtrzesiok/
dirtyjs-workshop/live

Plan

  1. Czym jest aplikacja DIRT?
  2. Websockety w Node.js
  3. "Tsunami warning system"
  4. Asynchroniczność  w JavaScript (async)
  5. [1 przerwa]
  6. Asynchroniczność  w JavaScript (promises)
  7. Ciekawostki
  8. [2 przerwa - obiadowa]
  9. Strumienie
  10. Fork'owanie
  11. [3 przerwa]
  12. Node-gyp
  13. Podsumowanie

Czym jest aplikacja
DIRT?

DIRT

Data Intensive Real Time

 

  1. Czas jest krytyczny
  2. Stare dane są bezwartościowe
  3. Nowe dane muszą być przetwarzane tak szybko jak to tylko możliwe

Przykłady aplikacji typu DIRT

  1. Serwer IM
  2. Twitter
  3. Streaming danych finansowych
  4. Telefonia
  5. Agregatory danych (monitoring)
  6. 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

  1. Czym jest aplikacja DIRT?
  2. Websockety w Node.js
  3. "Tsunami warning system"
  4. Asynchroniczność  w JavaScript (async)
  5. [1 przerwa]
  6. Asynchroniczność  w JavaScript (promises)
  7. Ciekawostki
  8. [2 przerwa - obiadowa]
  9. Strumienie
  10. Fork'owanie
  11. [3 przerwa]
  12. Node-gyp
  13. 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

  1. Czym jest aplikacja DIRT?
  2. Websockety w Node.js
  3. "Tsunami warning system"
  4. Asynchroniczność  w JavaScript (async)
  5. [1 przerwa]
  6. Asynchroniczność  w JavaScript (promises)
  7. Ciekawostki
  8. [2 przerwa - obiadowa]
  9. Strumienie
  10. Fork'owanie
  11. [3 przerwa]
  12. Node-gyp
  13. 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

  1. Czym jest aplikacja DIRT?
  2. Websockety w Node.js
  3. "Tsunami warning system"
  4. Asynchroniczność  w JavaScript (async)
  5. [1 przerwa]
  6. Asynchroniczność  w JavaScript (promises)
  7. Ciekawostki
  8. [2 przerwa - obiadowa]
  9. Strumienie
  10. Fork'owanie
  11. [3 przerwa]
  12. Node-gyp
  13. 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,243