过去 1 周我一直面临这个问题,我对此感到困惑。 保持简短并简单地解释问题。
我们有一个内存模型,用于存储预算等值。现在,当调用 API 时,它会产生与之关联的支出。
然后我们检查内存模型并将支出添加到现有支出中,然后检查预算,如果超出,我们将不再接受该模型的任何点击。对于每个调用,我们还会更新数据库,但这是一个异步操作。
一个简短的例子
api.get('/clk/:spent/:id', function(req, res) {
checkbudget(spent, id);
}
checkbudget(spent, id){
var obj = in memory model[id]
obj.spent+= spent;
obj.spent > obj.budjet // if greater.
obj.status = 11 // 11 is the stopped status
update db and rebuild model.
}
这曾经工作得很好,但现在随着并发请求,我们得到错误的支出,支出增加超过预算,并且在一段时间后停止。我们用jmeter模拟调用,发现了这一点。
据我们所知,节点是异步的,因此当状态更新为 11 时,许多线程已经更新了活动的支出。
如何为 Node.js 提供信号量类型的逻辑,以便可变预算与模型同步
更新
db.addSpend(campaignId, spent, function(err, data) {
campaign.spent += spent;
var totalSpent = (+camp.spent) + (+camp.cpb);
if (totalSpent > camp.budget) {
logger.info('Stopping it..');
camp.status = 11; // in-memory stop
var History = [];
History.push(some data);
db.stopCamp(campId, function(err, data) {
if (err) {
logger.error('Error while stopping );
}
model.campMAP = buildCatMap(model);
model.campKeyMap = buildKeyMap(model);
db.campEventHistory(cpcHistory, false, function(err) {
if (err) {
logger.error(Error);
}
})
});
}
});
代码的 GIST 现在有人可以帮忙吗
问:
semaphore
中是否有NodeJs
或同等内容?
答:没有。
问: 那么
NodeJs
用户如何处理竞争条件?
A: 理论上你不必这么做,因为
thread
中没有 javascript
。
在深入研究我提出的解决方案之前,我认为您了解
NodeJs
的工作原理非常重要。
对于
NodeJs
,它是由基于事件的架构驱动的。这意味着在 Node
进程中有一个事件队列,其中包含所有“待办事项”事件。
当
event
从队列中获取pop
时,node
将执行all所需的代码,直到完成。运行期间发出的任何 async
调用都会像其他 events
一样生成,并且它们会在 event queue
中排队,直到听到响应,然后再次运行它们。
问: 那么我该怎么做才能确保一次只有 1 个请求可以对数据库执行
updates
?
A: 我相信有很多方法可以实现这一目标,但更简单的方法之一是使用
set_timeout
API。
示例:
api.get('/clk/:spent/:id', function(req, res) {
var data = {
id: id
spending: spent
}
canProceed(data, /*functions to exec after canProceed=*/ checkbudget);
}
var canProceed = function(data, next) {
var model = in memory model[id];
if (model.is_updating) {
set_timeout(isUpdating(data, next), /*try again in=*/1000/*milliseconds*/);
}
else {
// lock is released. Proceed.
next(data.spending, data.id)
}
}
checkbudget(spent, id){
var obj = in memory model[id]
obj.is_updating = true; // Lock this model
obj.spent+= spent;
obj.spent > obj.budjet // if greater.
obj.status = 11 // 11 is the stopped status
update db and rebuild model.
obj.is_updating = false; // Unlock the model
}
注意:我在这里得到的也是伪代码,所以你可能需要稍微调整一下。
这里的想法是在模型中添加一个标志来指示
HTTP request
是否可以继续执行关键代码路径。在这种情况下,您的 checkbudget
功能及其他功能。
当请求到来时,它会检查
is_updating
标志以查看是否可以继续。如果是 true
那么它会安排一个事件,在一秒钟后触发,这个“setTimeout”基本上成为一个事件并被放入 node
的事件队列中以供稍后处理
当此事件稍后被触发时,将再次检查。这种情况会发生,直到
is_update
标志变为 false
,然后请求继续执行其操作,并且当所有关键代码完成后,is_update
再次设置为 false。
这不是最有效的方法,但它可以完成工作,当性能成为问题时,您可以随时重新审视解决方案。
现有的答案很糟糕。
首先,是的,Nodejs 中有一个信号量:https://www.npmjs.com/package/semaphore。
其次,他们提出的解决方案是自旋锁,这对于关键部分来说可能是最糟糕的解决方案。请只使用信号量,而不是这些奇怪的、非标准的、可怕的编码实践。仅仅因为 Nodejs 在一个进程上运行并不意味着不存在关键部分和竞争条件...