专栏首页工作笔记精华Redis分布式锁的正确实现方式

Redis分布式锁的正确实现方式

1.前言 分布式锁一般有三种实现方式:

数据库乐观锁 基于Redis的分布式锁 基于Zookeeper的分布式锁 本文介绍的是基于Redis的分布式锁;

2.可靠性 首先,为了确保分布式锁可用,需要锁至少满足以下四个条件:

互斥性,在任意时刻,只有一个客户端能持有锁。 不会发生死锁,即使有一个客户端在持有锁的期间崩溃而没有主动释放锁,也能保证后续其它客户端能加锁。 具有容错性。只要大部分Redis节点正常运行,客户端就可以加锁和解锁。 解铃还需系铃人,加锁和解锁必须是同一个客户端(同一个线程),客户端自己不能把别人加的锁给解了。 3.代码实现 首先我们通过Maven引入redis starter,在pom.xml文件中加入下面的代码:

        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
 
 加锁代码:
   /**
      * 释放锁成功返回值
      */
     private static final Long RELEASE_LOCK_SUCCESS = 1L;
     /**
      * 自动过期释放锁成功返回值
      */
     private static final Long RELEASE_LOCK_AUTO_SUCCESS = 0L;
     /**
      * @Description 尝试获取分布式锁
      * @param redisTemplate Redis客户端对象
      * @param lockKey 锁
      * @param value 唯一标识
      * @param expireTime 过期时间
      * @param util 单位
      * @return 是否获取成功
      */
     public static Boolean tryLock(RedisTemplate redisTemplate, String lockKey, String value, long expireTime, TimeUnit util){
         long currentTime = System.currentTimeMillis();
         Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, util);
         if(System.currentTimeMillis() - currentTime >= expireTime){
             return Boolean.FALSE;
         }
         if(Boolean.TRUE.equals(result)){
             return Boolean.TRUE;
         }
         return Boolean.FALSE;
     }

我们可以看到加锁也就一行是有效代码,使用了setIfAbsent方法,也就是只有在lockKey不存在时才加锁,第二个为value,这个也是很有用的,解铃还须系铃人就是通过这个值来区分,客户端不可以解锁其它人的锁;第三个参数是过期时间;第四个参数是过期时间单位;

其实setIfAbsent底层实现方法是对Jedis的如下包装,具体参数的详解注解上有:

  /**
    * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
    * GB).
    * @param key
    * @param value
    * @param nxxx NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key
    *          if it already exist.
    * @param expx EX|PX, expire time units: EX = seconds; PX = milliseconds
    * @param time expire time in the units of <code>expx</code>
    * @return Status code reply
    */
   public String set(final String key, final String value, final String nxxx, final String expx,
       final long time) {
     checkIsInMultiOrPipeline();
     client.set(key, value, nxxx, expx, time);
     return client.getStatusCodeReply();
   }

 解锁代码:
lua脚本:
if redis.call("get",KEYS[1]) == ARGV[1] then
     return redis.call("del",KEYS[1])
 else
     return 0
 end

 java代码:
    /**
      * 释放锁lua脚本
      */
     private static final String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
         /**
      * @Description 释放锁
      * @param redisTemplate Redis客户端对象
      * @param lockKey 锁
      * @param value 唯一标识
      * @return
      */
     public static Boolean releaseLock(RedisTemplate redisTemplate, String lockKey, String value){
         DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
         Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
         //释放锁成功,或锁自动过期
         if(RELEASE_LOCK_SUCCESS.equals(result) || RELEASE_LOCK_AUTO_SUCCESS.equals(result)){
             return Boolean.TRUE;
         }
         return Boolean.FALSE;
     }
 
 可以看到解锁也很简单,一条lua脚本加上execute方法就完成了锁删除;那么为什么要使用lua脚本?因为要确保上述操作的原子性;lua代码被当成一个命令区执行,并且直到eval命令执行完成,Redis才会执行其它命令。

当然上面的实现方式也不是很严谨,如加锁后业务逻辑还未执行完成锁已经过期,这会导致其它的客户端拿到锁;如果是单节点这样做问题不大,但是如果是个集群,加锁首先会落盘到master节点,然后再复制到salve节点,如果在未复制之前master节点挂掉,那么就会导致锁丢失的问题。为了解决上面的这些问题Redis官方推荐使用Redisson分布式锁,这是官方推荐的组件。

参考:https://redis.io/topics/distlock

Redisson:https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8

GitHub地址:https://github.com/mingyang66/spring-parent/tree/master/sgrain-spring-boot-redis-service/doc

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • YARN 内存参数终极详解 转

    Hadoop框架自身集成了很多第三方的JAR包库。Hadoop框架自身启动或者在运行用户的MapReduce等应用程序时,会优先查找Hadoop预置的JAR包。...

    stys35
  • Redis实现分布式锁3-使用LUA脚本实现分布式锁,解决原子性问题

    stys35
  • Springboot+Mybatis+MySql下,mysql使用json类型字段存取的处理

    1、mysql5.7开始支持json类型字段; 2、mybatis暂不支持json类型字段的处理,需要自己做处理

    stys35
  • 【转】JAVA之网络编程

    网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习。

    shirayner
  • IM系统如何调试TCP协议

    20字节的header,包括headLength包头长度,clientVersion客户端版本,cmdId命令Id号,seq消息序列号,bodyLength消息...

    普通程序员
  • 图解从 URL 到网页通信原理

    互联网的原始目的,就是为了传输文本(文本对话)。那我们使用浏览器发送请求后页面是如何呈现在我们面前的呢? 接下来由图片介绍下URL到呈现页面的过程。

    芋道源码
  • 一篇文章让你彻底弄懂SSL/TLS协议

    SSL/TLS是一种密码通信框架,他是世界上使用最广泛的密码通信方法。SSL/TLS综合运用了密码学中的对称密码,消息认证码,公钥密码,数字签名,伪随机数生成器...

    程序那些事
  • C++引用计数(reference counting)技术简介(2)

    Reference-counting可用于字符串以外的场合,任何class如果其不同的对象可能拥有相同的值,都适用此技术。但是如果重写class以便适用refe...

    Dabelv
  • java开发_UUID(Universally Unique Identifier,全局唯一标识符)和GUID(Globally Unique Identifier,全球唯一标识符)

    GUID: 即Globally Unique Identifier(全球唯一标识符) 也称作 UUID(Universally Unique IDentifie...

    Hongten
  • 文件的基本操作

        w  ---- 只写模式,不能调用read()进行读操作,如果打开一个已存在的文件,会先清空内容

    py3study

扫码关注云+社区

领取腾讯云代金券