【问题标题】:Semaphore equivalent in Node js , variable getting modified in concurrent request?Node js中的信号量等价物,变量在并发请求中被修改?
【发布时间】:2017-08-18 11:24:19
【问题描述】:

过去 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. 
}

这曾经可以正常工作,但现在并发请求我们得到虚假支出,支出增加超过预算,并且在一段时间后停止。我们用 j 米模拟了呼叫并发现了这一点。

据我们所知,节点是异步的,所以当状态更新到 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 可以请任何人帮忙

【问题讨论】:

  • We then check the in memory model and add the spent to the existing spend 这会更新内存模型吗?
  • update db and rebuild model. 所以,你只重建模型一次 status == 11?重建是同步的吗?还是一旦数据库更新就完成了,毫无疑问是异步的 - 确实一些真实的代码会更容易回答
  • 是的,这更新了模型@JaromandaX
  • 是的,只有当它是 11 时,我们才更新数据库,但我们在更新数据库之前更新内存模型,是的,数据库调用是 async 。抱歉,我无法分享生产代码
  • 并发,多线程还是多进程?您知道这不会解决您的问题,反而会使问题更加复杂,对吧?

标签: javascript node.js concurrency


【解决方案1】:

问:NodeJs 中是否有semaphore 或等效项?

答: 没有。

问:那么NodeJs用户如何处理竞态条件?

答:理论上你不应该这样做,因为javascript 中没有thread

在深入了解我提出的解决方案之前,我认为了解NodeJs 的工作原理对您很重要。

对于NodeJs,它由基于事件的架构驱动。这意味着在Node 进程中有一个包含所有“待办事项”事件的事件队列。

event 从队列中获取pop 时,node 将执行所有所需的代码,直到完成。在运行期间进行的任何async 调用都将作为其他events 生成,并且它们在event queue 中排队,直到收到响应并再次运行它们。

问:那我该怎么做才能保证一次只有1个请求可以对数据库执行updates呢?

答:我相信有很多方法可以实现这一点,但更简单的方法之一是使用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。

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

【讨论】:

  • 感谢您的解决方案,但性能是我的应用程序中的一个关键点,我们正在寻找大约 1 - 10 ms 的吞吐量。我不认为这会成功吗?
  • 当我们谈论throughput 时,它总是与它在每段时间内可以处理的事务量有关。我怀疑你的意思是这里的响应时间。如果您担心响应时间,请将retry 时间调整为10ms。为什么不实施解决方案并对其进行压力测试?很确定它会正常工作。否则,请随时提出您的疑虑。
  • is_updating 是一个布尔值,在此处充当信号量
  • @SamuelToh 好的,如果 NodeJs 将请求放入队列。那么只有1个请求在更新数据库不是很明显吗?为什么要使用 setTimeout?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-24
  • 2015-04-12
  • 2021-12-21
  • 1970-01-01
  • 2021-11-08
  • 1970-01-01
  • 2010-11-17
相关资源
最近更新 更多