2012-02-29 2 views
2

Salut les gens J'ai une question d'utilisation très pratique. Dites que je veux stocker l'heure moyenne de demande avec Redis avec le code js suivant. Fondamentalement, je suis en train de calculer le temps de demande moyenne et enregistrez Redis à chaque entrée de demande ([req_path, REQ_TIME])Utilisez node.js + redis pour stocker l'heure de la demande moyenne

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(); 

Mais il ne fonctionne pas comme prévu pour la avgreqtime clé. De stdout je suis arrivé

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 

Les lignes de débogage dans les fonctions du redis sont imprimées à la fois à la fin au lieu de chaque itération. Je pense que cela a quelque chose à voir avec la nature asynchrone de node.js mais je n'ai aucune idée de comment faire pour que cela fonctionne. Comme une expérience que je aussi essayé de remplacer la boucle avec les éléments suivants, sans succès:

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); 
     }); 
} 

Je suis arrivé le temps de la demande totale à chaque itération cette fois mais le problème est bâtons req_path avec « chemin/2 » qui est la dernière req_path dans test_data. En conséquence seulement « chemin/2 » est enregistré à avgreqtime et il est faux:

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"] 

J'utilise Redis 2.4.5 et le noeud Redis client est de https://github.com/mranney/node_redis

Répondre

5

Vous avez raison dans votre hypothèse que cela a à voir avec la nature asynchrone du nœud. Je vais essayer ici un exemple simple:

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

Ici, i sera ce que vous attendez que ce soit la première fois que vous faites référence (comme un paramètre à someAsyncFunction). À l'intérieur le rappel à cette fonction, i sera toujours 10. La boucle for est déjà terminée au moment où le rappel est exécuté. Pour résoudre ce problème, vous devez lieri en quelque sorte. Une façon est une fonction anonyme, immédiatement exécuté:

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 
} 

Maintenant, i sera lié à la valeur correcte à l'intérieur même du rappel. Ce n'est pas optimal, car nous devons spécifier une nouvelle fonction à chaque fois. Cela vaut mieux:

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

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

Notez que notre executeTheFunction ne prend pas un rappel. Cela signifie que nous ne pouvons pas vraiment contrôler l'exécution - tous les appels seront exécutés immédiatement, ce qui peut ne pas être ce que nous voulons s'il y a beaucoup d'appels. Dans un tel cas, je recommande le async module, ce qui rend ces choses faciles.

Mise à jour: Voici un exemple avec 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"); 
}); 

Maintenant, vous pouvez facilement gérer ce genre de choses avec async.queue etc.

+1

fonctionne comme un charme. Merci d'avoir signalé ce module intéressant. – ricochen

+1

Obtenir accro à ce module, je l'ai juste utilisé pour réécrire l'exemple dans (un de mes messages wordpress) [http://ricochen.wordpress.com/2011/10/15/node-js-example-2-parallel- traitement /] que j'ai écrit il y a quelques mois. Le code utilisant le module async est beaucoup plus simple. – ricochen

Questions connexes