redis做分布式锁的问题

参考

谷粒商城p158:https://www.bilibili.com/video/BV1np4y1C7Yf?p=158

redis官网:http://www.redis.cn/commands/set.html

提要

分布式场景下将原有的本地锁换为,基于redis的setnx命令的分布式锁

  • getCatalogJsonFromDb:从数据库查数据
  • getCatalogJsonFromDbWithLocalLock:利用本地锁查数据
  • getCatalogJsonFromDbWithRedisLock:利用redis的setnx命令的分布式锁查数据,现需要完成的

分布式锁演进-阶段一

代码
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() {
    
    // 1、占分布式锁,setnx
	Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111");
    if(lock){
        // 加锁成功。。。执行任务
        Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
        redisTemplate.delete("lock");
        return catalogJsonFromDb;
    }else{
        // 加锁失败。。。重试
        // 休眠100ms重试
        // 自旋方式
        return getCatalogJsonFromDbWithRedisLock();
    }

}
问题
  • setnx占好了位置,业务代码异常或程序宕机,没有执行删锁逻辑,死锁!!!
解决
  • 设置锁自动过期,即使没有删除,到期也会消失

分布式锁演进-阶段二

代码
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() {
    
    // 1、占分布式锁,setnx
	Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111");
    if(lock){
        // 加锁成功。。。执行任务
        
        // 2、设置过期时间
        redisTemplate.expire("lock", 30, TimeUnit.SECONDS);
        
        Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
        redisTemplate.delete("lock");
        return catalogJsonFromDb;
    }else{
        // 加锁失败。。。重试
        // 休眠100ms重试
        // 自旋方式
        return getCatalogJsonFromDbWithRedisLock();
    }

}
问题
  • 加锁与设置过期时间非原子操作,所以仍会出现死锁问题
解决
  • 利用redis提供的setexnx,做原子操作

分布式锁演进-阶段三

代码
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() {
    
    // 1、占分布式锁,setnx
    // 同时设置时间
	Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
    if(lock){
        // 加锁成功。。。执行任务
        
        // 2、设置过期时间
        // redisTemplate.expire("lock", 30, TimeUnit.SECONDS);
        
        Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
        redisTemplate.delete("lock");
        return catalogJsonFromDb;
    }else{
        // 加锁失败。。。重试
        // 休眠100ms重试
        // 自旋方式
        return getCatalogJsonFromDbWithRedisLock();
    }

}
问题
  • 如果业务时间过长,我们的锁过期自动删除,这时直接删锁,有可能把别人正在持有的锁删除了
解决
  • 占锁时指定uuid保证唯一性,删锁需要验证是否是自己的锁

分布式锁演进-阶段四

代码
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() {
    
    // 1、占分布式锁,setnx
    // 同时设置时间
	String uuid = UUID.randomUUID().toString();
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
    if(lock){
        // 加锁成功。。。执行任务
        
        // 2、设置过期时间
        // redisTemplate.expire("lock", 30, TimeUnit.SECONDS);
        
        Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
        // redisTemplate.delete("lock");
        String lockValue = redisTemplate.opsForValue().get("lock");
        // 验证是否是自己的锁
        if(uuid.equals(lockValue)){
            // 是,删锁
            redisTemplate.delete("lock");
        }
        return catalogJsonFromDb;
    }else{
        // 加锁失败。。。重试
        // 休眠100ms重试
        // 自旋方式
        return getCatalogJsonFromDbWithRedisLock();
    }

}
问题
  • 注意在redisget锁的值时,即String lockValue = redisTemplate.opsForValue().get("lock");,这时可能redis还存在我们的锁,这时返回的正是我们uuid,但是因为网络传输的延时,我们要执行delete操作时,我们的锁已经因为过期策略删除了,所以虽然这是的锁不是我们的,但程序代码仍然会执行删除锁(并非我们的),本质仍是非原子操作问题
解决
  • lua脚本,实现原子操作

分布式锁演进-阶段五

代码
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() {

    // 1、占分布式锁,setnx
    // 同时设置时间
    String uuid = UUID.randomUUID().toString();
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
    Map<String, List<Catalog2Vo>> catalogJsonFromDb = null;
    if (lock) {
        // 加锁成功
        
        // 2、设置过期时间
        // redisTemplate.expire("lock", 30, TimeUnit.SECONDS);
        
        try {
            catalogJsonFromDb = getCatalogJsonFromDb();
        } finally {
            // 获取值对比+对比成功删除=原子操作
            // String lockValue = redisTemplate.opsForValue().get("lock");
            // if (uuid.equals(lockValue)) {
            //     redisTemplate.delete("lock");
            // }

            // 脚本解锁
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            Long unlock = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
        }
        return catalogJsonFromDb;

    } else {
        // 加锁失败。。。重试
        // 休眠100ms重试
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getCatalogJsonFromDbWithRedisLock();
    }

}

总结

使用redis做分布式锁

  • 加锁和设置过期时间的原子性问题
  • 解锁与验证锁的归属的原子性问题
  • 还有业务时间与过期时间的设置,有时需要延长过期时间

可以考虑使用Redisson

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

无奈何杨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值