Javascript 如何使用异步I/O将实时数据集写入磁盘?

Javascript 如何使用异步I/O将实时数据集写入磁盘?,javascript,node.js,asynchronous,raspberry-pi,promise,Javascript,Node.js,Asynchronous,Raspberry Pi,Promise,我不熟悉node.js的开发(虽然在客户端javascript方面比较有经验),在node.js中处理异步操作时,我遇到了很多关于良好实践的问题 我的具体问题(尽管我认为这是一个相当通用的主题)是,我有一个node.js应用程序(运行在Raspberry Pi上),它每10秒将几个温度探测器的读数记录到内存数据结构中。这个很好用。随着时间的推移,数据在内存中积累,当数据积累并达到特定的大小阈值时,数据会定期老化(仅保留最后N天的数据),以防止其增长超过特定的大小。此温度数据用于控制其他一些设备

我不熟悉node.js的开发(虽然在客户端javascript方面比较有经验),在node.js中处理异步操作时,我遇到了很多关于良好实践的问题

我的具体问题(尽管我认为这是一个相当通用的主题)是,我有一个node.js应用程序(运行在Raspberry Pi上),它每10秒将几个温度探测器的读数记录到内存数据结构中。这个很好用。随着时间的推移,数据在内存中积累,当数据积累并达到特定的大小阈值时,数据会定期老化(仅保留最后N天的数据),以防止其增长超过特定的大小。此温度数据用于控制其他一些设备

然后,我有一个单独的时间间隔计时器,它每隔一段时间将这些数据写到磁盘上(如果进程崩溃,则将其持久化)。我正在使用async node.js(
fs.open()
fs.write()
fs.close()
)磁盘IO将数据写入磁盘

而且,由于磁盘IO的异步性质,在我看来,我试图向磁盘写入的数据结构可能在我将其写入磁盘时得到修改。这可能是一件坏事。如果数据只在写入磁盘时被附加到数据结构上,这实际上不会导致我写数据的方式出现问题,但是在某些情况下,当新数据被记录时,更早的数据可以被修改,这将真正混淆我在写入磁盘的过程中的完整性。

我可以想出各种各样的有点难看的保护措施,比如:

  • 切换到同步IO以将数据写入磁盘(出于服务器响应的原因,我真的不想这样做)
  • 在我开始写入数据时设置一个标志,并且在设置该标志时不记录任何新数据(导致我在写入期间丢失数据记录)
  • 选项2的更复杂版本,我在其中设置了标志,当标志被设置时,新数据进入一个单独的临时数据结构,当文件IO完成时,该结构将与真实数据合并(可行,但看起来很难看)
  • 拍摄原始数据的快照副本,并在知道没有其他人会修改该副本的情况下,花时间将该副本写入磁盘。我不想这样做,因为数据集相对较大,并且我处于有限的内存环境中(Raspberry PI)
  • 所以,我的问题是,当其他操作可能希望在异步IO期间修改数据时,使用异步IO编写大型数据集的设计模式是什么?有没有比上面列出的具体解决方法更通用的方法来处理我的问题?

    您的问题是。传统上,这是通过解决的,但是javascript/node实际上没有类似的内置功能

    那么,我们如何在节点中解决这个问题呢我们使用队列。就我个人而言,我使用的是

    队列通过保留需要执行的任务的列表来工作,并且在上一个任务完成后(类似于选项3),仅按照任务添加到队列中的顺序执行这些任务

    注意:异步模块的队列方法实际上可以同时运行多个任务(如上面的动画所示),但是,因为我们这里讨论的是数据同步,所以我们不希望这样。幸运的是,我们可以告诉它一次只运行一个

    在您的特定情况下,您需要做的是设置一个队列,该队列可以执行两种类型的任务:

  • 修改您的数据结构
  • 将数据结构写入磁盘
  • 无论何时从温度探测器获得新数据,都要将任务添加到队列中,以使用新数据修改数据结构。然后,每当间隔计时器启动时,将任务添加到将数据结构写入磁盘的队列中

    由于队列一次只运行一个任务,因此按照任务添加到队列中的顺序,它可以保证在将数据写入磁盘时永远不会修改内存中的数据结构

    一个非常简单的实现可能如下所示:

    var dataQueue = async.queue(function(task, callback) {
        if (task.type === "newData") {
            memoryStore.add(task.data); // modify your data structure however you do it now
            callback(); // let the queue know the task is done; you can pass an error here as usual if needed
        } else if (task.type === "writeData") {
            fs.writeFile(task.filename, JSON.stringify(memoryStore), function(err) {
                // error handling
                callback(err); // let the queue know the task is done
            })
        } else {
            callback(new Error("Unknown Task")); // just in case we get a task we don't know about
        }
    }, 1); // The 1 here is setting the concurrency of the queue so that it will only run one task at a time
    
    // call when you get new probe data
    funcion addNewData(data) {
        dataQueue.push({task: "newData", data: data}, function(err) {
            // called when the task is complete; optional
        });
    }
    
    // write to disk every 5 minutes
    setInterval(function() {
        dataQueue.push({task: "writeData", filename: "somefile.dat"}, function(err) {
            // called when the task is complete; optional
        });
    }, 18000);
    
    还请注意,现在可以将数据异步添加到数据结构中。假设您添加了一个新的探测器,每当事件的值发生变化时,它就会触发事件。您可以像处理现有探测一样
    addNewData(data)
    ,而不必担心它与正在进行的修改或磁盘写入冲突(如果您开始写入数据库而不是内存中的数据存储,那么这一点就很重要了)


    更新:使用

    其思想是使用
    bind()
    将参数绑定到函数,然后将
    bind()
    返回的新绑定函数推送到队列中。这样,您就不需要将某个自定义对象推送到它必须解释的队列中;您只需给它一个要调用的函数,所有设置都已经有了正确的参数。唯一需要注意的是,函数必须将回调作为其最后一个参数

    这应该允许您使用现有的所有函数(可能稍加修改),并在需要确保它们不会并发运行时将它们推送到队列中

    我把这些放在一起测试这个概念:

    var async = require('async');
    
    var dataQueue = async.queue(function(task, callback) {
        // task is just a function that takes a callback; call it
        task(callback); 
    }, 1); // The 1 here is setting the concurrency of the queue so that it will only run one task at a time
    
    function storeData(data, callback) {
        setTimeout(function() { // simulate async op
            console.log('store', data);
            callback(); // let the queue know the task is done
        }, 50);
    }
    
    function writeToDisk(filename, callback) {
        setTimeout(function() { // simulate async op
            console.log('write', filename);
            callback(); // let the queue know the task is done
        }, 250);
    }
    
    // store data every second
    setInterval(function() {
        var data = {date: Date.now()}
        var boundStoreData = storeData.bind(null, data);
        dataQueue.push(boundStoreData, function(err) {
            console.log('store complete', data.date);
        })
    }, 1000)
    
    // write to disk every 2 seconds
    setInterval(function() {
        var filename = Date.now() + ".dat"
        var boundWriteToDisk = writeToDisk.bind(null, filename);
        dataQueue.push(boundWriteToDisk, function(err) {
            console.log('write complete', filename);
        });
    }, 2000);
    

    首先,让我们展示一个实用的解决方案,然后让我们深入了解它的工作原理:

    var chain = Promise.resolve(); // Create a resolved promise
    var fs = Promise.promisifyAll(require("fs"));
    
    chain = chain.then(function(){
        return fs.writeAsync(...); // A
    });
    
    // some time in the future
    chain = chain.then(function(){
        return fs.writeAsync(...); // This will always execute after A is done
    })
    

    因为你已经用承诺来标记你的问题——值得一提的是,承诺能够很好地解决这个(相当复杂的)问题,而且很容易做到

    您的数据同步问题称为问题。有很多方法可以解决JavaScript中的同步问题——这是一本关于这个主题的好书

    输入:承诺 用承诺来解决这个问题的最简单的方法是通过一个承诺把所有的事情都联系起来。我知道你是
    var fsQueue = Promise.resolve(); // start a new chain
    
    // one place
    fsQueue = fsQueue.then(function(){ // assuming promisified fs here
        return fs.writeAsync(...); 
    });
    
    // some other place
    fsQueue = fsQueue.then(function(){
        return fs.writeAsync(...);
    });
    
    var fs = B.promisifyAll(require("fs")); // bluebird promisified fs 
    var syncFs = { // sync stands for synchronized, not synchronous
        queue: B.resolve();
        writeAsync = function(){
            var args = arguments
            return (queue = queue.then( // only execute later
                return fs.writeAsync.apply(fs,arguments);
            });
        } // promisify other used functions similarly
    };
    
    // assumes module is promisified and ignores nested functions
    function synchronize(module){
        var ret = {}, queue = B.resolve();
        for(var fn in module){
            ret[fn] = function(){
                var args = arguments;
                queue = queue.then(function(){
                    return module[fn].apply(module, args); 
                })
            };
        }
        ret.queue = queue; // expose the queue for handling errors
        return ret;
    }