Node.js如何处理并发API请求?库存扣减竞态问题求解
Node.js中解决库存扣减并发冲突的最优方案
你的问题本质是并发场景下的库存超卖问题,核心原因是多个请求同时读取到相同的库存值,各自执行扣减后覆盖结果。最优解决方案分两类,优先级从高到低:
1. 数据库层面原子操作(最推荐)
直接通过数据库的原子更新语句,把库存读取和扣减合并为一个不可分割的操作,从根源避免并发冲突。
示例代码(以MySQL + mysql2库为例)
const mysql = require('mysql2/promise'); async function deductStock(productId, deductAmount) { const connection = await mysql.createConnection({ host: 'localhost', user: 'root', password: 'your_password', database: 'your_db' }); try { // 原子更新:仅当库存足够时才扣减,返回受影响的行数 const [result] = await connection.execute( 'UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?', [deductAmount, productId, deductAmount] ); // 受影响行数为1则扣减成功,否则库存不足 if (result.affectedRows === 1) { console.log('库存扣减成功'); return true; } else { console.log('库存不足,扣减失败'); return false; } } finally { await connection.end(); } } // 调用示例 deductStock(1, 3); // 面包的productId假设为1
为什么这是最优?
- 数据库原生支持原子性,无需额外引入依赖
- 性能优于应用层锁,避免锁竞争带来的开销
- 支持多实例部署,不会因为应用层集群失效
2. 应用层锁(补充方案)
如果业务场景需要在应用层做逻辑判断,可根据部署架构选择不同锁方案:
单实例部署:内存互斥锁
用async-mutex库实现内存级别的互斥,确保同一时间只有一个请求执行扣减逻辑:
const { Mutex } = require('async-mutex'); // 为每个商品创建独立的锁,避免全局锁影响性能 const productLocks = new Map(); async function deductStock(productId, deductAmount) { // 获取或创建当前商品的锁 const mutex = productLocks.get(productId) || new Mutex(); if (!productLocks.has(productId)) { productLocks.set(productId, mutex); } const release = await mutex.acquire(); try { // 先查询当前库存 const [rows] = await connection.execute('SELECT stock FROM products WHERE id = ?', [productId]); const currentStock = rows[0].stock; if (currentStock >= deductAmount) { await connection.execute('UPDATE products SET stock = ? WHERE id = ?', [currentStock - deductAmount, productId]); console.log('扣减成功'); return true; } else { console.log('库存不足'); return false; } } finally { release(); } }
多实例部署:Redis分布式锁
用redlock库实现跨实例的分布式锁,确保集群环境下的互斥:
const Redlock = require('redlock'); const Redis = require('ioredis'); const redis = new Redis({ host: 'localhost' }); const redlock = new Redlock([redis], { driftFactor: 0.01, // 时钟漂移容忍度 retryCount: 3, // 重试次数 retryDelay: 200 // 重试间隔 }); async function deductStock(productId, deductAmount) { const lockKey = `stock_lock:${productId}`; let lock; try { // 获取锁,有效期10秒(需扣减逻辑在有效期内完成) lock = await redlock.lock(lockKey, 10000); // 执行库存查询和扣减逻辑 const [rows] = await connection.execute('SELECT stock FROM products WHERE id = ?', [productId]); const currentStock = rows[0].stock; if (currentStock >= deductAmount) { await connection.execute('UPDATE products SET stock = ? WHERE id = ?', [currentStock - deductAmount, productId]); console.log('扣减成功'); return true; } else { console.log('库存不足'); return false; } } catch (err) { console.log('获取锁失败,请求重试'); return false; } finally { if (lock) await lock.unlock().catch(() => {}); } }
注意事项
- 优先选择数据库原子操作,应用层锁仅作为特殊场景补充
- 分布式锁需设置合理的过期时间,避免死锁
- 不要在锁内执行耗时操作,否则会降低并发性能
内容的提问来源于stack exchange,提问作者Fabio Kounang
相关产品推荐
相关产品推荐

