NodeJS首调API用户获代金券逻辑异常,请求排查解决
问题分析与修复方案
核心问题
你的代码存在三个关键问题,导致并发场景下所有用户都能拿到代金券:
- 竞态条件:多个并发请求同时执行
redis.get(key)时,都会发现缓存为空,进而都执行createVoucherCode并写入缓存,每个请求都认为自己是第一个用户。 - Redis数据序列化错误:直接将对象传入
redis.setEx,Redis会把对象转为字符串"[object Object]",后续redis.get读取后无法解析为有效对象,导致cachedData.userId === userId的判断完全失效。 - 赋值逻辑错误:最后给
response.voucherCode赋值时用了cachedData.voucherCode,但此时cachedData仍是初始的null,正确应该使用刚生成的voucherCode。
修复后的代码
async function addVoucherCode(response, userId) { try { const key = "KEY_VOUCHER_CODE"; let cachedData = await redis.get(key); // 解析缓存的JSON数据 if (cachedData) { cachedData = JSON.parse(cachedData); if (cachedData.userId === userId) { response.voucherCode = cachedData.voucherCode; } return; } // 生成代金券 const voucherCode = await createVoucherCode(userId); if (!voucherCode) return; const cacheValue = JSON.stringify({ userId, voucherCode }); // 原子操作:仅当key不存在时设置值,并同时设置过期时间 const setResult = await redis.set(key, cacheValue, { NX: true, EX: 15 * 60 }); if (setResult) { // 设置成功,说明是第一个用户 response.voucherCode = voucherCode; } else { // 其他用户已抢先设置,读取缓存判断是否为当前用户 cachedData = JSON.parse(await redis.get(key)); if (cachedData.userId === userId) { response.voucherCode = cachedData.voucherCode; } } } catch (err) { console.error("[Error] addVoucherCode: ", err); } }
关键改进点
- 原子操作解决竞态:使用
redis.set的NX参数(仅当key不存在时设置),确保只有第一个请求能成功写入缓存,后续请求会直接读取已存在的缓存数据,避免重复创建代金券。 - 正确序列化数据:将缓存对象转为JSON字符串存储,读取时解析为对象,保证
userId等属性的判断逻辑有效。 - 修复赋值错误:成功设置缓存时,直接使用新生成的
voucherCode给响应赋值。 - 兼容写入失败场景:当检测到缓存已被其他用户设置时,重新读取缓存并判断当前用户是否为获券用户,避免逻辑遗漏。
补充说明
如果你的Redis客户端版本不支持set方法的NX/EX参数,可以改用SETNX+EXPIRE的组合,但要注意这两个操作不是原子性的,不过在大部分场景下也能满足需求;更严谨的话可以用Redis事务或Lua脚本保证原子性。
内容的提问来源于stack exchange,提问作者j.ian.le
相关产品推荐
相关产品推荐

