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

我最近才开始为node.js开发,所以如果这是一个愚蠢的问题 – 我来自Javaland,对象仍然顺畅地生活顺序和同步,请原谅我。 ;)

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

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. 获得一个新的系列涉及asynchronous数据库操作,所以如果我做了20个同时的密钥请求,但该系列只剩下两个密钥,我不会最终得到18个新系列的请求吗? 我能做些什么来避免这种情况?

更新这是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

我应该提到,即使不需要请求数据库,也需要使用数据库访问权限,因为所使用的密钥必须标记为在数据库中使用。 代码没有反映出这一点,因为在我开始执行这个部分之前,我遇到了麻烦。

更新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++); } } } 

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

正如另一个答案所提到的,你可能会得到不同于你想要的结果。 把事情整理一下:

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

关于.requestKeysfunction,您需要以某种方式引入某种同步。 这在一个方面并不可怕,因为只有一个执行线程,你不需要在单个操作中设置信号量的挑战,但是处理多个调用者是很有挑战性的,因为你需要其他调用者有效地(但不是真的)阻止等待第一个调用requestKeys() ,这是去数据库返回。

我需要更多地考虑这个部分。 我有一个基本的解决scheme,其中涉及设置一个简单的信号量和排队的callback,但是当我打字时,我意识到我实际上是在处理排队的callback时引入更微妙的潜在同步错误。

更新:

当你写关于你的EventEmitter方法时,我刚刚完成了一个方法,这看起来是合理的。 请参阅说明方法的要点 。 我拿了。 只要运行它,你就会看到行为。 它有一些控制台日志logging来查看哪些调用正在推迟一个新的密钥块或哪些可以立即处理。 解决scheme的主要活动部分是(注意, 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);}); } } }; 

如果您的Node.js代码来计算下一个键不需要执行asynchronous操作,那么您将不会遇到同步问题,因为只有一个JavaScript线程正在执行代码。 访问nextKey / upperBoundvariables将仅由一个线程依次完成(例如,请求1将首先访问,然后请求2,然后请求3等)。在Java世界中,您将始终需要同步,因为多个线程将会即使你没有进行数据库调用也可以执行。

然而,在你的Node.js代码中, 因为你正在进行asynchronous调用来获得nextKey,你可能会得到奇怪的结果 。 仍然只有一个JavaScript线程执行你的代码,但是请求1可能会调用数据库,然后Node.js可能接受请求2(而请求1正在从数据库中获取数据),第二个请求也会向DB请求获得密钥。 假设请求2比请求1更快地从数据库获取数据,并更新值为100/150的nextKey / upperBoundvariables。 一旦请求1获得它的数据(比如值50/100),那么它将更新nextKey / upperBound。 这种情况不会导致重复的密钥,但是您可能会在密钥中看到间隙(例如,并不是所有的密钥都将被使用,因为请求1最终会将值重置为50/100)

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