浅析redis setIfAbsent的用法及在分布式锁上的应用(同步锁的问题)

2022/3/20 2:27:56

本文主要是介绍浅析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中设置过期时间



这篇关于浅析redis setIfAbsent的用法及在分布式锁上的应用(同步锁的问题)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程