天天看點

分布式鎖+分布式下的緩存問題 使用redis(redisson) 實作分布式鎖分布式鎖+分布式下的緩存問題

分布式鎖+分布式下的緩存問題

  • 當微服務模式時,多個相同服務之間對于資料庫需要保持資料一緻性.此時需要從本地鎖 演變為 分布式鎖.
  • 本博文通過進階的形式 不斷提出問題以及解決思路,一步一步完善代碼,實作具有高可靠性的分布式鎖功能.
分布式鎖+分布式下的緩存問題 使用redis(redisson) 實作分布式鎖分布式鎖+分布式下的緩存問題

使用redis的set指令帶NX(not exist)參數實作分布式鎖

NX:隻有當不存在時,才可以set;成功set會傳回OK,不成功傳回null

分布式鎖+分布式下的緩存問題 使用redis(redisson) 實作分布式鎖分布式鎖+分布式下的緩存問題
//分布式鎖
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();
    }
}
           

階段二 獨立加上分布式鎖的過期時間

分布式鎖+分布式下的緩存問題 使用redis(redisson) 實作分布式鎖分布式鎖+分布式下的緩存問題
//分布式鎖
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();
    }
}
           

階段三 原子占鎖和設定過期時間

分布式鎖+分布式下的緩存問題 使用redis(redisson) 實作分布式鎖分布式鎖+分布式下的緩存問題
//分布式鎖
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比對

分布式鎖+分布式下的緩存問題 使用redis(redisson) 實作分布式鎖分布式鎖+分布式下的緩存問題
//分布式鎖
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腳本 删鎖原子操作

分布式鎖+分布式下的緩存問題 使用redis(redisson) 實作分布式鎖分布式鎖+分布式下的緩存問題
//分布式鎖
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;
}