我想知道如何在执行IO时防止NodeJS中的竞争条件。我一直在阅读一些内容,每个人都坚持认为竞争条件是不可能的,因为 NodeJS 是单线程的。
让我们看一下以下伪代码:
async decreaseUserBalance(userId, amount) {
const currentBalance = await sql.queryScalar('SELECT balance FROM user WHERE userid=?', [userId]);
await sql.query('UPDATE user SET balance = ? WHERE userid = ?', [currentBalance - amount, userId]);
}
假设连接到的数据库节点负载很重,并且需要一些时间才能完成每个请求,并且每次用户单击某个项目的购买按钮时都会调用此函数。
当用户开始向此按钮发送垃圾邮件或让机器人发送购买请求时会发生什么?在我的理解中,第一个请求将执行 SELECT 查询,并恢复执行。现在,由于用户正在向按钮发送垃圾邮件,因此下一个请求会进来,执行相同的功能。现在您有几个 SELECT 语句等待完成。现在,几个 select 语句完成执行并执行回调,因此现在有几个回调正在执行 UPDATE 语句,所有这些都具有相同的余额。这意味着,如果起始余额为 5,则所有这些余额都将从上次已知余额减少 var 金额。因此,基本上,无论您同时执行此查询的频率如何,第一堆请求都将从 SELECT 查询中获取相同的值并将其更新为一些虚假值。
对不起,如果我的解释有点模糊,但我相信这是一个非常现实的问题,我怀疑很多人都会考虑到这一点。
那么为了解决这个问题,Node是否支持互斥体之类的东西?据我所知,MongoDB不支持表锁定,因此锁定只是SQL的一个选项。
编辑:
我知道我可以在 1 个查询中完成此示例,这将解决它,但让我们说这是不可能的。那你会怎么解决这个问题呢?
编辑2:
好的,让我们看一下这个例子:
async tryBuyItem(userId, itemId, price) {
//Do we still have enough balance?
const currentBalance = await sql.queryScalar('SELECT balance FROM user WHERE userid=?', [userId]);
if (currentBalance >= price) {
await sql.query('UPDATE user SET balance = balance - ? WHERE userid = ?', [price, userId]);
await sql.query('INSERT INTO user_items (userid, itemid) VALUES (?, ?)', [userId, itemId]);
return true;
} else {
return false;
}
}
sql.query('UPDATE user SET balance = balance - ? WHERE userid = ?', [amount, userId]);
这里没有竞争条件,也没有使用任何事务。
如果是支付方式,您必须更新和插入许多文档,并且您的服务器将在几分钟内获得很多点击,您可以使用 var q = async.queue(function (task, callback) {
function(){}
}, 5);
处理或将整个功能放入async.waterfall模块中,该模块逐个执行。
https://caolan.github.io/async/docs.html#waterfall