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

Node.js并发场景下如何正确使用Redis的GET、SET命令?

嘿,这个问题我之前做并发项目时也踩过坑!本质就是竞态条件在搞鬼——多个并发请求同时读取了Redis里的同一个值,各自修改后再写回去,直接把彼此的更新给覆盖了,自然拿不到预期的3000。要解决这个问题,有几种靠谱的方案,根据你的场景选就行:


方案一:用Redis Lua脚本实现原子操作

Redis执行Lua脚本的时候是单线程原子性的——整个脚本的逻辑会一次性执行完,中间不会被其他请求打断。这是处理这类自定义原子操作最推荐的方式,性能也比锁方案好很多。

把你的add函数改成Lua脚本版本就行:

async function add() {
  // 定义Lua脚本:获取值、加1、存回Redis,最后返回新值
  const luaScript = `
    local current_num = redis.call('GET', KEYS[1])
    current_num = tonumber(current_num) + 1
    redis.call('SET', KEYS[1], tostring(current_num))
    return current_num
  `;
  // 执行脚本,KEYS[1]对应要操作的key(这里是'num')
  const newNum = await redisClient.eval(luaScript, 1, 'num');
  console.log(newNum);
  return newNum;
}

不管多少个并发请求过来,每个脚本的执行都是原子的,完全不会出现中间被打断的情况,最终肯定能得到3000。


方案二:用Redis分布式锁

如果你的更新逻辑特别复杂(比如需要调用外部服务、做复杂计算,没法塞进Lua脚本里),那可以用Redis实现分布式锁,保证同一时间只有一个客户端能执行GET-SET操作。

这里用Redis官方推荐的SET key value NX PX <过期时间>来实现锁(替代老旧的SETNX),既能保证锁的唯一性,还能设置过期时间防止死锁:

async function add() {
  const lockKey = 'lock:num';
  const lockValue = Date.now().toString() + Math.random(); // 用唯一值标记锁的持有者
  const lockExpire = 5000; // 锁过期时间,5秒,根据你的操作耗时调整

  try {
    // 获取锁:只有lockKey不存在时才设置成功(NX),同时设置过期时间(PX)
    const lockAcquired = await new Promise((resolve) => {
      redisClient.set(lockKey, lockValue, 'NX', 'PX', lockExpire, (err, reply) => {
        resolve(reply === 'OK');
      });
    });

    if (!lockAcquired) {
      // 没拿到锁,稍等一下再重试,避免频繁请求Redis
      await new Promise(resolve => setTimeout(resolve, 10));
      return add(); // 递归重试
    }

    // 拿到锁了,执行你的更新逻辑
    let numStr = await get('num');
    let num = Number(numStr);
    num++;
    await set('num', String(num));
    console.log(num);
    return num;
  } finally {
    // 释放锁:只有锁的持有者是当前客户端才释放,防止误删其他客户端的锁
    const releaseScript = `
      if redis.call('GET', KEYS[1]) == ARGV[1] then
        return redis.call('DEL', KEYS[1])
      else
        return 0
      end
    `;
    await redisClient.eval(releaseScript, 1, lockKey, lockValue);
  }
}

注意:锁的过期时间要设置得比你的更新操作耗时久一点,避免锁提前过期导致并发问题;释放锁一定要用Lua脚本原子判断,防止误删别人的锁。


题外话:为什么不用本地锁?

你的代码是在同一个Node进程里跑三个异步函数,用本地锁(比如Promise锁)也能解决,但如果以后你的应用扩展成多进程或者多实例,本地锁就完全没用了。所以还是推荐Redis层面的原子操作或者分布式锁,扩展性更好。

内容的提问来源于stack exchange,提问作者tallguysbb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:47:48