前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入浅出 超详细 从 线程锁 到 redis 实现分布式锁(篇节 2)「建议收藏」

深入浅出 超详细 从 线程锁 到 redis 实现分布式锁(篇节 2)「建议收藏」

作者头像
全栈程序员站长
发布2022-08-31 16:38:51
4020
发布2022-08-31 16:38:51
举报

大家好,又见面了,我是你们的朋友全栈君。

redis 实现 分布式锁

上节 我们讲了 线程锁 在单体项目中的作用,和 放在 分布式 项目里产生的问题,那接下来我们就来解决 分布式 架构上怎么 保证 数据的一直性

使用 redisTemplate 实现

代码语言:javascript
复制
// 设置锁
setIfAbsent("lock", "1213")---> SETNX lock "1213"
// 释放锁
redisTemplate.delete("lock");
代码语言:javascript
复制
@GetMapping("/cut")
    public Object kc() { 
   
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        /** * 设置锁,如果 lock 不存在的话,设置 lock=1213 并返回 true * 如果存在的话:就不操作 直接返回 false */
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1213");
        if(!lock){ 
   
            // 锁存在
            return "服务繁忙,请稍后再试";
        }

        int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
        if (num > 0) { 
   
            int lastNum = num - 1;
            redisTemplate.opsForValue().set("num", lastNum + "");
            System.out.println("扣减库存成功,剩余库存为:" + lastNum);
        } else { 
   
            System.out.println("扣减库存失败,库存不足");
        }

        // 释放锁
        redisTemplate.delete("lock");

        return "ok";
    }

简述一下逻辑: 第一个请求 进来,设置一把锁 进行锁住,然后 往下执行减库存的操作,此时 第二个线程进来获取锁,但是第一请求没有释放锁,所以第二个请求获取锁就会失败 得到返回值为 false,进入if 方法体 直接返回了。其他线程也是如此,直到 第一个线程释放锁后 其他线程才有获取锁的机会,每次只有一个线程能够成功获取锁,其他线程获取不到直接返回。

大家 觉得 这把锁怎么样,是不是解决问题了呢?还有什么问题吗?有没有那个小可爱想到了呢?

肯定有小伙伴想到了。

情况1:一个请求进来,获取锁,执行扣减库存的操作,在它 释放锁之前 服务突然抛异常了呢?
  • 那就 使用 try{}finally{}嘛,这下不怕抛异常 无法释放锁了吧
代码语言:javascript
复制
try{ 
   
       int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
       if (num > 0) { 
   
           int lastNum = num - 1;
           redisTemplate.opsForValue().set("num", lastNum + "");
           System.out.println("扣减库存成功,剩余库存为:" + lastNum);
       } else { 
   
           System.out.println("扣减库存失败,库存不足");
       }

   }finally { 
   
       // 释放锁
       redisTemplate.delete("lock");
   }
情况2:要是不抛异常,在释放锁之前服务重启了呢?那就来不及执行finally了吧,这下 锁也没有释放吧。
那该怎么解决呢?
有小伙伴 就会想:给锁加一个 定时器嘛,它要挂就挂,到了时间 锁自动释放,其他人又可以获取到 锁 服务又可用了
感觉是可行哦,那我们来实际操作一下。源码如下:
在这里插入图片描述
在这里插入图片描述
OK 定时有了,要是 线程挂在里面,时间一到 锁就会自动释放
事实真的会 是这个样子的吗?
情况3:锁设置成功,接下来对 锁进行定时,此时准备定时呢,还没定时成功突然程序挂了,又会导致死锁,像前那情况一样。
那这时 我们就要保存设置锁 和 定时 两个指令的原子性,要么全部成功 要么全部失败,那该怎么实现呢?
这个问题 大牛已经都想过了,往下看
代码语言:javascript
复制
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1213",10, TimeUnit.SECONDS);
一条更比两条强。这个底层使用 Lua 实现,保证其原子性。
再看一下 我们优化后的代码
代码语言:javascript
复制
@GetMapping("/cut")
    public Object kc2() { 
   
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        /** * 设置锁,如果 lock 不存在的话,设置 lock=1213 并返回 true * 如果存在的话:就不操作 直接返回 false */
        String lockKey = "lock";
        String redisClientId = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, redisClientId,10, TimeUnit.SECONDS);

        if(!lock){ 
   
            // 锁存在
            return "服务繁忙,请稍后再试";
        }
        try{ 
   
            int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
            if (num > 0) { 
   
                int lastNum = num - 1;
                redisTemplate.opsForValue().set("num", lastNum + "");
                System.out.println("扣减库存成功,剩余库存为:" + lastNum);
            } else { 
   
                System.out.println("扣减库存失败,库存不足");
            }
        }finally { 
   
            // 释放锁
            if (redisClientId.equals(redisTemplate.opsForValue().get(lockKey))){ 
   
                redisTemplate.delete(lockKey);
            }
        }
        return "ok";
    }
小伙伴 会说,这下没问题啦 哈哈。。。。
你确定 没问题?
那就我来给你分析分析

正常逻辑:一个请求进来 说去锁,设置有效时间为 10 秒,然后 执行下面 业务逻辑,最后释放锁,然后第二个线程进来。。。。

lock 有效时间为 10 秒,保不定 那个线程执行任务的时候 执行完要 15 秒,此时,lock 10秒就失效,那下一个线程就会进来,假如第二个线程要执行8 秒,第一个线程5秒后就执行完了 然后释放lock 锁,线程1的锁早就失效了,它释放的锁确实线程2的锁,而第二个线程还有3秒才执行完,此时线程3获取到锁,又进来了,3秒后线程2又释放线程3 的锁,这样下去线程3释放线程4.。。。 就会导致 锁永久失效。

所以 这个超时时间该设置多少呢? 就需要根据项目来考量了。

那有什么 好的解决方案吗,解决这个锁失效的问题呢?

有的,那肯定是有的,听我徐徐道来。。。

假设 设置锁有效时间为 30 秒,那当线程获取锁后,开一个子线程 做一个定时器,每隔一段时间去检查该对象的锁是否存在,存在的话 就重新给 锁续命。 那如何保证 自家的锁不会被别人释放呢? 这下那个 锁的 value 就派上用场了,给每个线程的锁配置上唯一标识(这个唯一标识就使用UUID) 每次释放锁的时候就判断是否是自己的锁,保证只释放自己的锁。

理论有了,那该怎么实现呢?
使用Redisson 实现分布式锁
1、引入 Redisson 依赖
代码语言:javascript
复制
<dependency>
   <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.0</version>
</dependency>
2、配置Redisson
代码语言:javascript
复制
@Bean
public Redisson redisson(){ 
   
    // 此为单机模式
    Config config = new Config();
    config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0).setPassword("root");
    return (Redisson) Redisson.create(config);
}
3、代码实现
代码语言:javascript
复制
@Autowired
private Redisson redisson;

@GetMapping("/cut")
public Object kc() { 
   
    String lockKey = "lock";
    RLock rLock = redisson.getLock(lockKey);
    try { 
   
        // 加锁
        rLock.lock();
        int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
        if (num > 0) { 
   
            int lastNum = num - 1;
            redisTemplate.opsForValue().set("num", lastNum + "");
            System.out.println("扣减库存成功,剩余库存为:" + lastNum);
        } else { 
   
            System.out.println("扣减库存失败,库存不足");
        }
    } finally { 
   
        // 释放锁
        rLock.unlock();
    }
    return "ok";
}
但是 主从架构 也是可能出现问题,如 redis 挂了
后期优化 可以 用 redis 集群,增加高可用。
OK redis 锁分布式就讲完了,到这里 锁就差不多了
要是还想更加完善 可以选择使用ZK来实现,但是性能是没有 redis 好。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/152027.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • redis 实现 分布式锁
    • 使用 redisTemplate 实现
      • 情况1:一个请求进来,获取锁,执行扣减库存的操作,在它 释放锁之前 服务突然抛异常了呢?
      • 情况2:要是不抛异常,在释放锁之前服务重启了呢?那就来不及执行finally了吧,这下 锁也没有释放吧。
      • 那该怎么解决呢?
      • 有小伙伴 就会想:给锁加一个 定时器嘛,它要挂就挂,到了时间 锁自动释放,其他人又可以获取到 锁 服务又可用了
      • 感觉是可行哦,那我们来实际操作一下。源码如下:
      • OK 定时有了,要是 线程挂在里面,时间一到 锁就会自动释放
      • 事实真的会 是这个样子的吗?
      • 情况3:锁设置成功,接下来对 锁进行定时,此时准备定时呢,还没定时成功突然程序挂了,又会导致死锁,像前那情况一样。
      • 那这时 我们就要保存设置锁 和 定时 两个指令的原子性,要么全部成功 要么全部失败,那该怎么实现呢?
      • 这个问题 大牛已经都想过了,往下看
      • 一条更比两条强。这个底层使用 Lua 实现,保证其原子性。
      • 再看一下 我们优化后的代码
      • 小伙伴 会说,这下没问题啦 哈哈。。。。
      • 你确定 没问题?
      • 那就我来给你分析分析
      • 理论有了,那该怎么实现呢?
      • 使用Redisson 实现分布式锁
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档