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

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