• 浅析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中设置过期时间

  • 相关阅读:
    h5 input调起摄像头、摄像机、录音机
    基数排序
    快速排序 && 希尔排序 && 插入排序
    堆排序
    归并排序(Merge sort)
    动态规划:背包问题
    Chap5: question: 29
    排列 && 组合
    Chap4: question: 19
    Chap3: question: 11
  • 原文地址:https://www.cnblogs.com/goloving/p/16026003.html
Copyright © 2020-2023  润新知