Кластер на основе NodeJS

Материал из support.qbpro.ru

оригинал http://gogoo.ru/blacksmith/blog/show/klaster_na_osnove_nodejs

Как известно, NodeJS работает в одном процессе. Хоть это решение и очень быстрое, но и его может не хватить для того чтобы обработать нагрузку. Попробуем запустить несколько инстанций сервера. Для работы в кластере потребуется различать узлы. Не сильно задумываясь о реализации этой идеи, я решил назвать узлы женскими именами. Очень просто было найти список из одной тысячи английских женских имен. По этому поводу даже возникло несколько шуток: «максимальный размер кластера — тысяча узлов». Каждый узел будет получать данные о своих настройках и других узлах из базы данных mysql. Нас интересует ip-адрес и порт, который будет прослушивать сервер, помимо этого узлы в кластере должны взаимодействовать между собой. Основная задача при взаимодействии между узлами заключается в передаче данных от одного узла другому. Здесь нам потребуется внутренний протокол взаимодействия. Опять же не изобретая велосипед, реализуем взаимодействие между узлами посредством протокола memcached. Это позволит получать данные от узлов кластера, используя общедоступные библиотеки memcached, например из PHP)))). Итак запускаем узел командой:

node index.js name=diana

В серверном скрипте получим имя узла.

var arguments = process.argv.splice(2);
if (arguments.length > 0) {
    var pairs = arguments[0].split('=');
    app.node = '';
    if (pairs[0] == 'node')
        app.node = pairs[1];
}

Ну вот мы знаем имя узла. Пусть в нашем движке все настройки лежат в одной таблице в виде ключ-значение. И ключ — это всего лишь поле объекта. Это javascript и удобно взаимодействовать с настройками как с одним объектом. Например это может выглядеть так: ключ «node.diana.port», значение «8080». Вы уже видите элегантность решения? А вот и его реализация:

/**
 * Configuration container for Externals
 */
app.config = {};
conf_table = 'app_config';
 
app.init_config = function() {
    var checkSql = 'SELECT * FROM ' + conf_table + ';';
    var res = app.conn.querySync(checkSql);
    var row;
 
    while ((row = res.fetchArraySync())) {
        var keys = row[1].split('.');
        var obj = app.config;
        var index = 0;
        for (index = 0; index < keys.length - 1; index++) {
            if (obj[keys[index]] == undefined)
                obj[keys[index]] = {};
            obj = obj[keys[index]];
        }
        obj[keys[index]] = row[2];
    }
};

/**
 * Set Configuration for Externals
 */
app.configure = function(key, value) {
    app.config[key] = value;
    return this;
};

Теперь заставим наш сервер слушать входящие запросы на установленном в настройках порту:

if (app.config.node[app.node] != undefined) {
        if (app.config.node[app.node].port != undefined) {
            var port = app.config.node[app.node].port;
            // Entry point
            sys.puts("PORT: " + port);
            http.createServer(function(req, resp) {
                // Здесь будет находится непосредственно обработка
                // запроса ...
            }).listen(port);
        }
    }

Я использую linux повсеместно, а именно предпочитаю дистрибутив gentoo. Сейчас мы запустим весь этот бардель. Ниже содержимое файла запуска /etc/init.d/nodejs.


#!/sbin/runscript
# Copyright 1999-2006 Gentoo Foundation
WOMANS="aisha emely natali"
 
depend() {
    need net mysql
    provide nodejs
}
 
start() {
    ebegin "Starting ${SVCNAME}"
    for WOMAN in $WOMANS
    do
        echo ${WOMAN}
        PIDFILE=/var/run/${SVCNAME}-${WOMAN}.pid
            LOGFILE=/var/log/nodejs/${SVCNAME}-${WOMAN}.log
            ERRLOGFILE=/var/log/nodejs/${SVCNAME}-${WOMAN}-error.log
        start-stop-daemon --start -q -m -p "${PIDFILE}" -1 ${LOGFILE} -2 ${ERRLOGFILE} -u \
            nginx -d "/var/www/${SVCNAME}" -b -x /usr/bin/node -- index.js node=${WOMAN}
    done
 
    eend $?
}
 
stop() {
    ebegin "Stopping ${SVCNAME}"
    for WOMAN in $WOMANS
    do
        echo ${WOMAN}
        PIDFILE=/var/run/${SVCNAME}-${WOMAN}.pid
            LOGFILE=/var/log/nodejs/${SVCNAME}-${WOMAN}.log
        start-stop-daemon --stop -q -p "${PIDFILE}"
    done
    eend $?
}

Небольшой комментарий к стартовому скрипту. Если вы знакомы с gentoo, то знаете что скрипт сетевого интерфейса является всего лишь ссылкой на /etc/init.d/net.lo. В стартовом скрипте кластера применен такой же подход. Для того чтобы установить каталог исполнения используется выражение «-d /var/www/${SVCNAME}», где ${SVCNAME} является именем стартового скрипта. Вам необходимо создать символьную ссылку на стартовый скрипт. В моем случае это делается командой

ln -s /etc/init.d/nodejs /etc/init.d/gogoo.ru

С помощью нехитрых действий мы создали кластерную систему. Остальное сделает nginx. Но о том, как распределить нагрузку и научить «девушек» разговаривать между собой я напишу позже.