我是否需要像Java一样同步node.js代码?

时间:2014-03-29 13:52:16

标签: javascript node.js asynchronous

我最近才开始为node.js开发,所以请原谅我这是一个愚蠢的问题 - 我来自Javaland,那里的对象仍然按顺序和同步生活愉快。 ;)

我有一个密钥生成器对象,它使用高低算法的变体发布数据库插入的密钥。这是我的代码:

function KeyGenerator() {
    var nextKey;
    var upperBound;

    this.generateKey = function(table, done) {
        if (nextKey > upperBound) {
            require("../sync/key-series-request").requestKeys(function(err,nextKey,upperBound) {
                if (err) { return done(err); }
                this.nextKey = nextKey;
                this.upperBound = upperBound;
                done(nextKey++);
            });
        } else {
            done(nextKey++);
        }
    }
}

显然,当我要求密钥时,我必须确保它永远不会,永远两次发出相同的密钥。在Java中,如果我想启用并发访问,我会生成这个synchronized

  1. 在node.js中,有没有类似的概念,还是没有必要?我打算使用async.parallel向生成器询问批量插入的一堆密钥。我的期望是,由于节点是单线程的,我不必担心多次发出相同的密钥,有人可以确认这是正确的吗?

  2. 获取新系列涉及异步数据库操作,因此如果我同时执行20个键请求,但系列只剩下两个键,那么我最终会得到18个新系列请求?我该怎么做才能避免这种情况?

  3. 更新 这是requestKeys的代码:

    exports.requestKeys = function (done) {
        var db = require("../storage/db");
        db.query("select next_key, upper_bound from key_generation where type='issue'", function(err,results) {
            if (err) { done(err); } else {
                if (results.length === 0) {
                // Somehow we lost the "issue" row - this should never have happened
                    done (new Error("Could not find 'issue' row in key generation table"));
                } else {
                    var nextKey = results[0].next_key;
                    var upperBound = results[0].upper_bound;
                    db.query("update key_generation set next_key=?, upper_bound=? where type='issue'",
                        [ nextKey + KEY_SERIES_WIDTH, upperBound + KEY_SERIES_WIDTH],
                        function (err,results) {
                            if (err) { done(err); } else {
                                done(null, nextKey, upperBound);
                        }
                    });
                }
            }
        });
    }
    

    更新2

    我应该提一下,即使不需要请求新系列,使用密钥也需要db访问,因为消费的密钥必须标记为在数据库中使用。代码并没有反映这一点,因为在我开始实现该部分之前,我遇到了麻烦。

    更新3

    我想我是通过事件发射得到的:

    function KeyGenerator() {
        var nextKey;
        var upperBound;
        var emitter = new events.EventEmitter();
        var requesting = true;
    
        // Initialize the generator with the stored values
        db.query("select * from key_generation where type='use'", function(err, results)
            if (err) { throw err; }
                if (results.length === 0) {
                    throw new Error("Could not get key generation parameters: Row is missing");
                }
                nextKey = results[0].next_key;
                upperBound = results[0].upper_bound;
                console.log("Setting requesting = false, emitting event");
                requesting = false;
                emitter.emit("KeysAvailable");
        });
    
        this.generateKey = function(table, done) {
            console.log("generateKey, state is:\n    nextKey: " + nextKey + "\n    upperBound:" + upperBound + "\n    requesting:" + requesting + " ");
            if (nextKey > upperBound) {
                if (!requesting) {
                    requesting = true;
                    console.log("Requesting new series");
                    require("../sync/key-series-request").requestSeries(function(err,newNextKey,newUpperBound) {
                        if (err) { return done(err); }
                        console.log("New series available:\n    nextKey: " + newNextKey + "\n    upperBound: " + newUpperBound);
                        nextKey = newNextKey;
                        upperBound = newUpperBound;
                        requesting = false;
                        emitter.emit("KeysAvailable");
                        done(null,nextKey++);
                    });
                } else {
                    console.log("Key request is already underway, deferring");
                    var that = this;
                    emitter.once("KeysAvailable", function() { console.log("Executing deferred call"); that.generateKey(table,done); });
                }
            } else {
                done(null,nextKey++);
            }
        }
    }
    

    我已经记录了日志输出,它确实做了我想要的。

2 个答案:

答案 0 :(得分:1)

正如另一个答案提到的那样,您可能会得到与您想要的结果不同的结果。按顺序排列:

function KeyGenerator() {
    // at first I was thinking you wanted these as 'class' properties
    // and thus would want to proceed them with this. rather than as vars
    // but I think you want them as 'private' members variables of the
    // class instance. That's dandy, you'll just want to do things differently
    // down below
    var nextKey;
    var upperBound;

    this.generateKey = function (table, done) {
        if (nextKey > upperBound) {
            // truncated the require path below for readability.
            // more importantly, renamed parameters to function
            require("key-series-request").requestKeys(function(err,nKey,uBound) {
                if (err) { return done(err); }
                // note that thanks to the miracle of closures, you have access to
                // the nextKey and upperBound variables from the enclosing scope
                // but I needed to rename the parameters or else they would shadow/
                // obscure the variables with the same name.
                nextKey = nKey;
                upperBound = uBound;
                done(nextKey++);
            });
        } else {
            done(nextKey++);
        }
    }
}

关于.requestKeys函数,您需要以某种方式介绍某种同步。这在某种程度上实际上并不可怕,因为只有一个执行线程,您不需要冒出在单个操作中设置信号量的挑战,但处理多个呼叫者是一项挑战,因为您需要其他调用者有效(但不是真的)阻止等待第一次调用requestKeys(),这将调用DB返回。

我需要多考虑一下这部分内容。我有一个基本的解决方案,涉及设置一个简单的信号量和排队回调,但当我输入它时,我意识到我在处理排队的回调时实际上引入了一个更微妙的潜在同步错误。

更新:

当你写关于你的EventEmitter方法时,我刚刚完成了一种方法,这似乎是合理的。请参阅this gist,其中说明了该方法。我拿了。只要运行它,你就会看到行为。它有一些控制台日志记录,可以查看哪些呼叫延迟了新的密钥块,或者可以立即处理。解决方案的主要移动部分是(请注意,keyManager提供了require('key-series-request')的简化实施:

function KeyGenerator(km) {
    this.nextKey = undefined;
    this.upperBound = undefined;
    this.imWorkingOnIt = false;
    this.queuedCallbacks = [];
    this.keyManager = km;


    this.generateKey = function(table, done) {
        if (this.imWorkingOnIt){
            this.queuedCallbacks.push(done);
            console.log('KG deferred call. Pending CBs: '+this.queuedCallbacks.length);
            return;
        };
        var self=this;
        if ((typeof(this.nextKey) ==='undefined') || (this.nextKey > this.upperBound) ){
            //  set a semaphore & add the callback to the queued callback list
            this.imWorkingOnIt = true;
            this.queuedCallbacks.push(done);
            this.keyManager.requestKeys(function(err,nKey,uBound) {
                if (err) { return done(err); }
                self.nextKey = nKey;
                self.upperBound = uBound;
                var theCallbackList = self.queuedCallbacks;
                self.queuedCallbacks = [];
                self.imWorkingOnIt = false;
                theCallbackList.forEach(function(f){
                    // rather than making the final callback directly,
                    // call KeyGenerator.generateKey() with the original
                    // callback
                    setImmediate(function(){self.generateKey(table,f);});

                });

            });
        } else {
            console.log('KG immediate call',self.nextKey);
            var z= self.nextKey++;
            setImmediate(function(){done(z);});
        }
    }
};

答案 1 :(得分:0)

如果用于计算下一个键的Node.js代码不需要执行异步操作,那么您将不会遇到同步问题,因为只有一个JavaScript线程执行代码。对nextKey / upperBound变量的访问将仅由一个线程按顺序完成(即请求1将首先访问,然后请求2,然后请求3等等。)在Java世界中,您将始终需要同步,因为多个线程将是即使您没有进行数据库呼叫,也会执行。

但是,在你的Node.js代码中,因为你正在进行异步调用来获取nextKey,你可能会得到奇怪的结果。仍然只有一个JavaScript线程执行您的代码,但是请求1可能会调用DB,然后Node.js可能接受请求2(而请求1从数据库获取数据)和第二个请求还会向DB请求获取密钥。让我们说请求2从数据库中获取比请求1更快的数据,并使用值100/150更新nextKey / upperBound变量。一旦请求1获取其数据(比如值50/100),它将更新nextKey / upperBound。此方案不会导致重复键,但您可能会在键中看到间隙(例如,并非所有键100到150都将被使用,因为请求1最终将值重置为50/100)

这让我觉得你需要一种方法来同步访问,但我不确定什么是实现这一目标的最佳方法。