分布式锁+分布式下的缓存问题 使用redis(redisson) 实现分布式锁

分布式锁+分布式下的缓存问题

  • 当微服务模式时,多个相同服务之间对于数据库需要保持数据一致性.此时需要从本地锁 演变为 分布式锁.
  • 本博文通过进阶的形式 不断提出问题以及解决思路,一步一步完善代码,实现具有高可靠性的分布式锁功能.

在这里插入图片描述

使用redis的set命令带NX(not exist)参数实现分布式锁

NX:只有当不存在时,才可以set;成功set会返回OK,不成功返回null
在这里插入图片描述

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
    
    

    //1、占分布式锁。去redis占坑
    Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
    if(aBoolean){
    
    
        //加锁成功 执行业务
        Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
        //删除锁
        stringRedisTemplate.delete("lock");
        return dataFromDB;
    }else {
    
    
        //加锁失败   重试 自旋
        return getCatalogJsonFromDBWithRedisLock();
    }
}

阶段二 独立加上分布式锁的过期时间

在这里插入图片描述

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
    
    

    //1、占分布式锁。去redis占坑
    Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
    if(aBoolean){
    
    
        //加锁成功 执行业务
        //2、设置过期时间
        stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
        //删除锁
        stringRedisTemplate.delete("lock");
        return dataFromDB;
    }else {
    
    
        //加锁失败   重试 自旋
        return getCatalogJsonFromDBWithRedisLock();
    }
}

阶段三 原子占锁和设置过期时间

在这里插入图片描述

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
    
    

    //1、占分布式锁。去redis占坑  并设置过期时间 必须是同步的 原子的
    Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock",30,TimeUnit.SECONDS);
    if(aBoolean){
    
    
        //加锁成功 执行业务
        Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
        //删除锁
        stringRedisTemplate.delete("lock");
        return dataFromDB;
    }else {
    
    
        //加锁失败   重试 自旋
        return getCatalogJsonFromDBWithRedisLock();
    }
}

阶段四 删锁进行权限uuid匹配

在这里插入图片描述

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
    
    

    //1、占分布式锁。去redis占坑  并设置过期时间 必须是同步的 原子的
    String uuid = UUID.randomUUID().toString();
    Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
    if(aBoolean){
    
    
        //加锁成功 执行业务
        Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
        String lock = stringRedisTemplate.opsForValue().get("lock");
        if(uuid.equals(lock)){
    
    
            //删除自己的锁
            stringRedisTemplate.delete("lock");
        }
        return dataFromDB;
    }else {
    
    
        //加锁失败   重试 自旋
        return getCatalogJsonFromDBWithRedisLock();
    }
}

阶段五 lua脚本 删锁原子操作

在这里插入图片描述

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
    
    

    //1、占分布式锁。去redis占坑  并设置过期时间 必须是同步的 原子的
    String uuid = UUID.randomUUID().toString();
    Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
    if(aBoolean){
    
    
        //加锁成功 执行业务
        Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
        //获取值 + 对比 + 删除 必须是原子操作  lua脚本解锁
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                "then " +
                "   return  redis.call('del', KEYS[1])" +
                "else " +
                "   return 0 " +
                "end";
        Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
        
        return dataFromDB;
    }else {
    
    
        //加锁失败   重试 自旋
        return getCatalogJsonFromDBWithRedisLock();
    }
}

阶段六 最终结果

不论业务是否正确完成都删除自己建立的锁

    //分布式锁
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
    
    

        //1、占分布式锁。去redis占坑  并设置过期时间 必须是同步的 原子的
        String uuid = UUID.randomUUID().toString();
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
        if(aBoolean){
    
    
            //加锁成功 执行业务
            Map<String, List<Catelog2Vo>> dataFromDB = null;
            try {
    
    
                dataFromDB = this.getDataFromDB();
            }finally {
    
    
                //获取值 + 对比 + 删除 必须是原子操作  lua脚本解锁
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                        "then " +
                        "   return  redis.call('del', KEYS[1])" +
                        "else " +
                        "   return 0 " +
                        "end";
                Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDB;
        }else {
    
    
            //加锁失败   重试 自旋
            //睡眠
            try {
    
    
                Thread.sleep(200);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            return getCatalogJsonFromDBWithRedisLock();
        }
    }

使用Redisson框架实现分布式锁

如果手动使用redis实现分布式锁,在每一次业务处理前都需要编写原子上锁和lua脚本原子解锁语句,过于繁琐,使用Redisson分布式锁的框架能更加方便实现.Reddison底层实现了JUC包下的方法,因此可用无缝对接JUC的使用.

引入依赖

<!--引入redis的分布式锁  分布式对象框架redison-->
      <dependency>
          <groupId>org.redisson</groupId>
          <artifactId>redisson</artifactId>
          <version>3.12.0</version>
      </dependency>

配置redissonClient客户端

@Configuration
public class MyRedissonConfig {
    
    
    /**
     * 所有对Redisson的使用 都是通过RedissonClient对象
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() throws IOException{
    
    
        //1、创建配置   redisson包下的config
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.231.1:6379");
        //2、根据config创建出RedissonClient示例
        return Redisson.create(config);
    }
}

使用redisson锁实现分布式锁

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedissonLock() {
    
    

    //1、占分布式锁。redisson
    /**
     * 锁的名字 决定了锁的粒度,越细越快
     * 锁的粒度约定:  具体缓存的是某个数据  例如 11号商品  product-11-lock
     */
    RLock lock = redissonClient.getLock("catalogJson-lock");
    lock.lock();
    Map<String, List<Catelog2Vo>> dataFromDB = null;
    try{
    
    
        //加锁成功 执行业务
        dataFromDB = this.getDataFromDB();
    }finally{
    
    
        lock.unlock();
    }
    return dataFromDB;
}

猜你喜欢

转载自blog.csdn.net/weixin_44634197/article/details/108308395