我的API中有一条路由,例如,可以将其称为/ users /:userId / updateBalance.此路由将获取用户当前余额,添加来自请求的任何内容,然后使用新计算的余额更新余额.这样的请求每隔30分钟就会针对特定用户进入服务器,因此直到最近,我还认为并发问题是不可能的.
最终发生的情况是,某个地方的已发送请求失败,并且仅在30分钟后(大约在另一个请求的一秒钟之内)再次发送.结果是,正如我在数据库中看到的那样,这两个请求都从数据库中获取了相同的余额,并且都添加了各自的金额.本质上,第二个请求实际上读取了一个过时的余额,因为通常它应该在请求1执行后执行.
为了更清楚地给出一个数值示例,假设请求1将2美元添加到余额中,请求2则添加5美元,而用户的余额为10美元.如果请求并行执行,则根据请求1或请求2首先完成,用户余额将分别为$12或$15,因为两个请求都从DB提取了$10的余额.但是,显然预期的行为是我们希望执行请求1,将用户余额更新为$12,然后请求2执行并将余额从$12更新为$17.
为了更好地了解此过程的整体执行情况:接收了请求,调用了一个函数,该函数必须等待DB的余额,然后该函数计算新的余额并更新db,然后执行完成了.
因此,我对此有一些疑问.第一个是,节点在等待异步请求(如MySQL数据库读取)时如何处理传入的请求.根据我观察到的结果,我假设当第一个请求等待数据库时,第二个请求可以开始处理吗?否则,我不确定在节点等单线程环境中如何经历这种异步行为.
其次,我该如何控制和预防它.我本想使用带有forUpdate锁的MysqL事务,但事实证明,由于当前编写代码的方式,这似乎是不可能的.有没有办法告诉节点某个代码块不能“并行”执行?还是其他选择?
防止此IMO的最简单方法是使用队列.该路由处理程序可以直接将事件推送到队列(在Redis中,在AWS SQS中,在RabbitMQ等中)以及应用程序中其他地方(甚至在完全不同的服务中),而不是直接在路由处理程序中处理余额更新.会有一个消费者监听该队列中的新事件.如果更新失败,请将其重新添加到队列的开头,添加一些等待时间,然后重试.
这样,无论您的第一个请求失败多少次,您的余额都将是正确的,并且对该余额的未决更改将以正确的顺序进行.万一队列中的事件反复失败,您甚至可以向某人发送电子邮件或通知以进行查看,而问题已解决的同时,余额的更改将添加到队列中,并且一旦固定,一切都会正确处理.
您甚至可以读取该队列并向您的用户显示信息,告诉用户余额有待更新,因此可能不准确.
希望这可以帮助!