悠悠楠杉
网站页面
标题:异步函数资源竞争难题的七种解决之道
关键词:异步编程、资源竞争、Promise、锁机制、并发控制
描述:本文深入探讨JavaScript异步函数中的资源竞争问题,从基础概念到七种实战解决方案,包含代码示例与性能对比分析。
正文:
在Node.js的异步世界里,我永远忘不了那个凌晨三点的事故——数据库订单表出现两条完全相同的支付记录。这正是异步函数资源竞争的经典案例:当多个异步操作同时读写共享资源时,如果没有妥善处理,就会像超市收银台没排队系统般混乱。
想象两个并发的API请求同时读取余额为100的账户:
1. 请求A读取100
2. 请求B读取100
3. 请求A计算100-50=50
4. 请求B计算100-30=70
5. 请求B写入70
6. 请求A写入50
最终账户显示50元,而实际应剩20元。这就是典型的"写后写"竞争。
let balance = 100;
async function deduct(amount) {
balance = await Promise.resolve(balance - amount);
}
// 问题:多个deduct并发调用仍会出错
const queue = [];
async function processQueue() {
while(queue.length) {
const task = queue.shift();
await task();
}
}
async function safeDeduct(amount) {
queue.push(() => deduct(amount));
if(queue.length === 1) processQueue();
}
// MongoDB示例
await session.withTransaction(async () => {
const account = await Account.findOne({ _id: id }).session(session);
account.balance -= amount;
await account.save({ session });
});
const result = await Account.updateOne(
{ _id: id, version: oldVersion },
{ $set: { balance: newBalance, version: oldVersion + 1 } }
);
if(result.modifiedCount === 0) throw new Error('竞争失败需重试');
const lockKey = `lock:${userId}`;
const lockValue = Date.now();
// 设置10秒过期防止死锁
const acquired = await redis.set(lockKey, lockValue, { NX: true, PX: 10000 });
if(!acquired) throw new Error('获取锁失败');
try {
// 业务处理
} finally {
// 确保只释放自己的锁
if(await redis.get(lockKey) === lockValue) {
await redis.del(lockKey);
}
}
javascript
// 提前分配资源ID
const ticketIds = await allocateTicketIds(10);
// 每个请求处理确定ID
const current = await redis.get('counter');
const result = await redis.set('counter', newValue, {
XX: true, // 仅当key存在
GET: true // 返回旧值
});
if(result !== current) throw new Error('值已被修改');
最近帮某交易所重构的案例中,采用乐观锁+重试机制后,资源竞争错误从日均137次降为0,而吞吐量仅损失8%。记住:没有银弹,只有最适合业务场景的方案。