1.前言

对于Redis完成分布式锁的几种方案这个论题,打开之前我想先简略聊聊什么是分布式锁,分布式锁的使用场景,除了Redis外还有什么技能完成分布式锁等一系列内容。

1.1分布式锁

说大一点,便是在现在发展越来越敏捷的大背景下,去中心化分布式体系越来越遍及,在咱们实践的生产开发傍边,有一种不可避免的场景便是多个进程互斥的对其资源的使用,为了确保数据不重复,要求在同一时刻,同一使命只在一个节点上运转,且确保在多进程下的数据安全,分布式锁就十分重要了。

1.2分布式锁的几种方案

办法有很多种,依据技能角度的不同

有根据MySQL的办法,经过表的唯一索引,经过insert和delete就能够完成加锁和解锁的作用;

有根据zookeeper的办法,经过创立临时有序节点,判断创立的节点序号是否最小。若是,则表明获取到锁,不是,则watch /lock目录下序号比本身小的前一个节点,解锁只需要删除节点;

有根据Redis的办法。经过履行setnx,若成功再履行expire添加过期时刻的办法加锁,解锁履行delete指令。

办法有很多,不一一列举了。

1.3Redis分布式锁需要满意的条件

  • 互斥性。在恣意时刻,只要一个客户端能持有锁。
  • 不发生死锁。即使有一个客户端在持有锁的期间溃散而没有主动解锁也能确保后续其他客户端能加锁。
  • 同一性。加锁和解锁必须是同一个客户端,客户端自己不能把他人加的锁给解了,即不能误解锁。
  • 容错性。只需大多数Redis节点正常运转,客户端就能够获取和开释锁。

2.Redis完成分布式锁的几种方案

能够经过以下办法完成(包含但不限于):

  • SETNX + EXPIRE
  • SETNX + value(体系时刻+过期时刻)
  • 经过开源结构-Redisson

简略来说说,用Java代码演示:

2.1 SETNX + EXPIRE

setnx(SET IF NOT EXISTS)+ expire指令。先用setnx来抢锁,假如抢到锁,再用expire给锁设置一个过期时刻,这样持有锁超不时开释锁,避免锁忘记开释。但此刻setnxexpire两个指令无法确保原子性,例如:

if(jedis.setnx(key_resource_id,lock_value) == 1){ //加锁
    expire(key_resource_id,100); //设置过期时刻
    try {
        //事务代码块
    }catch() {
    }finally {
       jedis.del(key_resource_id); //开释锁
    }
}

2.2 SETNX + value(体系时刻+过期时刻)

能够把过期时刻放到setnx的value值里边。假如加锁失败,再拿出value值校验一下即可。加锁代码如下:

long expires = System.currentTimeMillis() + expireTime; //体系时刻+设置的过期时刻
String expiresStr = String.valueOf(expires);
// 假如当时锁不存在,则加锁成功
if (jedis.setnx(key_resource_id, expiresStr) == 1) {
        return true;
} 
// 假如锁现已存在,获取锁的过期时刻
String currentValueStr = jedis.get(key_resource_id);
// 假如获取到的过期时刻,小于体系当时时刻,表明现已过期
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
     // 锁已过期,获取上一个锁的过期时刻,并设置现在锁的过期时刻
    String oldValueStr = jedis.getSet(key_resource_id, expiresStr);
    if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
         // 考虑多线程并发的状况,只要一个线程的设置值和当时值相同,它才能够加锁
         return true;
    }
}
//其他状况均回来加锁失败
return false;

2.3 经过开源结构-Redisson

那么此刻就要去想了,假如现已超越了加锁的过期时刻,但是事务还没履行完成,这个时分怎么做呢?是把过期时刻延长吗?明显不合理,能够经过开源结构-Redisson优化这个问题,简略来说,Redisson便是当一个线程获得锁以后,给该线程敞开一个守时守护线程,每隔一段时刻检查锁是否还存在,存在则对锁的过期时刻延长,避免锁过期提早开释。假设两个线程抢夺统一公共资源:线程A获取锁,并经过哈希算法挑选节点,履行Lua脚本加锁,同时其看门狗机制会发动一个watch dog(后台线程),每隔10秒检查线程,假如线程A还持有锁,那么就会不断的延长锁key的生存时刻。线程B获得锁失败,就会订阅解锁音讯,当获取锁到剩余过期时刻后,调用信号量办法阻塞住,直到被唤醒或等候超时。一旦线程A开释了锁,就会广播解锁音讯。所以,解锁音讯的监听器会开释信号量,获取锁被阻塞的线程B就会被唤醒,并重新测验获取锁。

Redisson 支撑单点形式、主从形式、哨兵形式、集群形式,假设现为单点形式:

//结构Config
Config config = new Config();
config.useSingleServer().setAddress("redis://ip:port").setPassword("Password.~#").setDatabase(0);
//结构RedissonClient
RedissonClient redissonClient = Redisson.create(config);
//获取锁实例
RLock rLock = redissonClient.getLock(lockKey);
try {
    //获取锁,waitTimeout为最大等候时刻,超越这个值,则认为获取锁失败。leaseTime为锁的持有时刻
    boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);
    if (res) {
        //事务块
    }
} catch (Exception e) {
}finally{
    //解锁
    rLock.unlock();
}

3.小结

Redis的分布式锁完成办法有很多,这里不一一列举了,有时机再打开Lua脚本、分布式锁Redlock等内容。

Enjoy GreatSQL :)

关于 GreatSQL

GreatSQL是由万里数据库保护的MySQL分支,专心于提高MGR可靠性及性能,支撑InnoDB并行查询特性,是适用于金融级使用的MySQL分支版本。

相关链接: GreatSQL社区 Gitee GitHub Bilibili

GreatSQL社区:

Redis实现分布式锁的几种方案

社区有奖主张反馈: greatsql.cn/thread-54-1…

社区博客有奖征稿详情: greatsql.cn/thread-100-…

社区2022年度勋章获奖名单: greatsql.cn/thread-184-…

(对文章有疑问或许有独到见解都能够去社区官网提出或共享哦~)

技能交流群:

微信&QQ群:

QQ群:533341697

微信群:添加GreatSQL社区帮手(微信号:wanlidbc )好友,待社区帮手拉您进群。