【问题标题】:Use node.js+redis to store average request time使用node.js+redis存储平均请求时间
【发布时间】:2012-02-29 17:57:50
【问题描述】:

大家好,我有一个非常实用的 redis 用例问题。假设我想使用以下 js 代码使用 Redis 存储平均请求时间。基本上我正在尝试计算平均请求时间并在每个请求条目时保存到redis( [ 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”,这是 test_data 中的最后一个 req_path。结果只有 'path/2' 被保存到 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]
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

【问题讨论】:

    标签: node.js redis


    【解决方案1】:

    您的猜测是正确的,它与节点的异步特性有关。我将在这里尝试一个简单的示例:

    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 module,它让这件事变得简单。

    更新:这是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 等轻松管理此类内容。

    【讨论】:

    • 像魅力一样工作。感谢您指出这个有趣的模块。
    • 对这个模块上瘾了,我只是用它来重写我几个月前写的(我的一篇 wordpress 帖子)[ricochen.wordpress.com/2011/10/15/… 中的示例。使用异步模块的代码要简单得多。
    猜你喜欢
    • 2020-09-20
    • 2021-03-06
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多