基于Redis实现分布式锁的思考

synchronized虽然能够解决同步问题,但是每次只有一个线程访问,并且synchronized锁属于JVM锁,仅适用于单点部署;然而分布式需要部署多台实例,属于不同的JVM线程对象

  • 分布式锁
  • synchronized锁为什么不能应用于分布式锁?
  • 使用redis中setnx实现分布式锁。
    • 总结

    分布式锁

    基于redis实现分布式锁思考几个问题???

    synchronized锁为什么不能应用于分布式锁?

    synchronized虽然能够解决同步问题,但是每次只有一个线程访问,并且synchronized锁属于JVM锁,仅适用于单点部署;然而分布式需要部署多台实例,属于不同的JVM线程对象

    使用redis中setnx实现分布式锁。

    //设置分布式锁
    String lockKey = "product_001_key";
    //语义:如何不存在则存入缓存中,且返回true;
    //否则已存在,则返回false即加锁失败
    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "product_001_lock");
    if (!result) {
      //没有加锁成功,则返回提示等
    }
    try{
    
    }catch() {
    
    }finally{
      //释放锁
      stringRedisTemplate.delete(lockKey);
    }
    

    针对以上设置分布式锁思考一下问题?

    1.如果突然服务器宕机,那么必然造成锁无法释放,即造成死锁?

    解决方案:设置超时时间。

    //设置分布式锁
    String lockKey = "product_001_key";
    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "product_001_lock");
    //设置锁超时时间30s
    stringRedisTemplate.expire(lockKey,30, TimeUnit.SECONDS);
    if (!result) {
      //没有加锁成功,则返回提示等
    }
    try{
    
    }catch() {
    
    }finally{
      //释放锁
      stringRedisTemplate.delete(lockKey);
    }
    

    2.加锁和设置超时时间中间引起服务器宕机,则一样会导致死锁。

    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "product_001_lock");
    //------服务器宕机,则超时时间未设置成功-------
    //设置锁超时时间30s
    stringRedisTemplate.expire(lockKey,30, TimeUnit.SECONDS);
    

    解决方案:原子性操作,即同时加锁和设置超时时间;

    即上面的代码合并成一句操作:

    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"product_001_lock", 30, TimeUnit.SECONDS)
    

    3.思考超时时间设置是否合理呢?即线程执行时间和锁超时时间并非一致。

    场景:假设设置加锁超时时间10s;

    高并发场景下,线程A执行时间为15s,redis依据超时时间,将其线程A加的锁释放掉;然后线程B获取锁,并加锁成功,此时线程A执行结束,执行finally代码块就会将线程B加的锁释放。

    解决方案:设置线程随机ID,释放锁时判断是否为当前线程加的锁,即使存在线程A因线程执行时间超时被动释放其锁,但至少保证当前超时线程不会释放其他线程加的锁。但是面对线程执行时间大于设置的超时时间,也是会存在并发问题。

    String lockKey = "product_001";
    String clientId = UUID.randomUUID().toString();
    //设置超时时间,且加锁和设置线程ID
    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId, 30, TimeUnit.SECONDS)`
    
    if (!result) {
      //没有加锁成功,则返回提示等
    }
    try{
    
    }catch() {
    
    }finally{
      //释放锁:加锁线程ID和当前执行线程ID相同,才允许释放锁
     if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
            stringRedisTemplate.delete(lockKey);
        }
    }
    

    4.上面场景解决方案:加锁续命即续线程锁超时时间

    解决方案:加锁成功时,开启一个后台线程,每隔10s(自定义)判断当前线程是否还持有锁,持有锁则再续命30s等

    Redission实现分布式锁

    实现原理流程:

    基于 Redis 实现分布式锁的思考

    图片

       String lockKey = "product_001";
            //获取锁对象,并未加锁
            RLock redissonLock = redisson.getLock(lockKey);
            try {
                // **此时加锁**,实现锁续命功能
                redissonLock.lock();
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
                if (stock > 0) {
                    int realStock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", realStock + "");
                    System.out.println("扣减成功,剩余库存:" + realStock + "");
                } else {
                    System.out.println("扣减失败,库存不足");
                }
            }finally {
              //释放锁
                redissonLock.unlock();
            }
    

    总结

    综上,设计实现分布式锁需要满足以下条件:

    1. 互斥性;在任意时刻,只有一个客户端能持有锁。
    2. 不能发生死锁;即使存在一个线程持有锁的期间崩溃而没有主动解锁,也能保证后续其他线程能加锁。
    3. 加锁和解锁必须是同一个线程。

    内容出处:,

    声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。文章链接:http://www.yixao.net/procedure/24459.html

    发表评论

    登录后才能评论