Redis如何实现仅Key存在时执行Set操作,不存在则不写入值
实现Redis Key存在才写入的原子方案
Redis 从2.6.12版本开始,原生SET命令就支持XX修饰符,完全满足「Key存在才执行写入,不存在则直接跳过」的需求。该判断逻辑在Redis服务端原子执行,完全不存在客户端多步操作带来的竞态问题。
命令参数说明
SET命令支持两个互斥的写入条件修饰符:
NX:仅当Key不存在时执行写入,就是你已知的setnx的原生实现XX:仅当Key已存在时执行写入,正好匹配你需要的「Key不存在则不写入」的需求
同时SET命令支持直接携带过期时间参数,避免多步操作(比如set后单独调用expire)带来的竞态问题。
PHP代码改造
首先修正原有初始化逻辑的竞态风险(原逻辑set和expireat分两步执行,若set后进程崩溃,Key会永久残留):
$expired_at = strtotime(date('Y-m-d')) + 24 * 3600; $key = "store_of_today"; // 原子完成「不存在则初始化+设置过期时间」,NX保证仅首次初始化生效 $initRes = $redis->set($key, 100, ['NX', 'EXAT' => $expired_at]); $left = $initRes ? 100 : (int)$redis->get($key);
核心的业务处理后写入逻辑,不需要提前做exists校验,直接用XX参数原子执行即可:
// 执行业务逻辑,计算得到新值$new_left // XX参数保证仅Key存在时才写入,KEEPTTL保留原有过期时间不被覆盖 $updateRes = $redis->set($key, $new_left, ['XX', 'KEEPTTL']); if (!$updateRes) { // 写入失败说明Key已过期/被删除,在这里执行兜底逻辑,比如重新初始化当日配额、返回跨天错误等 }
该方案完全解决毫秒级跨天竞态问题:Key存在性判断和写入操作是Redis服务端单线程模型下原子完成的,中间不会插入任何其他请求,也不会出现「校验时Key存在、执行写入时Key刚好过期」的时间差问题。
极老版本兼容方案
如果你的Redis版本低于2.6.12(目前主流生产环境已极少使用),可以通过Lua脚本原子执行判断+写入逻辑,效果和XX参数一致:
// Lua脚本在Redis中原子执行,不存在客户端操作间隙 $luaScript = <<<SCRIPT if redis.call('EXISTS', KEYS[1]) == 1 then return redis.call('SET', KEYS[1], ARGV[1]) end return 0 SCRIPT; $updateRes = $redis->eval($luaScript, [$key, $new_left], 1); if (!$updateRes) { // Key不存在,写入失败处理 }
内容的提问来源于stack exchange,提问作者afraid.jpg
相关产品推荐
相关产品推荐

