Redis分布式锁那些事(一)

随着项目规模扩大,分布式、集群环境下的并发控制成为挑战。本文深入探讨了如何使用Redis实现分布式锁,以解决类似电商下单扣库存的事务一致性问题。通过setnx命令和lua脚本确保锁的原子性和幂等性,同时讨论了过期时间和锁删除的策略,以及处理服务器异常情况的方法。
摘要由CSDN通过智能技术生成

背景

随着用户量的慢慢增加,项目的架构也逐渐会演变分布式、集群方式,这就会产生集群情况下如何保证事务仅仅执行一次的问题。比如老生常谈的电商系统中的下单扣减商品库存,等等。有很多种解决方案,单思想基本一样。

场景

用户A 进行购买商品D,在提交订单的时候用户B同时也进行购买商
品D。在单台服务器时使用java自有的synchronized或Lock的相关api即可控制并发问题,集群情况下A用户可能调用的是1机器的接口,用户B可能调用的是2机器的接口。synchronized或Lock就不能满足我们想要的结果,这是就需要用到分布式锁,下面就基于redis来进行探讨

单节点redis

主要就是用redis中的setnx进行实现,如果你以为就简单调用这个方法就完事了嘛,那对不起,服务器环境会教你如何笑着活下去。以下是作者使用的方式从初学者到放弃。
(1)处理业务之前首先setnx获取锁,当处理完之后进行del操作。不考虑服务器环境的前提下需要注意的是 一定 一定 一定要进行del操作。一般是进行 try(操作业务)catch-finally(del锁)操作,前提是不考虑服务器环境的前提。比如服务器不会挂。redis不会挂。下面是考虑到服务器环境的问题
(2)在(1)的情况下加入setnx获取锁之后 服务器宕机了,那么这个锁就会一直存在。那这肯定是不行的。有人该说了加个过期时间就好了。当然加过期时间肯定是没毛病的。但是如何设置,以及过期时间的时间长短如何设置这也是个问题,是不是简单的 先setnx 然后 expire呢,答案不是 如果在setnx成功后服务器宕机或者执行expire失败呢(finally 中del操作就再说了)。
redis官网中提到过 SET key my_random_value NX PX 30000
一些第三方已经对该命令进行封装如spring RedisTemplate.setIfAbsent(),有的一些是没有进行封装如jedis(作者现在用的是没有,不知道最新的版本有没有进行封装)那就只能自己封装接口。
上面还说过过期时间买的长短该如何设置呢。设置时间始终大于业务执行时间还好说,如果设置的时间小于业务执行时间该如何呢。是不是我这业务还没执行完。又会有一个请求进来。这就没有做到并发控制的效果。那该如何操作呢。那你该想到了设置时间大一点,执行完后释放锁不就完了。真巧我也是这么想的。但是删除并不是随便删除的。例如:用户A 获取锁并且设置过期时间3秒钟(业务正常是2秒钟),由于网络或者其他原因导致本次业务执行超过3秒。三秒后用户B进行操作请求锁并且设置成功。但这时用户A执行完业务进行删除锁。那是不是就有问题。就好比我们用要是开门一样。我的钥匙开我的门。那我要用我的钥匙开了你家的门,那不就事大了嘛。
所以redis作者提到过设置所得值是使用随机数my_random_value ,并且这个随机值是我独有的,原文 This value must be unique across all clients and all lock requests。在删除之前先进行获取改值与本地进行比较如果一样则进行删除。如果不一样则不删除。其中获取值和删除key需要是原子操作。这就需要使用lua脚本代码如下

private static String SET_NX_LUA = "" +
            "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
            "    return redis.call(\"del\",KEYS[1])\n" +
            "else\n" +
            "    return 0\n" +
            "end";

    public boolean removeNx(String key,String value){
        List<String> keys = new ArrayList<>();
        keys.add(key);
        List<String> values = new ArrayList<>();
        values.add(value);
        Object eval = jedisCluster.eval(SET_NX_LUA, keys, values);
        String s = eval.toString();
        return !"0".equals(s);
    }

这样单节点情况下redis的分布式锁算是基本完成。如果有其他情况欢迎留言一起探讨。集群的情况下后续在进行分析


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值