前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis分布式锁的正确实现方式

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

作者头像
stys35
发布2020-04-02 18:12:21
9860
发布2020-04-02 18:12:21
举报
文章被收录于专栏:工作笔记精华工作笔记精华

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

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

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

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

代码语言:javascript
复制
        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
 
代码语言:javascript
复制
 加锁代码:
   /**
      * 释放锁成功返回值
      */
     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的如下包装,具体参数的详解注解上有:

代码语言:javascript
复制
  /**
    * 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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档