Статьи

Создайте кластерный сервер ATOM с Node.js

Из документации Node.js : один экземпляр Node выполняется в одном потоке. Чтобы воспользоваться преимуществами многоядерных систем, пользователь иногда захочет запустить кластер процессов Node для обработки нагрузки. Сегодня я покажу вам, как использовать Cluster for Node.js для предоставления простой статической ленты ATOM.

Примечание. В настоящее время кластер (на момент написания этой статьи) помечен как : Стабильность: 1 Экспериментальный — радикальные изменения в будущих версиях . Так что имейте в виду, что изменения в приведенном ниже коде, вероятно, потребуются в будущем. Для ознакомления с тем, куда движется Кластер, вы уже можете просмотреть (будущие) документы, расположенные здесь .

Для начала я позаимствовал некоторый код из документации по кластеру Node.js , этого блога и репозитория GitHub . Затем я изменил и в некоторых случаях исправил код для работы, как ожидалось.

Сначала я изменил ATOM.js отсюда . Имейте в виду, что вы можете добавить еще много всего, чтобы сделать приведенный ниже файл более надежным и поддерживать дополнительные функции ATOM. Кроме того, я изначально использовал NPM для его установки, но столкнулся с несколькими проблемами, поэтому я просто скопировал фактический файл в папку своего проекта.

/*
 Borrowed from: https://github.com/dylang/node-atom/blob/master/lib/atom.js
 Author: https://github.com/dylang
 */
 
var XML = require('xml');
 
function ATOM (options, items) {
    options = options || {};
 
    this.title          = options.title || 'Untitled ATOM Feed';
    this.description    = options.description || '';
    this.feed_url       = options.feed_url;
    this.site_url       = options.site_url;
    this.image_url      = options.image_url;
    this.author         = options.author;
    this.items          = items || [];
 
    this.item = function (options) {
        options = options || {};
        var item = {
            title:          options.title || 'No title',
            description:    options.description || '',
            url:            options.url,
            guid:           options.guid,
            categories:     options.categories || [],
            author:         options.author,
            date:           options.date
        };
 
        this.items.push(item);
        return this;
    };
 
    this.xml = function(indent) {
        return '<?xml version="1.0" encoding="UTF-8"?>\n'
            + XML(generateXML(this), indent);
    }
 
}
 
function ifTruePush(bool, array, data) {
    if (bool) {
        array.push(data);
    }
}
 
function generateXML (data){
 
    var feed =  [
        { _attr: {
            'xmlns':         'http://www.w3.org/2005/Atom',
            'xml:lang':      'en-US'
        } },
        { id:           'urn:uuid:90c76b31-d399-21d2-b93C-0004449e0ca7' },
        { link:         { _attr: { type: 'text/html', rel: 'alternate', href: data.site_url } } },
        { link:         { _attr: { type: 'application/atom+xml', rel: 'self', href: data.feed_url } } },
        { title:        data.title },
        { updated: new Date().toISOString() }
    ];
 
    data.items.forEach(function(item) {
        var entry = [
            { id:        'urn:uuid:77c76b31-d549-21d2-b93C-8829942e4b5f' }
        ];
        ifTruePush(item.date,    entry, { published:    new Date(item.date).toISOString() });
        ifTruePush(item.updated, entry, { updated:      new Date(item.updated).toISOString() });
        ifTruePush(item.link,    entry, { link:         { _attr: { type: 'text/html', rel: 'alternate', href: item.url } } });
        ifTruePush(item.title,   entry, { title:        item.title });
        ifTruePush(item.description, entry, { content:  { _attr: { type: 'xhtml', 'xml:lang': 'en' }, _cdata: item.description } });
        //ifTruePush(item.author || data.author, entry, { 'dc:creator': { _cdata: item.author || data.author } });
        feed.push({ entry: entry });
    });
 
    return { feed: feed };
}
 
module.exports = ATOM;

Опять же, имейте в виду, что это только для примера, который я строю, очевидно, вы не хотели бы иметь жестко закодированные идентификаторы и т. Д.

Убедитесь в том , чтобы сохранить файл как выше atom.js .

Следующим шагом является использование NPM для установки пакета XML (локально для папки моего проекта).

$ npm install xml

Создайте файл в папке вашего проекта с именем app.js и добавьте в него следующий код:

var ATOM = require('./atom');
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
 
var feed = new ATOM({
    title: 'ATOM Feed',
    description: 'This is an ATOM feed',
    feed_url: 'http://localhost:8888/',
    site_url: 'http://localhost:8888/',
    author: 'GiantFlyingSaucer'
});
 
feed.item({
    title:  'ATOM Entry',
    description: 'Hello World',
    url: 'http://localhost:8888/',
    guid: 'urn:uuid:60a76c80-d399-11d2-b93C-0003939e0cf1',
    author: 'Guest Author',
    date: '2012-05-20T21:50:02Z'
});
 
var xml = feed.xml();
var workers = [];
 
var server = require('http').createServer(function (req,res){
    if(req.url == '/favicon.ico') return;
    res.writeHead(200);
    res.end(xml);
    console.log('HTTP request answered by Worker (PID): ' + process.pid);
 
    // Kill the process after a second
    setInterval(function() {
        process.exit(0);
    }, 1000);
});
 
if(cluster.isMaster){
    for (var i = 0; i < numCPUs; i++) {
        var worker = cluster.fork();
        workers.push(worker);
        console.log('Starting Worker (PID): ' + worker.pid);
    }
 
    cluster.on('death', function (worker){
        console.log('Worker (PID) ' + worker.pid + ' has stopped');
        for (var i = 0; i < workers.length; i++) {
            var tmpWorker = workers[i];
            if(worker.pid === tmpWorker.pid) {
                workers.splice(i, 1);
                console.log('Workers array length is now: ' + workers.length);
            }
        }
    });
}else{
    server.listen(8888);
}

Ну, это много кода, так что давайте пройдемся по нему.

Я создаю простой канал ATOM с одной записью:

var feed = new ATOM({
    title: 'ATOM Feed',
    description: 'This is an ATOM feed',
    feed_url: 'http://localhost:8888/',
    site_url: 'http://localhost:8888/',
    author: 'GiantFlyingSaucer'
});
 
feed.item({
    title:  'ATOM Entry',
    description: 'Hello World',
    url: 'http://localhost:8888/',
    guid: 'urn:uuid:60a76c80-d399-11d2-b93C-0003939e0cf1',
    author: 'Guest Author',
    date: '2012-05-20T21:50:02Z'
});

Это было бы минимум в лучшем случае, но достаточно для этого примера. Оттуда я создаю массив для хранения всех рабочих, которые будут запущены при вызове cluster.fork () для каждого процессора. Я работаю на Mac Mini с 4 ядрами, поэтому моя машина раскрутит четырех рабочих. Ваш компьютер будет отличаться в зависимости от количества ядер вашего процессора.

var workers = [];

Здесь происходит настоящее волшебство:

if(cluster.isMaster){
    for (var i = 0; i < numCPUs; i++) {
        var worker = cluster.fork();
        workers.push(worker);
        console.log('Starting Worker (PID): ' + worker.pid);
    }
 
    cluster.on('death', function (worker){
        console.log('Worker (PID) ' + worker.pid + ' has stopped');
        for (var i = 0; i < workers.length; i++) {
            var tmpWorker = workers[i];
            if(worker.pid === tmpWorker.pid) {
                workers.splice(i, 1);
                console.log('Workers array length is now: ' + workers.length);
            }
        }
    });
}else{
    server.listen(8888);
}

Проверка выполняется, чтобы увидеть, являемся ли мы мастером, если это так, то вызывается цикл и запускается в зависимости от того, сколько у меня процессорных ядер. Я храню тех новых работников для последующего использования. Событие «смерть» добавляется в код для захвата любых работников, которые внезапно умирают по любой причине. В этом случае я удаляю их из массива рабочих и записываю некоторую информацию. Если это не мастер, тогда мы просто раскручиваем базовый HTTP-сервер и ждем входящих запросов. При использовании Cluster все работники могут использовать один и тот же порт, в данном случае 8888.

Чтобы сделать это лучшим примером, я добавил код для завершения процесса после того, как он отвечает на запрос HTTP. Вы можете удалить эту часть, но я бы порекомендовал сначала посмотреть, как она работает. Кроме того, вы можете наблюдать за событием «смерти» и просто раскручивать замещающего работника, когда он умирает. В моем сценарии я просто позволил ему умереть.

Время запустить пример:

$ node app.js

Ожидайте результатов, когда я периодически нажимаю обновить по следующему URL: http: // localhost: 8888 /

<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US">
  <id>urn:uuid:90c76b31-d399-21d2-b93C-0004449e0ca7</id>
  <link type="text/html" rel="alternate" href="http://localhost:8888/"/>
  <link type="application/atom+xml" rel="self" href="http://localhost:8888/"/>
  <title>ATOM Feed</title>
  <updated>2012-04-21T16:16:32.759Z</updated>
  <entry>
    <id>urn:uuid:77c76b31-d549-21d2-b93C-8829942e4b5f</id>
    <published>2012-05-20T21:50:02.000Z</published>
    <title>ATOM Entry</title>
    <content type="xhtml" xml:lang="en">
      <![CDATA[ Hello World ]]>
    </content>
  </entry>
</feed>

Выход терминала:

Starting Worker (PID): 311
Starting Worker (PID): 312
Starting Worker (PID): 313
Starting Worker (PID): 314
HTTP request answered by Worker (PID): 314
Worker (PID) 314 has stopped
Workers array length is now: 3
HTTP request answered by Worker (PID): 313
Worker (PID) 313 has stopped
Workers array length is now: 2
HTTP request answered by Worker (PID): 311
Worker (PID) 311 has stopped
Workers array length is now: 1
HTTP request answered by Worker (PID): 312
Worker (PID) 312 has stopped
Workers array length is now: 0

Просто нажмите CTRL-C, чтобы завершить мастер.