Node js 中的信号量等效项,变量在并发请求中被修改?

问题描述 投票:0回答:2

过去 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 现在有人可以帮忙吗

javascript node.js concurrency
2个回答
9
投票

问:

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。

这不是最有效的方法,但它可以完成工作,当性能成为问题时,您可以随时重新审视解决方案。


0
投票

现有的答案很糟糕。

首先,是的,Nodejs 中有一个信号量:https://www.npmjs.com/package/semaphore

其次,他们提出的解决方案是自旋锁,这对于关键部分来说可能是最糟糕的解决方案。请只使用信号量,而不是这些奇怪的、非标准的、可怕的编码实践。仅仅因为 Nodejs 在一个进程上运行并不意味着不存在关键部分和竞争条件...

© www.soinside.com 2019 - 2024. All rights reserved.