处理 NodeJS 的异步行为

Posted

技术标签:

【中文标题】处理 NodeJS 的异步行为【英文标题】:Dealing with NodeJS asynchronous behavior 【发布时间】:2013-03-19 13:07:12 【问题描述】:

在 MongoDB+Mongoose 中使用 NodeJS。

首先,我知道异步非阻塞代码的优点。所以我确实处理回调。但最后我遇到了以下问题。

假设我有一个用户可以随时调用的函数。而且有可能,一个超级“闪电般”的用户几乎同时调用了它两次。

function do_something_with_user(user_id)
    User.findOne(_id:user_id).exec(function(err,user) // FIND QUERY
        // Do a lot of different stuff with user
        // I just cannot update user with a single query
        // I might need here to execute any other MongoDB queries
        // So this code is a set of queries-callbacks
        user.save() // SAVE QUERY
    )

当然它是这样执行的:FIND QUERY, FIND QUERY, SAVE QUERY, SAVE QUERY

这完全打破了应用程序的逻辑(应该查找查询、保存查询、查找查询、保存查询)。所以我决定通过为特定用户“锁定”整个函数来防止异步行为(所以函数代码内部仍然是异步的)。

var lock_function_for_user = 

function do_something_with_user(user_id)
    if(!lock_function_for_user[user_id])
        lock_function_for_user[user_id] = true
        User.findOne(_id:user_id).exec(function(err,user)
            // Same code as above
            user.save(function()
                lock_function_for_user[user_id] = false
            )
        )
     else 
        setTimeout(function()
            do_something_with_user(user_id)
        ,100) // assuming that average function execution time is 100ms in average
    

所以,我的问题是:这是一个好的做法,好的 hack 还是坏的 hack?如果这是一个糟糕的黑客,请提供任何其他解决方案。特别是,当我们扩展和启动多个 NodeJS 进程时,我怀疑这个解决方案是否会起作用。

【问题讨论】:

【参考方案1】:

这是一个非常糟糕的做法,你不应该使用计时器来控制代码的流动。

这里的问题称为原子性。如果你需要做 find-save,find-save 那么你需要以某种方式打包这些操作(事务)。这取决于您使用的软件。在 redis 中,您有 multi 和 exec 命令。在 mongodb 中有 findAndModify()。另一种解决方案是使用索引。当您尝试两次保存同一字段时,您会收到错误消息。在 mongoose 的 schemaType 中使用属性“index: true”和“unique: true”:

var schema = mongoose.Schema (
    myField:  type: String, index: true, unique: true, required: true ,
);

这就是您需要的:Mongodb - Isolate sequence of operations - Perform Two Phase Commits。但要考虑到,如果您需要进行大量事务,mongodb 可能不是最佳选择。

【讨论】:

您的回答确实有道理。你提到了良好的做法。但是,假设我有 FIND-SOMECODE-FIND-SOMECODE-SAVE-SAVE (查找文档,做某事,查找另一个文档,做某事,保存两者)。看来我不能在这里使用 findAndModify 。我想我也不能使用索引。如果 Mongo 在第二次保存中抛出错误,我当然可以回滚第一次保存,但那又如何呢?如何尝试再次调用该函数?因此,即使使用索引,问题仍然存在。 查看此网页:docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations。执行两阶段提交。 最后,我需要什么。谢谢你。我将使用 Redis-MongoDB 混合。无论如何,您的回答非常有帮助,因为 Redis 也是非阻塞的。所以我会在 Redis 中使用 multi,在 Mongo 中使用隔离。【参考方案2】:

你不想浪费内存,所以替换

lock_function_for_user[user_id] = false

delete lock_function_for_user[user_id]

除此之外:您可以just be optimistic and retry if a conflict happens。只需忽略锁定并确保数据库在出现问题时注意到(并在这种情况下重试)。当然,哪种方式更好取决于这种冲突真正发生的频率。

【讨论】:

谢谢。当然我会使用delete,这是一个很好的点。但是,在下面我提到的答案中,如果 DB 发现一些奇怪的东西并且我处理了错误,我不知道如何再次启动该函数。我不想丢失用户请求,我的目标不仅是保护数据库免受垃圾写入,还要保留用户请求并重试。那是一个问题:“如何重试?”。比方说,当我们扩展时,这个错误可能会经常发生。 @igorpavlov 使用这样的模式怎么样? performChange(document_id, function(document) ...; return changedDocument; ) 然后 performChange 可以在发生错误时再次调用自身。基本上,我认为您通常应该能够通过对新函数应用相同的更改来重试,因此您只需要能够将更改表达为以旧文档作为参数并返回新文档的 JS 函数。 调用函数 self 会导致对数据库的大量查询。你永远不知道,之前函数调用的查询什么时候结束:10 毫秒、1000 毫秒?它会产生巨大的混乱。顺便说一句,它似乎与 .save(err, saved_document) /* 对保存的文档做 smth */

以上是关于处理 NodeJS 的异步行为的主要内容,如果未能解决你的问题,请参考以下文章

异步/等待异常和 Visual Studio 2013 调试输出行为

NodeJs + ExpressJs 应用程序路由奇怪的行为

.net core 和 Mvc api 行为之间的区别

CRON + Nodejs + 多核 => 行为?

ES6基础入门教程(十六)promise异步队列

异步程序未异步运行的奇怪行为