Mysql 当事件循环等待DB操作时,如何处理对nodejs服务器的传入请求

Mysql 当事件循环等待DB操作时,如何处理对nodejs服务器的传入请求,mysql,node.js,asynchronous,Mysql,Node.js,Asynchronous,我的API中有一个路由,例如让我们调用它/users/:userId/updateBalance。此路由将获取用户当前余额,添加来自请求的任何内容,然后使用新计算的余额更新余额。像这样的请求每30分钟就有一个特定用户进入服务器,所以直到最近,我还认为并发问题是不可能的 最终发生的情况是,在某个地方,一个发送的请求失败了,30分钟后才再次发送,大约在另一个请求的一秒钟之内。结果是,正如我在数据库中看到的,这两个请求都从数据库中获取了相同的余额,并且都添加了各自的金额。本质上,第二个请求实际上读取了

我的API中有一个路由,例如让我们调用它/users/:userId/updateBalance。此路由将获取用户当前余额,添加来自请求的任何内容,然后使用新计算的余额更新余额。像这样的请求每30分钟就有一个特定用户进入服务器,所以直到最近,我还认为并发问题是不可能的

最终发生的情况是,在某个地方,一个发送的请求失败了,30分钟后才再次发送,大约在另一个请求的一秒钟之内。结果是,正如我在数据库中看到的,这两个请求都从数据库中获取了相同的余额,并且都添加了各自的金额。本质上,第二个请求实际上读取了一个过时的余额,因为它通常应该在请求1执行之后执行

为了更清楚地给出一个数字示例,假设请求1向余额中添加2美元,请求2向余额中添加5美元,用户的余额为10美元。如果请求并行运行,则用户余额将以$12或$15结束,这取决于请求1或请求2是先完成的,因为两个请求都从数据库中获取$10的余额。然而,显然,预期的行为是,我们希望请求1执行,将用户余额更新为12美元,然后请求2执行,并将余额从12美元更新为17美元

为了更好地了解此流程的整体执行情况:接收请求,调用函数,函数必须等待来自DB的余额,然后函数计算新余额并更新DB,然后执行完成

所以我有几个问题要问。第一个问题是,当节点等待异步请求(如MySQL数据库读取)时,它如何处理传入请求。根据我观察到的结果,我假设当第一个请求等待DB时,第二个请求可以开始处理?否则,我无法确定在节点这样的单线程环境中如何体验这种异步行为


其次,我该如何控制和预防这种情况。我本来想使用一个带有forUpdate锁的MySQL事务,但由于目前编写代码的方式,这似乎是不可能的。有没有办法告诉node某个代码块不能“并行”执行?或者任何其他选择?

您说得对,当节点等待数据库查询返回时,它将处理任何传入的请求,并在第一个请求完成之前启动该请求数据库调用

防止这种情况的最简单方法是使用队列。与直接在路由处理程序中处理余额更新不同,该路由处理程序可以将事件推送到队列(在Redis、AWS SQS、RabbitMQ等中)和应用程序中的其他地方(甚至在完全不同的服务中),您将有一个消费者侦听该队列中的新事件。如果更新失败,请将其添加回队列的开头,添加一些等待时间,然后重试

这样,无论您的第一个请求失败多少次,您的余额都是正确的,对该余额的未决更改将以正确的顺序进行。如果队列中的事件多次失败,您甚至可以向某人发送电子邮件或通知以查看该事件,当问题得到解决时,余额的更改将添加到队列中,一旦解决,所有内容都将得到正确处理

您甚至可以读取该队列并向用户显示信息,例如,告诉用户余额有待定更新,因此可能不准确

希望这有帮助

第一个问题是,当节点等待异步请求(如MySQL数据库读取)时,它如何处理传入请求

nodejs的事件循环使这种情况发生,否则您将拥有一个性能超低的完全同步程序

  • 上下文中包含的每个异步函数都将在上下文本身执行后执行

  • 在上下文执行完成和异步函数执行之间,可以安排执行其他异步函数(此“插入”由事件循环管理)

  • 如果等待异步函数,则上下文的剩余代码将安排在异步函数执行后的某个位置

玩的时候更清楚。例1:

// Expected result: 1, 3, 4, 2    

function asyncFunction(x) {
  // setTimeout as example of async operation
  setTimeout(() => console.log(x), 10)
}

function context() {
  console.log(1)
  asyncFunction(2)
  console.log(3)
}

context()

console.log(4)
例2:

// Expected result: 1, 2, 3    

function asyncFunction(x) {
  // Promise as example of async operation
  return new Promise((resolve) => {
    console.log(x)
    resolve()
  })
}

async function context() {
  console.log(1)

  await asyncFunction(2)

  console.log(3)
}

context()
示例3(与您的情况更相似):

在您的示例中:

  • 当服务器接收到连接时,将计划处理程序的执行

  • 当处理程序被执行时,它会安排查询的执行,处理程序上下文的剩余部分在执行之后安排


在计划的执行之间,一切都可能发生。

已经回复,但更多关于事件循环的信息
// Expected result: 1, 2, 4, 5, 3, 6

function asyncFunction(x) {
  // Promise as example of async operation
  return new Promise((resolve) => {
    console.log(x)
    resolve()
  })
}

async function context(a, b, c) {
  console.log(a)

  await asyncFunction(b)

  console.log(c)
}

context(1, 2, 3)
context(4, 5, 6)