浅析redis setIfAbsent的用法及在分布式锁上的应用及同步锁的缺陷

文章介绍了Redis的SETNX命令和Java中setIfAbsent方法在实现分布式锁中的应用,讨论了同步锁的问题,如可能出现的并发问题,并提出了使用setIfAbsent以避免重复设置。还提到了在分布式锁实现中需要注意的事务管理和并发竞争问题,以及解决方案。
摘要由CSDN通过智能技术生成

浅析redis setIfAbsent的用法及在分布式锁上的应用及同步锁的缺陷

一、业务场景:同步锁的问题与分布式锁的应用

1、redis的基本命令

(1)SETNX命令(SET if Not eXists)

语法:SETNX key value

功能:当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

(2)expire命令

语法:expire KEY seconds

功能:设置key的过期时间。如果key已过期,将会被自动删除。

(3)DEL命令

语法:DEL key [KEY …]

功能:删除给定的一个或多个 key,不存在的 key 会被忽略。

2、实现同步锁原理

(1)加锁:“锁”就是一个存储在redis里的key-value对,key是把一组操作用字符串来形成唯一标识,value其实并不重要,因为只要这个唯一的key-value存在,就表示这个操作已经上锁。

(2)解锁:既然key-value对存在就表示上锁,那么释放锁就自然是在redis里删除key-value对

(3)阻塞、非阻塞:阻塞式的实现,若线程发现已经上锁,会在特定时间内轮询锁。非阻塞式的实现,若发现线程已经上锁,则直接返回。

(4)处理异常情况:假设当投资操作调用其他平台接口出现等待时,自然没有释放锁,这种情况下加入锁超时机制,用redis的expire命令为key设置超时时长,过了超时时间redis就会将这个key自动删除,即强制释放锁
在这里插入图片描述
3、使用redis锁还是出现同步问题

一种可能是,2台机器同时访问,一台访问,还没有把锁设置过去的时候,另一台也查不到就会出现这个问题。

解决方法:这跟写代码的方式有关。先查,如果不存在就set,这种方式有极微小的可能存在时间差,导致锁set了2次。

推荐使用 setIfAbsent 这样在 redis set 的时候是单线程的,不会存在重复的问题。

二、setIfAbsent的使用
1、作用:如果为空就set值,并返回1;如果存在(不为空)不进行操作,并返回0。

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

2、setIfAbsent 和 setnx

setIfAbsent 是java中的方法

setnx 是 redis命令中的方法

redis> SETNX mykey "Hello"  // 不存在mykey,设置值并返回1
(integer) 1
redis> SETNX mykey "World"  // 存在mykey,不处理并返回0
(integer) 0
redis> GET mykey
"Hello"
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);
}

3、需要注意的是以前 stringRedisTemplate.setIfAbsent() 在服务器是由2个命令组成的 完成一个setnx时候在设置 expire 时候中间中断了,无法保证原子性。

故需要使用 4 个参数的那个重载方法,这个底层是 set key value [EX seconds] [PX milliseconds] [NX|XX] 是原子性的

public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) {
  byte[] rawKey = rawKey(key);
  byte[] rawValue = rawValue(value);
  Expiration expiration = Expiration.from(timeout, unit);
  return execute(connection -> connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent()), true);
} 

三、如何使用呢?分布式锁
1、应用场景:比如我们有2台服务器,4个镜像节点,那么就会有4个负载均衡的后台服务,如果你需要在代码里加一个定时任务。那么最后4个后台服务都会执行这个定时任务,就会重复4次。

2、怎么办呢?加分布式锁,就会用到上面的方法咯。

3、分布式锁实现中可能遇到的问题:看这篇文章,关于stringRedisTemplate.setIfAbsent()并设置过期时间遇到的问题:https://blog.csdn.net/weixin_34419326/article/details/88677793,主要注意一下几点:

(1)加了事务管理之后,setIfAbsent的返回值是null
在这里插入图片描述
(2)当出现并发时,String result = stringRedisTemplate.opsForValue().get(key); 这里就会有多个线程同时拿到为空的key,然后同时写入脏数据。

(3)最终解决方法:将redis版本升级到2.1以上,然后使用直接在setIfAbsent中设置过期时间

在这里插入图片描述

引用中提到Redisson实现了分布式可重入锁,比原生的SET mylock userId NX PX milliseconds lua实现的效果更好些。这里的SET mylock userId NX PX milliseconds是指使用RedisSET命令来实现分布式锁,其中NX表示只在key不存在时才设置值,PX milliseconds表示设置过期时间为milliseconds毫秒。而使用Redisson的setIfAbsent方法可以更方便地实现分布式锁。 使用setIfAbsent方法可以通过调用RedisSETNX命令来实现分布式锁,它会在key不存在时设置值。如果设置成功,表示获取到锁;如果设置失败,表示锁已经被其他进程获取。 以下是一个简单的示例代码,演示了如何使用setIfAbsent方法实现分布式锁: ```java RLock lock = redissonClient.getLock("my-lock"); boolean locked = lock.tryLock(); try { if (locked) { // 获取到锁,执行业务逻辑 } else { // 锁已被其他进程获取,执行其他逻辑 } } finally { if (locked) { lock.unlock(); } } ``` 在这个示例中,我们通过调用tryLock方法来尝试获取锁。如果获取成功,表示获取到锁,可以执行业务逻辑;如果获取失败,表示锁已经被其他进程获取,可以执行其他逻辑。 需要注意的是,Redisson的setIfAbsent方法是非阻塞的,它会立即返回获取锁的结果。如果需要阻塞等待获取锁,可以使用lock方法,并在finally语句块中调用unlock方法来释放锁。 综上所述,setIfAbsent方法是Redisson实现分布式锁的一种方式,它通过调用RedisSETNX命令来判断锁的获取情况,可以方便地实现分布式锁的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Redis实现分布式锁](https://blog.csdn.net/fourforfo/article/details/126022518)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值