导读:本期聚焦于蜗牛创作的《Redis GETSET命令怎么用?如何获取旧值并设置新值》,敬请观看详情。GETSET是Redis中一个看似简单却非常实用的字符串命令,它能在一次原子操作中返回键当前的旧值,同时把该键设置为新值。这种原子特性让它在分布式锁释放、计数器重置、缓存过期判断、限流统计等场景中大放异彩。本文将详细讲解GETSET的基本语法和返回值规则,对比GET加SET组合写法在并发下的问题,分析键不存在或键类型错误时的行为,并结合具体代码演示如何用GETSET实现带过期时间检测的分布式锁以及令牌桶限流。同时也会介绍Redis新版本中SET命令带GET参数的替代方案,帮助你写出更简洁高效的Redis应用代码。

在Redis的字符串命令家族里,GETSET算是一个容易被新手忽视、却经常被资深工程师用在高并发场景里的命令。它的作用一句话就能说清:给某个key设置新值,并把设置之前的旧值返回给你。听起来简单,但关键在于这两步是原子的,不会被打断,这正是它和先GET再SET这种两步操作的本质区别。

Redis GETSET命令怎么用?如何获取旧值并设置新值

GETSET的基本语法与返回值规则

GETSET的语法非常简单,格式为GETSET key value。执行时Redis会先取出key当前对应的值并返回给客户端,随后立刻把该key的值替换为传入的value。如果key原本不存在,命令会返回nil,但仍会完成设置操作,也就是说key会被创建出来并赋予新值。

需要注意两点。第一,GETSET只能作用于字符串类型的key,如果key存在但类型是列表、哈希或集合,Redis会直接报错,提示WRONGTYPE,操作不会执行。第二,GETSET本身不会改变key的过期时间,如果这个key之前设置过TTL,执行GETSET后过期时间依然保留,这一点和SET命令的默认行为不同,很多人在这里踩过坑。

redis> SET counter 100
OK
redis> GETSET counter 200
"100"                  # 返回旧值100,此时counter已经是200
redis> GET counter
"200"
redis> GETSET not_exist_key hello
(nil)                  # key不存在,返回nil,但hello已被设置
redis> GET not_exist_key
"hello"

为什么不用GET加SET组合?原子性才是核心

有人会问,先执行GET拿到旧值,再执行SET写入新值,效果不是一样吗?在单线程、无并发的场景下确实等价,但Redis是典型的多客户端并发访问环境,两条命令之间可能穿插了其他客户端的请求。假设两个客户端同时对同一个key做先读后写,最终的写入顺序和读取到的旧值都可能不符合预期,这就是典型的竞态条件。

GETSET把读取旧值和写入新值合并成一个不可分割的操作,在Redis单线程执行模型下,命令执行期间不会有其他命令插队,因此结果总是确定的。如果客户端只需要执行一次往返就能完成读旧值写新值的逻辑,还能减少一次网络往返开销,对性能敏感的场景也有帮助。

当然,如果你的业务逻辑更复杂,比如需要根据旧值计算新值,GETSET就不适用了,此时应该考虑WATCH加MULTI的事务方案,或者直接使用Lua脚本把读取和判断逻辑封装成原子操作。GETSET适合的是那种新旧值之间没有依赖关系、但必须原子交接的场景。

实战场景:分布式锁的释放与互斥控制

GETSET最经典的应用之一是分布式锁。一种基于超时时间的简易锁实现思路是:锁的value存储锁的到期时间戳,加锁时先SETNX,失败后再用GETSET尝试抢占。GETSET返回的旧值如果已经过期,说明上一个持锁者超时了,当前客户端可以合理接管这把锁,同时把自己的到期时间戳写进去。因为读写是原子的,多个客户端同时抢占也不会出现两个都成功却互相覆盖还不知情的混乱局面。

public String tryLock(Jedis jedis, String lockKey, long expireMs) {
    long expires = System.currentTimeMillis() + expireMs;
    String old = jedis.getSet(lockKey, String.valueOf(expires));
    if (old == null) {
        return String.valueOf(expires); // 锁不存在,直接获取成功
    }
    // 旧值已过期,说明原持有者超时,接管锁
    if (Long.parseLong(old) < System.currentTimeMillis()) {
        return String.valueOf(expires);
    }
    return null; // 锁仍被他人持有,获取失败
}

需要提醒的是,这种基于时间戳的锁方案存在时钟依赖问题,不同机器的系统时钟偏差可能导致误判。生产环境更推荐使用Redisson这类封装完善的分布式锁框架,或者使用SET key value NX EX加Lua脚本释放的方案。GETSET思路的价值在于帮助我们理解原子操作在并发控制中的意义。

另一个常见用法是限流和计数重置。比如实现一个每分钟限流100次的接口,可以把key设置为分钟时间戳加计数的组合,当GETSET返回的旧值显示已经是新的时间窗口时,直接重置计数。此外在缓存场景中,GETSET也常被用来做互斥的缓存重建:拿到旧缓存的同时立刻写入一个占位值,避免大量请求同时穿透到数据库。

新版本的选择:SET命令的GET参数

从Redis 6.2开始,SET命令支持了GET参数,写法为SET key value GET,效果和GETSET基本一致,同样是原子地返回旧值并写入新值。官方文档中已经建议使用SET的GET参数来替代GETSET,未来版本中GETSET有可能被标记为废弃。

SET加GET的优势在于可以和其他选项自由组合,比如SET key value GET EX 60可以在返回旧值的同时设置60秒过期时间,一条命令完成了过去需要两条命令才能做到的事情。而GETSET无法指定过期参数,只能依赖key原有的TTL,灵活性明显不足。

redis> SET mykey newvalue GET EX 60
"oldvalue"             # 返回旧值,同时设置新值并附带60秒过期
redis> SET newkey hello GET
(nil)                  # key不存在时同样返回nil

如果你的Redis版本在6.2以上,建议新代码直接使用SET的GET参数;如果还需要兼容旧版本,GETSET依然是可靠的选择。两者在原子语义上完全等价,迁移成本很低,只需要注意返回值的判断逻辑保持一致即可。掌握GETSET,本质上是掌握了利用原子操作简化并发控制的思想,这个思想在Lua脚本和事务方案中同样适用。

Redis GETSETRedis命令分布式锁修改时间:2026-09-09 12:06:54

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260909/53357.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。