Node.js Redis异步问题

时间:2013-04-03 15:58:32

标签: node.js asynchronous redis

我来自PHP背景,试图围绕“事件驱动”的Node.js环境。我编写了一个小脚本,从目录中读取文件,并使用节目,季节和剧集编号的标题更新Redis(如果它们晚于当前存储在数据库中的内容)。看来我遇到了一个异步问题我无法完全理解Redis DB的标题为“我的秀”,季节“05”和标题“01”。正在读取的文件包含“My Show S05E02”和“My Show S05E01”。

数据库应该只在季节/剧集晚于当前季节/剧集时更新,但是由于“updateTitle”被非常快速地调用,并且由于某种原因“My Show S05E02”在“我的节目S05E01”之前传递了更新功能始终将两个值都与原始值“我的显示S05E01”进行比较,因此它会用E02更新Redis,然后再用E01更新!

以下是代码:

function processFiles()
{
fs.readdir(WATCH_DIR, function(err, files){
    for (var i = 0; i <= files.length; i++)
    {
        checkFile(files[i]);
    }
});
}

function updateTitle(title, season, episode)
{
var cur_season, cur_episode;

redis_client.hget(title, 'Season', function(err, data){
    cur_season = data;
    redis_client.hget(title, 'Episode', function(err, data){
        cur_episode = data;
        redis_client.sismember('Titles', title, function(err, data){
            console.log('comparing S'+season+'E'+episode+' to current S'+cur_season+'E'+cur_episode);
            if ((season == cur_season && episode >= cur_episode) || season > cur_season)
            {
                redis_client.hset(title, 'Season', season);
                redis_client.hset(title, 'Episode', episode);
                console.log('setting '+title+' S'+season+'E'+episode);
            }
        });
    });
});
}

function checkFile(file, mtime)
{
var reg         = new RegExp("^"+FILE_PREFIX);
var seasoned    = new RegExp("S(\\d{2})E(\\d{2})", "i");

var cache = {}
if (reg.test(file))
{
    fs.stat(WATCH_DIR + file, function(err, stats){
        console.log(file, stats.mtime.toLocaleDateString() +' '+ stats.mtime.toLocaleTimeString() );
        fs.readFile(WATCH_DIR + file, 'utf8', function(ferr, data){
            if (seasoned.test(data))
            {
                title = data.replace(/S(\d{2})E(\d{2})(.*?)$/, '')
                    .replace(/[\._\-]+/, ' ')
                    .replace(/^\s+/, '')
                    .replace(/\s+$/, '');

                var season = data.match(/S(\d{2})/i);
                season = season[1];
                var episode = data.match(/E(\d{2})/i);
                episode = episode[1];
                updateTitle(title, season, episode);
            }
        });
    });
}
}

fs.watch(WATCH_DIR, function(type, file){
if (type == 'change')
{
    processFiles();
}
});

任何帮助将不胜感激。我确信这里还有其他错误或最佳做法,也可以随意分享这些错误 - 但我只是在试图找出异步问题时碰到了我的头!

仅供参考 - 这只是一个宠物项目,以便我能记住我正在观看的每一集我喜欢看的节目。

3 个答案:

答案 0 :(得分:2)

问题是您在这里没有保证的执行顺序。例如,它可能按此顺序发生:

  1. 阅读文件1
  2. 首先获取Redis的请求
  3. 阅读文件2
  4. 向Redis提出第二次请求
  5. 首先从Redis回来。
  6. 第二次从Redis回来。
  7. 首先执行Redis。
  8. 对Redis执行第二次设置。
  9. 如果第一个操作取决于第二个操作的结果,则需要确保第一个操作在开始第二个操作之前完成,这通常是使用回调完成的。

    考虑一下:

    function doSomethingAsync (num) {
        console.log('Starting something ' + num);
        setTimeout(function () {
            console.log('Done doing something ' + num);
        }, 10);
    }
    
    function runEverything () {
        for (var i = 0; i < 3; i++)
            doSomethingAsync(i);
    }
    
    runEverything();
    

    输出:

    Starting something 0
    Starting something 1
    Starting something 2
    Done doing something 0
    Done doing something 1
    Done doing something 2
    

    但是,如果我们添加一个回调结构并替换runEverything中的循环来使用这些回调,那么它将等待之前的doSomethingAsync完成,然后才开始执行下一个回调:

    function doSomethingAsync (num, callback) {
        console.log('Starting something ' + num);
        setTimeout(function () {
            console.log('Done doing something ' + num);
            callback();
        }, 10);
    }
    
    function runEverything () {
        var i = 0;
        var doneCallback = function () {
            if (++i < 3)
                doSomethingAsync(i, doneCallback);
        };
    
        doSomethingAsync(i, doneCallback);
    }
    

    输出:

    Starting something 0
    Done doing something 0
    Starting something 1
    Done doing something 1
    Starting something 2
    Done doing something 2
    

    欢迎使用Node.js。

答案 1 :(得分:1)

这可能不是解决问题的最佳解决方案,但它可能是最简单。

你是对的 - 它被设置回E01的原因是由于竞争条件。它正试图将剧集设置为02,然后转到下一个文件,即01,但它还没有赶上并完成设置为02,所以它读取01.这是有问题的一行:

if ((season == cur_season && episode >= cur_episode) || season > cur_season)

如您所见,您正在检查是否episode >= cur_episode。将其更改为episode > cur_episode,您应该至少解决在这种特定情况下倒退的剧集。如果当前剧集为01,它可能仍然会遇到问题,它会在03中写入,然后是02,并且在写入03之前02会覆盖03。您可以尝试阅读所有更改的剧集,然后检查它们并确定哪个是最后一个应用程序,而不是重复写入和读取redis。

你真的想要解决竞争条件,它可以解决一般情况并使你成为一个更强大的Node程序员,但是你会遇到各种不同的方法来解决这个问题(延迟,回调,承诺)等等......)远远超出了本次讨论的范围。我建议你read upcontinue learning祝你好运!

答案 2 :(得分:1)

未提及的解决方案是使用async库。使用此模块,您可以轻松地序列化所有“checkFile”调用。您将有效地将所有调用添加到队列中,每个调用在执行前等待前一个调用完成。虽然此解决方案可能运行速度稍慢,但您最终不应该遇到任何您遇到的控制流问题。