Используйте node.js + redis для хранения среднего времени запроса - PullRequest
2 голосов
/ 29 февраля 2012

Привет, ребята. У меня очень практичный вопрос по поводу использования Redis.Скажем, я хочу сохранить среднее время запроса в Redis со следующим кодом js.В основном я пытаюсь рассчитать среднее время запроса и сохранить для повторения на каждой записи запроса ([req_path, req_time])

var rc=require('redis').createClient()
    ,rc2=require('redis').createClient()
    ,test_data=[
        ['path/1', 100]
        ,['path/2', 200]
        ,['path/1', 50]
        ,['path/1', 70]
        ,['path/3', 400]
        ,['path/2', 150]
    ];

rc.del('reqtime');
rc.del('reqcnt');
rc.del('avgreqtime');

for(var i=0, l=test_data.length; i<l; i++) {
    var item=test_data[i], req_path=item[0], req_time=item[1];
    console.log('debug: iteration # %d, item=%j', i, item);
    rc.zincrby('reqtime', req_time, req_path );
    rc.zincrby('reqcnt', 1, req_path, function(err, c) {
        rc2.zscore('reqtime', req_path, function(err, t) {
            var avg=t/c;
            console.log('req_path='+req_path+',t='+t+',c='+c);
            console.log('debug: added member %s to sorted set "avgreqtime" with score %f', req_path, avg);
            rc2.zadd('avgreqtime', avg, req_path);
        });
    });
}
rc.quit();
rc2.quit();

Но это не работает, как ожидалось для ключа avgreqtime ,Из стандартного вывода я получил

debug: iteration # 0, item=["path/1",100]
debug: iteration # 1, item=["path/2",200]
debug: iteration # 2, item=["path/1",50]
debug: iteration # 3, item=["path/1",70]
debug: iteration # 4, item=["path/3",400]
debug: iteration # 5, item=["path/2",150]
req_path=path/2,t=undefined,c=1
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=1
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=2
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=3
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=1
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=2
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN

Строки отладки внутри функций redis печатаются сразу в конце, а не во время каждой итерации.Я думаю, что это как-то связано с асинхронной природой node.js, но я понятия не имею, как получить эту работу.В качестве эксперимента я также попытался заменить цикл for следующим безуспешно:

for(var i=0, l=test_data.length; i<l; i++) {
    var item=test_data[i], req_path=item[0], req_time=item[1];
    console.log('debug: iteration # %d, item=%j', i, item);
    rc.multi()
        .zincrby('reqtime', req_time, req_path )
        .zincrby('reqcnt', 1, req_path )
        .exec( function(err, replies) {
            console.log('debug(%s): got %j', req_path, replies);
            var avg=replies[0]/replies[1];
            rc2.zadd('avgreqtime', avg, req_path);
        });
}

На этот раз я получил общее время запроса на каждой итерации, но проблема в том, что req_path придерживается 'path / 2', который являетсяпоследний req_path в test_data.В результате в avgreqtime сохраняется только «путь / 2», и это неправильно:

debug: iteration # 0, item=["path/1",100]
debug: iteration # 1, item=["path/2",200]
debug: iteration # 2, item=["path/1",50]
debug: iteration # 3, item=["path/1",70]
debug: iteration # 4, item=["path/3",400]
debug: iteration # 5, item=["path/2",150]
debug(path/2): got ["100","1"]
debug(path/2): got ["200","1"]
debug(path/2): got ["150","2"]
debug(path/2): got ["220","3"]
debug(path/2): got ["400","1"]
debug(path/2): got ["350","2"]

Я использую Redis 2.4.5, а клиент узла redis имеет значение https://github.com/mranney/node_redis

1 Ответ

5 голосов
/ 29 февраля 2012

Вы правы, полагая, что это связано с асинхронной природой узла. Я попробую простой пример здесь:

for(var i = 0; i < 10; i++) {
  someAsyncFunction(i, function(err, data) {
    console.log("executed function for", i);
  });
}

Здесь i будет тем, что вы ожидаете, что это будет первый раз, когда вы обращаетесь к нему (в качестве параметра someAsyncFunction). Внутри обратного вызова этой функции, i всегда будет 10. Цикл for уже завершен к моменту выполнения обратного вызова. Чтобы это исправить, нужно как-то связать i. Одним из способов является анонимная функция, выполняемая немедленно:

for(var i = 0; i < 10; i++) {
  (function(i) {
    someAsyncFunction(i, function(err, data) {
      console.log("executed function for", i);
    });
  })(i); // Execute function with parameter i immediately
}

Теперь i будет привязан к правильному значению даже внутри обратного вызова. Это не оптимально, потому что нам нужно каждый раз указывать новую функцию. Это лучше:

var executeTheFunction = function(i) {
  someAsyncFunction(i, function(err, data) {
    console.log("executed function for", i);
  });
};

for(var i = 0; i < 10; i++) {
  executeTheFunction(i);
}

Обратите внимание, что наш executeTheFunction не принимает обратный вызов. Это означает, что мы не можем реально контролировать выполнение - все вызовы будут выполняться немедленно, что может быть не тем, что мы хотим, если есть много вызовов. В таком случае я рекомендую асинхронный модуль , который облегчает эту работу.

Обновление: Вот пример с async:

var calculateAverage = function(item, callback) {
    var req_path = item[0], req_time = item[1];

    rc.multi()
        .zincrby('reqtime', req_time, req_path )
        .zincrby('reqcnt', 1, req_path )
        .exec( function(err, replies) {
            if(err) return callback(err);
            console.log('debug(%s): got %j', req_path, replies);
            var avg=replies[0]/replies[1];
            rc2.zadd('avgreqtime', avg, req_path, callback);
        });
}

async.map(test_data, calculateAverage, function(err) {
    if(err)
        console.error("Error:", err);
    else
        console.log("Finished");
});

Теперь вы можете легко управлять такими вещами с помощью async.queue и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...