如何巧妙运用redis的setIfAbsent实现高效且安全的分布式锁?

2026-04-19 17:561阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计864个文字,预计阅读时间需要4分钟。

如何巧妙运用redis的setIfAbsent实现高效且安全的分布式锁?

如果为空则set值,并返回1;如果存在(不为空)则不进行操作,并返回0。非常明显,比get和set要好。因为先判断get,再set,这样可以避免重复set值。+setIfAbsent和setnx是Java中的方法。

如果为空就set值,并返回1

如何巧妙运用redis的setIfAbsent实现高效且安全的分布式锁?

如果存在(不为空)不进行操作,并返回0

很明显,比get和set要好。因为先判断get,再set的用法,有可能会重复set值。

setIfAbsent 和 setnx

setIfAbsent 是java中的方法

setnx 是 redis命令中的方法

setnx 例子

redis> SETNX mykey "Hello" (integer) 1 redis> SETNX mykey "World" (integer) 0 redis> GET mykey "Hello"

setIfAbsent 例子

代码:

BoundValueOperations boundValueOperations = this.redisTemplate.boundValueOps(redisKey); flag = boundValueOperations.setIfAbsent(value); // flag 表示的是否set boundValueOperations.expire(seconds, TimeUnit.SECONDS); if(!flag){ // 重复 repeatSerial.add(serialNo); continue; }else{// 没有重复 norepeatSerial.add(serialNo); }

补充:使用redis事物解决stringRedisTemplate.setIfAbsent()并设置过期时间遇到的问题

spring-date-redis版本:1.6.2

场景:

在使用setIfAbsent(key,value)时,想对key设置一个过期时间,同时需要用到setIfAbsent的返回值来指定之后的流程,所以使用了以下代码:

boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value); if(store){ stringRedisTemplate.expire(key,timeout); // todo something... }

这段代码是有问题的:当setIfAbsent成功之后断开连接,下面设置过期时间的代码stringRedisTemplate.expire(key,timeout); 是无法执行的,这时候就会有大量没有过期时间的数据存在数据库。想到一个办法就是添加事务管理,修改后的代码如下:

stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi(); boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value); if(store){ stringRedisTemplate.expire(key,timeout); } stringRedisTemplate.exec(); if(store){ // todo something... }

这样就保证了整个流程的一致性。本因为这样就可以了,可是事实总是不尽人意,因为我在文档中发现了以下内容:

加了事务管理之后,setIfAbsent的返回值竟然是null,这样就没办法再进行之后的判断了。

好吧,继续解决:

stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi(); String result = stringRedisTemplate.opsForValue().get(key); if(StringUtils.isNotBlank(result)){ return false; } // 锁的过期时间为1小时 stringRedisTemplate.opsForValue().set(key, value,timeout); stringRedisTemplate.exec(); // todo something...

上边的代码其实还是有问题的,当出现并发时,String result = stringRedisTemplate.opsForValue().get(key); 这里就会有多个线程同时拿到为空的key,然后同时写入脏数据。

最终解决方法:

使用stringRedisTemplate.exec();的返回值判断setIfAbsent是否成功

stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi(); stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event)); stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS); List result = stringRedisTemplate.exec(); // 这里result会返回事务内每一个操作的结果,如果setIfAbsent操作失败后,result[0]会为false。 if(true == result[0]){ // todo something... }

将redis版本升级到2.1以上,然后使用

直接在setIfAbsent中设置过期时间

update :

java 使用redis的事务时不能直接用Api中的multi()和exec(),这样multi()和exec()两次使用的stringRedisTemplate不是一个connect,会导致死锁,正确方式如下:

private Boolean setLock(RecordEventModel event) { String lockKey = event.getModel() + ":" + event.getAction() + ":" + event.getId() + ":" + event.getMessage_id(); log.info("lockKey : {}" , lockKey); SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() { List<Object> exec = null; @Override @SuppressWarnings("unchecked") public Boolean execute(RedisOperations operations) throws DataAccessException { operations.multi(); stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event)); stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS); exec = operations.exec(); if(exec.size() > 0) { return (Boolean) exec.get(0); } return false; } }; return stringRedisTemplate.execute(sessionCallback); }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持易盾网络。如有错误或未考虑完全的地方,望不吝赐教。

本文共计864个文字,预计阅读时间需要4分钟。

如何巧妙运用redis的setIfAbsent实现高效且安全的分布式锁?

如果为空则set值,并返回1;如果存在(不为空)则不进行操作,并返回0。非常明显,比get和set要好。因为先判断get,再set,这样可以避免重复set值。+setIfAbsent和setnx是Java中的方法。

如果为空就set值,并返回1

如何巧妙运用redis的setIfAbsent实现高效且安全的分布式锁?

如果存在(不为空)不进行操作,并返回0

很明显,比get和set要好。因为先判断get,再set的用法,有可能会重复set值。

setIfAbsent 和 setnx

setIfAbsent 是java中的方法

setnx 是 redis命令中的方法

setnx 例子

redis> SETNX mykey "Hello" (integer) 1 redis> SETNX mykey "World" (integer) 0 redis> GET mykey "Hello"

setIfAbsent 例子

代码:

BoundValueOperations boundValueOperations = this.redisTemplate.boundValueOps(redisKey); flag = boundValueOperations.setIfAbsent(value); // flag 表示的是否set boundValueOperations.expire(seconds, TimeUnit.SECONDS); if(!flag){ // 重复 repeatSerial.add(serialNo); continue; }else{// 没有重复 norepeatSerial.add(serialNo); }

补充:使用redis事物解决stringRedisTemplate.setIfAbsent()并设置过期时间遇到的问题

spring-date-redis版本:1.6.2

场景:

在使用setIfAbsent(key,value)时,想对key设置一个过期时间,同时需要用到setIfAbsent的返回值来指定之后的流程,所以使用了以下代码:

boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value); if(store){ stringRedisTemplate.expire(key,timeout); // todo something... }

这段代码是有问题的:当setIfAbsent成功之后断开连接,下面设置过期时间的代码stringRedisTemplate.expire(key,timeout); 是无法执行的,这时候就会有大量没有过期时间的数据存在数据库。想到一个办法就是添加事务管理,修改后的代码如下:

stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi(); boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value); if(store){ stringRedisTemplate.expire(key,timeout); } stringRedisTemplate.exec(); if(store){ // todo something... }

这样就保证了整个流程的一致性。本因为这样就可以了,可是事实总是不尽人意,因为我在文档中发现了以下内容:

加了事务管理之后,setIfAbsent的返回值竟然是null,这样就没办法再进行之后的判断了。

好吧,继续解决:

stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi(); String result = stringRedisTemplate.opsForValue().get(key); if(StringUtils.isNotBlank(result)){ return false; } // 锁的过期时间为1小时 stringRedisTemplate.opsForValue().set(key, value,timeout); stringRedisTemplate.exec(); // todo something...

上边的代码其实还是有问题的,当出现并发时,String result = stringRedisTemplate.opsForValue().get(key); 这里就会有多个线程同时拿到为空的key,然后同时写入脏数据。

最终解决方法:

使用stringRedisTemplate.exec();的返回值判断setIfAbsent是否成功

stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi(); stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event)); stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS); List result = stringRedisTemplate.exec(); // 这里result会返回事务内每一个操作的结果,如果setIfAbsent操作失败后,result[0]会为false。 if(true == result[0]){ // todo something... }

将redis版本升级到2.1以上,然后使用

直接在setIfAbsent中设置过期时间

update :

java 使用redis的事务时不能直接用Api中的multi()和exec(),这样multi()和exec()两次使用的stringRedisTemplate不是一个connect,会导致死锁,正确方式如下:

private Boolean setLock(RecordEventModel event) { String lockKey = event.getModel() + ":" + event.getAction() + ":" + event.getId() + ":" + event.getMessage_id(); log.info("lockKey : {}" , lockKey); SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() { List<Object> exec = null; @Override @SuppressWarnings("unchecked") public Boolean execute(RedisOperations operations) throws DataAccessException { operations.multi(); stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event)); stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS); exec = operations.exec(); if(exec.size() > 0) { return (Boolean) exec.get(0); } return false; } }; return stringRedisTemplate.execute(sessionCallback); }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持易盾网络。如有错误或未考虑完全的地方,望不吝赐教。