You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NodeJS首调API用户获代金券逻辑异常,请求排查解决

问题分析与修复方案

核心问题

你的代码存在三个关键问题,导致并发场景下所有用户都能拿到代金券:

  1. 竞态条件:多个并发请求同时执行redis.get(key)时,都会发现缓存为空,进而都执行createVoucherCode并写入缓存,每个请求都认为自己是第一个用户。
  2. Redis数据序列化错误:直接将对象传入redis.setEx,Redis会把对象转为字符串"[object Object]",后续redis.get读取后无法解析为有效对象,导致cachedData.userId === userId的判断完全失效。
  3. 赋值逻辑错误:最后给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 20:15:38