千家信息网

怎么使用redis分布式锁

发表于:2024-11-11 作者:千家信息网编辑
千家信息网最后更新 2024年11月11日,本篇内容介绍了"怎么使用redis分布式锁"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. red
千家信息网最后更新 2024年11月11日怎么使用redis分布式锁

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

    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依赖

                            org.redisson                        redisson                        3.11.1                

    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            /*             * 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分布式锁"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

    分布式 业务 功能 代码 命令 消费 商品 逻辑 库存 数据 方法 消费者 时间 相同 情况 生产者 生产 成功 数量 指令 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 微软如何提升网络安全 电脑自制监控服务器 网络安全学专业有什么内容 网络安全主题创意绘画简单又好看 数据库怎么查建表时间查询 为什么兴趣部落连不上服务器 国家网络安全教育教案 wow at服务器 宁德电大网络安全章节测验 湖南今潮互联网科技有限公司 天津人人车网络技术公司 网络安全培训课程体系 出入库软件开发 中国网络安全第一人郭盛华 成都奇马网络技术有限公司 成都网络安全专业好就业吗 河南创航网络技术有限公司 网络技术部门级别 十三五时期网络安全工作成就 服务器的工业管理 上海网络服务器机柜哪里有 关系数据库中数据逻辑关系 共建网络安全手抄报视频 怎么购买境外服务器上外网 天津人人车网络技术公司 nas能装数据库不 汕头无限软件开发价钱 小小橙软件开发有限公司 vfp怎么将表添加到数据库中 网彩软件开发
    0