详解redis分布式锁(优化redis分布式锁的过程及Redisson使用)

目录
  • 1. redis在实际的应用中
  • 2.如何使用redis的功能进行实现分布式锁
    • 2.1 redis分布式锁思想
      • 2.1.1设计思想:
      • 2.1.2 根据上面的设计思想进行代码实现
    • 2.2 使用redisson进行实现分布式锁

    1. redis在实际的应用中

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

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

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

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

       @getmapping("getint0")
        public string test() {
            synchronized (this) {
                //获取当前商品的数量
                int productnum = integer.parseint(stringredistemplate.opsforvalue().get("product"));
                //然后对商品进行出库操作,即进行减1
                /*
                 * a业务逻辑
                 *
                 * */
                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设计思想:

    • 在请求相同功能的接口时,使用redis的setnx命令,如果使用setnx命令后返回的是为true,说明此时没有其他的调用这个接口,就相当于获取到锁了,然后就可以继续执行接下来的业务逻辑了。当执行完业务逻辑后,在返回数据前,就把key删除了,然后其他的请求就能获取到锁了。
    • 如果使用setnx命令,返回的是false,说明此时有其他的消费者正在调用这个接口,因此需要等待其他消费者顺利消费完成后,才能获取到分布式的锁。

    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
            /*
            * a业务逻辑
            *
            * */
            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
                /*
                 * b业务逻辑
                 * */
                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
                /*
                 * c业务逻辑
                 * */
                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
                /*
                 * c业务逻辑
                 * */
    
                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配置

    @configuration
    public 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
                /*
                 * c业务逻辑
                 * */
                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分布式锁详解(优化redis分布式锁的过程及redisson使用)的文章就介绍到这了,更多相关redis分布式锁内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

    (0)
    上一篇 2022年3月21日
    下一篇 2022年3月21日

    相关推荐