【问题标题】:Node.js Redis Async IssuesNode.js Redis 异步问题
【发布时间】:2013-03-25 09:46:46
【问题描述】:

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

只有当季节/剧集晚于当前季节/剧集时,数据库才应该更新,但是由于“updateTitle”的调用速度非常快,并且由于某种原因,“My Show S05E02”在“My Show S05E01”之前传递更新函数总是将这两个值与原始值“My Show 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();
}
});

任何帮助将不胜感激。我确信这里还有其他错误或最佳实践,也请随时分享这些 - 但我只是想弄清楚异步问题!

仅供参考 - 这只是一个宠物项目,这样我就可以记住我目前在观看我喜欢看的每个节目的哪一集。

【问题讨论】:

    标签: node.js asynchronous redis


    【解决方案1】:

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

    1. 读取文件 1
    2. 首先向 Redis 发出 get 请求
    3. 读取文件 2
    4. 向 Redis 发出第二次获取请求
    5. 首先从 Redis 中获取返回值。
    6. 第二次从 Redis 中获取回报。
    7. 对 Redis 执行第一次设置。
    8. 对 Redis 执行第二组操作。

    如果第一个操作取决于第二个操作的结果,您需要确保第一个操作在开始第二个操作之前完成,这通常使用回调来完成。

    考虑一下:

    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。

    【讨论】:

    • 虽然这个逻辑看起来很有效,但我仍然很难将它应用到我的代码中。也许我只需要重构比我尝试的更多的东西。
    • processFiles 基本上是您的runEverything 版本。它需要将回调函数传递给checkFileseasoned.test(data) === false 将直接调用它,或者将回调传递给updateTitleupdateTitle 将在更新完成时调用回调。在调用回调之前不要开始处理下一个文件。
    • 谢谢布雷特。我得到了它与那一点信息一起工作。只是想知道 - 这会变成“阻塞”代码吗?
    • 不,它仍然是非阻塞的,因为当您的代码等待来自 Redis 的响应时,其他代码可能会在 Node.js 事件循环中运行。您绝对应该阅读事件循环,特别是 Node.js 事件循环,如果这令人困惑的话。
    【解决方案2】:

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

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

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

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

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

    【讨论】:

    • 我想过在应用程序中检查它们并选择最新的“updateTitle”,但由于 fs.readdir 和 fs.readfile 也是异步的,我想我只是在创建一个“竞争条件”在应用程序的其他地方。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-04
      • 2015-02-19
      • 1970-01-01
      • 2015-12-01
      • 1970-01-01
      • 2013-11-09
      • 1970-01-01
      • 2011-10-18
      相关资源
      最近更新 更多