分布式下加锁

以前章的redis做缓存出现缓存击穿加锁为例

1.加本地锁

synchronized (this)

在单体应用没问题,但是分布式下本地锁只能锁住当前线程,锁不住其他。

如有多台应用服务,每个加本地锁,也有几把锁,发送多个请求,
所以在分布式情况下,就需要分布式锁


2.分布式锁 

 缺点:性能慢

我们可以同时去一个地方 占坑 ,如果占到,就执行逻辑。否则就必须等待,直到释放锁。
占坑 可以去 redis ,可以去数据库,可以去任何大家都能访问的地方。
等待可以 自旋 的方式。
分布式锁演进 - 阶段一
使用redis的NX机制
死锁:业务代码异常或者程序宕机,没有执行删除锁逻辑,这就照成了死锁
解决:设置锁的自动过期,即使没有删除,会自动删除

 

    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //1.占分布式锁。去redis占坑
        Boolean locak = redisTemplate.opsForValue().setIfAbsent("locak", "111");
    if(locak){
        //加锁成功。。。
        Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
        redisTemplate.delete("locak");
        return dataFromDb;
    }else{
        //没成功-...重试
        //休眠100毫秒
        return getCatalogJsonFromDbWithRedisLock();
    }

    }

分布式锁演进 - 阶段二
业务代码设置过期时间
同意会出现的问题如果,设置过期时间的代码块出现了系统异常也会出现死锁
解决:
 
设置过期时间和占位必须是原子的。 redis 支持使用 setnx ex 命令

 

 public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //1.占分布式锁。去redis占坑
        Boolean locak = redisTemplate.opsForValue().setIfAbsent("locak", "111");
    if(locak){
        //加锁成功。。。
        //2.设置过期时间
        redisTemplate.expire("locak",30,TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
        redisTemplate.delete("locak");
        return dataFromDb;
    }else{
        //没成功-...重试
        //休眠100毫秒
        return getCatalogJsonFromDbWithRedisLock();
    }

    }

分布式锁演进 - 阶段三
又出现一个问题就是删除锁,
由于业务时间很长,自己的锁过期了,已经删除了,我再删除锁,可能删除的就是别人正在持有的锁
解决:
占锁的时候,值指定为 uuid ,每个人匹配是自己
的锁才删除。

 

    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //1.占分布式锁。去redis占坑
        Boolean locak = redisTemplate.opsForValue().setIfAbsent("locak", "111",300,TimeUnit.SECONDS);
    if(locak){
        //加锁成功。。。
        //2.设置过期时间,必须和加锁是同步的,原子的
       // redisTemplate.expire("locak",30,TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
        redisTemplate.delete("locak");
        return dataFromDb;
    }else{
        //没成功-...重试
        //休眠100毫秒
        return getCatalogJsonFromDbWithRedisLock();
    }

    }

分布式锁演进 - 阶段四
在里还会出现一个问题就是,当判断值和删除锁的时候,锁过期,别人已经设置到了新的值,那么我们删除的就是别人的锁的值
解决:

删除锁必须保证原子行,即:获取值对比+对比成功删除=原子操作
使用redis+Lua脚本完成

 

public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //1.占分布式锁。去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean locak = redisTemplate.opsForValue().setIfAbsent("locak", uuid,300,TimeUnit.SECONDS);
    if(locak){
        //加锁成功。。。
        //2.设置过期时间,必须和加锁是同步的,原子的
       // redisTemplate.expire("locak",30,TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
       // redisTemplate.delete("locak");

        //获取值对比+对比成功删除=原子操作
        String locaValue = redisTemplate.opsForValue().get("locak");
        if(uuid.equals(locaValue)){
            //删除自己的锁
            redisTemplate.delete("locak");
        }
        return dataFromDb;
    }else{
        //没成功-...重试
        //休眠100毫秒
        return getCatalogJsonFromDbWithRedisLock();
    }

    }

分布式锁演进 - 阶段五 - 最终形态

 总结:(保证加锁和删除都是原子行的操作)

保证加锁【占位 + 过期时间】和删除锁【判断 + 删除】的原子性。
更难的事情,锁的自动续期
当然最简单的自动续期解决是把锁的时间加长
//从数据库查询并封装数据
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //1.占分布式锁。去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean locak = redisTemplate.opsForValue().setIfAbsent("locak", uuid,300,TimeUnit.SECONDS);
    if(locak){
        //加锁成功。。。
        //2.设置过期时间,必须和加锁是同步的,原子的
       // redisTemplate.expire("locak",30,TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> dataFromDb;
        try{
             dataFromDb = getDataFromDb();
        }finally {
            String script="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            //删除锁
            Integer locak1 = redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class)
                    , Arrays.asList("locak"), uuid);
        }
       
       // redisTemplate.delete("locak");

        //获取值对比+对比成功删除=原子操作
//        String locaValue = redisTemplate.opsForValue().get("locak");
//        if(uuid.equals(locaValue)){
//            //删除自己的锁
//            redisTemplate.delete("locak");
//        }

      
        return dataFromDb;
    }else{
        //没成功-...重试
        //休眠100毫秒
        return getCatalogJsonFromDbWithRedisLock();
    }

    }

Redisson-lock(可重入锁)

A方法里有B ,A加了1号锁,b发现A加了1号锁,就沿用A里的1号锁,但是必须等A用完1号锁,这种锁叫不可重入锁

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐路上的小人物

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值