Redis实现分布式锁🔒

Redis实现分布式锁🔒

[toc]

本篇文章将简单的通过Spring Boot 项目展示三种常见的redis分布式锁的实现

一. SETNX

语法:SETNX key value

key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

  • 可用版本:

    = 1.0.0

  • 时间复杂度:

    O(1)

  • 返回值:

    设置成功,返回 1 。设置失败,返回 0

在Spring Boot项目中实现

  1. 加入引用
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 实现具体的逻辑
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void demo() {
        String lockKey = "lock";
        String clientId = UUID.randomUUID().toString();
        try{
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,30,TimeUnit.SECOND);
                        //如果设置失败,则说明锁已经被占用,直接返回‘失败’
            if(!result){
                return "error_code";
            }
          
            //获取锁成功的情况下,正常的执行逻辑

        }catch(Exception e){

        }finally{
            if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
                stringRedisTemplate.delete(lockKey);
            }
        }
    }

存在的问题:

  • A进程设置的锁,因为超时,被自动释放。B进程设置新的锁,然后A结束后释放了B进程的锁。形成连锁反应
  • A进程设置的锁,因为超时,被自动释放。B进程设置新的锁,提前开始执行。致使锁的同步功能失效。

解决方法:

  • 使用UUID,每一次生成只有自己知道的特殊值,释放时判断(上例代码已实现)
  • 开启一个守护线程,每隔更短的时间,监测锁存不存在,若存在则加时。简称锁续命

二. Redisson

网站: redisson.org

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

在Spring Boot项目中实现

  1. 加入引用
<dependency>
    <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.6.5</version>
</dependency>
  1. 配置SpringBean
@Bean
public Redisson redisson(){
  //此为单机模式
  Config config = new Config();
  config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
  return (Redisson)Redisson.create(config);
}
  1. 实现具体的逻辑
@Autowired
private Redisson redisson;

public String RedissonDemo () {
   String lockKey = "product_001";
   String clientId = UUID.randomUUID().toString();
   RLock redissonLock = redisson.getLock(lockKey);
   try {
      redissonLock.lock();
      //业务逻辑
   } finally {
      redissonLock.unlock();
   }
}

使用Redisson的逻辑图如下:

image.png

存在的问题:

  • 性能问题:多个请求,只有第一个会成功,其他的自循等待
  • 主从架构问题:主节点还没有将锁给出去的信息同步给从节点就挂了,其他的从节点不知道该锁已经给出去了,当另一个客户端申请时,新的主节点重复的给出了锁的权限。

三. Redlock

官方给出了Redlock算法,大致意思如下:

在分布式版本的算法里我们假设我们有N个Redis Master节点,这些节点都是完全独立的,我们不用任何复制或者其他隐含的分布式协调算法。我们把N设成5,因此我们需要在不同的计算机或者虚拟机上运行5个master节点来保证他们大多数情况下都不会同时宕机。一个客户端需要做如下操作来获取锁:

1、获取当前时间(单位是毫秒)。

2、轮流用相同的key和随机值(客户端的唯一标识)在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。

3、客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。

4、如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。

5、如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

虽然说RedLock算法可以解决单点Redis分布式锁的高可用问题,但如果集群中有节点发生崩溃重启,还是会出现锁的安全性问题。具体出现问题的场景如下:

假设一共有A, B, C, D, E,5个Redis节点,设想发生了如下的事件序列:

1、客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)

2、节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了

3、节点C重启后,客户端2锁住了C, D, E,获取锁成功

这样,客户端1和客户端2同时获得了锁(针对同一资源)。针对这样场景,解决方式也很简单,也就是让Redis崩溃后延迟重启,并且这个延迟时间大于锁的过期时间就好。这样等节点重启后,所有节点上的锁都已经失效了。也不存在以上出现2个客户端获取同一个资源的情况了。

通过redisson来使用Redlock算法实例

    public String redLock() {
        String lockKey = "product_001";
        //这里实际上是需要自己实例子化不同redis实例的redisson客户端连接,这里只是伪代码用一个redisson客户端简化
        RLock lock1 =redisson.getLock(lockKey);
        RLock lock2 =redisson.getLock(lockKey);
        RLock lock3 =redisson.getLock(lockKey);
        
        /**
         * 根据多个RLock对象构建RedissonRedLock(最核心)
         */
        RedissonRedLock redLock = new RedissonRedLock(lock1,lock2,lock3);
        try{
            /**
             * waitTimeout (第一个)尝试获取锁的最大等待时间,超过这个时间,则认为获取锁失败
             * leaseTime (第二个)锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务可以完成
             */
            boolean res = redLock.tryLock(10,30, TimeUnit.SECONDS);
            
            if(res){
                //成功获取锁,此处处理业务
            }
        } catch (Exception e) {
            throw new RuntimeException("lock fail");
        } finally {
            redLock.unlock();
        }
        return "end" ;

    }

四. 总结

以上是使用Redis实现分布式锁的几种常见的方式,它实际上是基于缓存的锁。其特点总结如下:

  • 优点:性能高,实现起来较为方便,在允许偶发的锁失效情况,不影响系统正常使用,建议采用缓存锁。

  • 缺点:通过锁超时机制不是非常可靠,当线程获得锁后,处理时间过长导致锁超时,就失去了锁的作用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352