文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么使用redis分布式锁

2023-06-25 14:55

关注

本篇内容介绍了“怎么使用redis分布式锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1. redis在实际的应用中

不仅可以用来缓存数据,在分布式应用开发中,经常被用来当作分布式锁的使用,为什么要用到分布式锁呢?

在分布式的开发中,以电商库存的更新功能进行讲解,在实际的应用中相同功能的消费者是有多个的,假如多个消费者同一时刻要去消费一条数据,假如业务逻辑处理逻辑是查询出redis中的商品库存,而如果第一个进来的消费的消费者获取到库存了,还没进行减库存操作,相对晚来的消费者就获取了商品的库存,这样就导致数据会出错,导致消费的数据变多了。

例如:消费者A和消费者B分别去消费生产者C1和生产者C2的数据,而生产者都是使用同一个redis的数据库的,如果生产者C1接收到消费者A的消息后,先进行查询库存,然后当要进行减库存的时候,因为生产者C2接收到消费者B的消息后,也去查询库存,而因为生产者C1还没有进行库存的更新,导致生产者C2获取到的库存数是脏数据,而不是生产者C1更新后的数据,导致业务出错。

怎么使用redis分布式锁

如果不是分布式的应用,可以使用synchronized进行防止库存更新的问题的产生,但是synchronized只是基于JVM层面的,如果在不同的JVM中,就不能实现这样的功能。

   @GetMapping("getInt0")    public String test() {        synchronized (this) {            //获取当前商品的数量            int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));            //然后对商品进行出库操作,即进行减1                        if (productNum > 0) {                stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));                int productNumNow = productNum - 1;            } else {                return "product=0";            }            int productNumNow = productNum - 1;            return "success=" + productNumNow;        }    }

2.如何使用redis的功能进行实现分布式锁

2.1 redis分布式锁思想

如果对redis熟悉的话,我们能够想到redis中具有setnx的命令,该命令的功能宇set功能类似,但是setnx的命令在进行存数据前,会检查redis中是否已经存在相同的key,如存在的话就返回false,反之则返回true,因此我们可以使用该命令的功能,设计一个分布式锁。

2.1.1设计思想:
2.1.2 根据上面的设计思想进行代码实现

代码片段【1】

  @GetMapping("getInt1")    public String fubushisuo(){        //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false        String lockkey = "yigehaimeirumengdechengxuyuan";        String lockvalue = "yigehaimeirumengdechengxuyuan";        boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue);        //如果能够成功的设置lockkey,这说明当前获取到分布式锁        if (!opsForSet){            return "false";        }        //获取当前商品的数量        int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));        //然后对商品进行出库操作,即进行减1                if (productNum>0){            stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));            int productNumNow = productNum - 1;        }else {            return "product=0";        }        //然后进行释放锁        stringRedisTemplate.delete(lockkey);        int productNumNow = productNum-1;        return "success="+productNumNow;    }
2.1.2.1反思代码片段【1】

如果使用这种方式,会产生死锁的方式:
死锁发生的情况:
(1) 如果在a业务逻辑出现错误时,导致不能执行delete()操作,使得其他的请求不能获取到分布式锁,业务lockkey一直存在于reids中,导致setnx操作一直失败,所以不能获取到分布式锁
(2) 解决方法,使用对业务代码进行try…catch操作,如果出现错误,那么使用finally对key进行删除

优化代码【2】

 @GetMapping("getInt2")    public String fubushisuo2(){        //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false        String lockkey = "yigehaimeirumengdechengxuyuan";        String lockvalue = "yigehaimeirumengdechengxuyuan";        boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue);        int productNumNow = 0;        //如果能够成功的设置lockkey,这说明当前获取到分布式锁        if (!opsForSet){            return "false";        }        try {            //获取当前商品的数量            int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));            //然后对商品进行出库操作,即进行减1                        if (productNum>0){                stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));                productNumNow = productNum-1;            }else {                return "product=0";            }        }catch (Exception e){            System.out.println(e.getCause());        }finally {                //然后进行释放锁            stringRedisTemplate.delete(lockkey);        }        return "success="+productNumNow;    }
2.1.2.2反思代码【2】

出现问题的情况:
如果这种情况也有会产生的情况,如果此时有多台服务器都在运行该方法,
其中有一个方法获取到了分布式锁,而在运行下面的业务代码时,此时该服务器突然宕机了,导致其他的不能获取到分布式锁,

解决方法:加上过期时间,但又服务宕机了,过了设置的时间后,redis会可以把key给删除,这样其他的的服务器就可以正常的进行上锁了。

优化代码【3】

 @GetMapping("getInt3")    public String fubushisuo3(){        //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false        String lockkey = "yigehaimeirumengdechengxuyuan";        String lockvalue = "yigehaimeirumengdechengxuyuan";       //[01] boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue);        //设置过期时间为10秒,但是如果使用该命令,没有原子性,可能执行expire前宕机了,而不是设置过期时间,       //[02] stringRedisTemplate.expire(lockkey, Duration.ofSeconds(10));        //使用setIfAbsent(lockkey,lockvalue,10,TimeUnit.SECONDS);代码代替上面[01],[02]行代码        Boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, lockvalue, 10, TimeUnit.SECONDS);        int productNumNow = 0;        //如果能够成功的设置lockkey,这说明当前获取到分布式锁        if (!opsForSet){            return "false";        }        try {            //获取当前商品的数量            int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));            //然后对商品进行出库操作,即进行减1                        if (productNum>0){                stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));                productNumNow = productNum-1;            }else {                return "product=0";            }        }catch (Exception e){            System.out.println(e.getCause());        }finally {                //然后进行释放锁            stringRedisTemplate.delete(lockkey);        }                return "success="+productNumNow;    }
2.1.2.3 反思优化代码【3】

出现问题的情况:
如果c业务逻辑持续超过了设置时间,导致redis中的lockkey过期了,
而其他的用户此时访问该方法时获取到锁了,而在此时,之前的的c业务逻辑也执行完成了,但是他会执行delete,把lcokkey删除了。导致分布式锁出错。
例子:在12:01:55的时刻,有一个A来执行该getInt3方法,并且成功获取到锁,但是A执行了10秒后还不能完成业务逻辑,导致redis中的锁过期了,而在11秒的时候有B来执行getint3方法,因为key被A删除了,导致B能够成功的获取redis锁,而在B获取锁后,A因为执行完成了,然后把reids中的key给删除了,但是我们注意的是,A删除的锁是B加上去的,而A的锁是因为过期了,才被redis自己删除了,因此这导致了C如果此时来时也能获取redis分布式锁

解决方法:使用UUID,产生一个随机数,当要进行delete(删除)redis中key时,判断是不是之前自己设置的UUID

代码优化【4】

  @GetMapping("getInt4")    public String fubushisuo4(){        //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false        String lockkey = "yigehaimeirumengdechengxuyuan";        //获取UUID        String lockvalue = UUID.randomUUID().toString();        Boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, lockvalue, 10, TimeUnit.SECONDS);        int productNumNow = 0;        //如果能够成功的设置lockkey,这说明当前获取到分布式锁        if (!opsForSet){            return "false";        }        try {            //获取当前商品的数量            int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));            //然后对商品进行出库操作,即进行减1                        if (productNum>0){                stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));                productNumNow = productNum-1;            }else {                return "product=0";            }        }catch (Exception e){            System.out.println(e.getCause());        }finally {                //进行释放锁            if (lockvalue==stringRedisTemplate.opsForValue().get(lockkey)){                stringRedisTemplate.delete(lockkey);            }        }        return "success="+productNumNow;    }
2.1.2.4 反思优化代码【4】

出现问题的情况:
此时该方法是比较完美的,一般并发不是超级大的情况下都可以进行使用,但是关于key的过期时间需要根据业务执行的时间,进行设置,防止在业务还没执行完时,key就过期了.

解决方法:目前有很多redis的分布式锁的框架,其中redisson用的是比较多的

2.2 使用redisson进行实现分布式锁

先添加redisson的maven依赖

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.11.1</version></dependency>

redisson的bean配置

@Configurationpublic class RedissonConfigure {    @Bean    public Redisson redisson(){        Config config = new Config();        config.useSingleServer().setAddress("redis://27.196.106.42:6380").setDatabase(0);        return (Redisson) Redisson.create(config);    }}

实现分布式锁代码如下

 @GetMapping("getInt5")    public String fubushisuo5(){        //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false        String lockkey = "yigehaimeirumengdechengxuyuan";        //获取UUID        RLock lock = redisson.getLock(lockkey);        lock.lock();        int productNumNow = 0;        //如果能够成功的设置lockkey,这说明当前获取到分布式锁        try {            //获取当前商品的数量            int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));            //然后对商品进行出库操作,即进行减1                        if (productNum>0){            stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));            productNumNow = productNum-1;            }else {                return "product=0";            }        }catch (Exception e){            System.out.println(e.getCause());        }finally {           lock.unlock();            }        //然后进行释放锁        return "success="+productNumNow;    }

从面就能看到,redisson实现分布式锁是非常简单的,只要简单的几条命令就能实现分布式锁的功能的。
redisson实现分布式锁的只要原理如下:
redisson使用了Lua脚本语言使得命令既有原子性,redisson获取锁时,会给key设置30秒的过期是按,同时redisson会记录当前请求的线程编号,然后定时的去检查该线程的状态,如果还处于执行状态的话,而且key差不多要超期过时时,redisson会修改key的过期时间,一般增加10秒。这样就可以动态的设置key的过期时间了,弥补了优化代码【4】的片段

“怎么使用redis分布式锁”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯