Redis setnx分佈式鎖與Redisson分佈式鎖的實現

What

使用Redis 對於分佈式服務進行加鎖, 防止一個服務多個部署實例,對資源搶佔發生衝突。

Why

在單體應用時,對於併發場景,讀取公共資源如扣庫存,買車票等,使用簡單的加鎖即可實現,Java本身提供了很多併發處理的API,如Synchronized。

但是對於分佈式服務來說, 一個服務可能被部署在多臺機器中,形成多個實例,之前的單進程多線程變成了多進程多線程 Java提供的API就不足以解決此類問題

單體應用(沒問題)

單個鎖實現單進程多線程併發訪問問題

@GetMapping("/getNumber")
    public Integer getNumber(){
        Integer redPacketNumber;
        synchronized (this){
            log.info("《idea redPacket》   請求紅包數量");
            redPacketNumber = Integer.valueOf(stringRedisTemplate.opsForValue().get(RED_PACKET_NUMBER));
            if(redPacketNumber > 0){
                stringRedisTemplate.opsForValue().set(RED_PACKET_NUMBER, String.valueOf(--redPacketNumber));
                log.info("扣除成功,剩餘庫存 {}", redPacketNumber);
            } else {
                log.warn("扣除失敗, 數量爲{}", redPacketNumber);
            }
        }
        return redPacketNumber;
    }

分佈式應用

一個服務多個實例

如果還用synchronized關鍵字 因爲在不同服務, synchronized只能鎖住單個實例的代碼, 對於其他服務的代碼不起作用。

How

Redis分佈式鎖

使用redis當作分佈式鎖

原理

setnx命令 是redis的一條原生命令 大意爲 set if not exists, 在指定的key不存在的情況下,爲key設置值 使用如下

redis 127.0.0.1:6379> SETNX KEY_NAME VALUE
複製代碼

setIfAbsent方法

使用 Redis的setIfAbsent方法可以達到setnx命令同樣的效果。 如果key對應的value爲空,則設置值, 返回true 否則返回false

注意:

  1. 使用過期時間 爲了保證萬一服務報錯, 鎖過一會自動解鎖

  2. 使用隨機Value值進行存儲 防止 鎖的持續時間小於 業務執行時間, 導致鎖自動解鎖,使鎖失效 下一個請求就會直接進入。 所以使用隨機Value 使得這個鎖唯一,只能自己解開 (實踐中出了問題, 鎖解開的沒訪問的快, 還沒解開,請求都發送完了)

使用

 public Integer getNumberByDistributed(){
        String redPacket = "redPacket";
        Integer redPacketNumber;
        String vauleId = UUID.randomUUID().toString();
        try{
            Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redPacket, vauleId, 5, TimeUnit.SECONDS);
            //已存在,相當於已加鎖
            if(!aBoolean){
                return -1;
            }
            log.info("《idea redPacket》   請求紅包數量");
            redPacketNumber = Integer.valueOf(stringRedisTemplate.opsForValue().get(RED_PACKET_NUMBER));
            if(redPacketNumber > 0){
                stringRedisTemplate.opsForValue().set(RED_PACKET_NUMBER, String.valueOf(--redPacketNumber));
                log.info("扣除成功,剩餘庫存 {}", redPacketNumber);
            } else {
                log.warn("扣除失敗, 數量爲{}", redPacketNumber);
            }
        } finally{
            //釋放鎖(爲當前鎖)
            if(stringRedisTemplate.opsForValue().get(redPacket).equals(vauleId)){
                stringRedisTemplate.delete(redPacket);
            }
        }
        return redPacketNumber;
    }

Redisson客戶端

高性能的異步,無鎖的redis客戶端 成熟的分佈式鎖解決方案, 可以減少開發人員的工作量,且性能優異。 這個方法可以保證較高的可用性 推薦使用

1. maven依賴

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.13.2</version>
</dependency>  

2. 注入實例

    @Bean
    public Redisson redisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://yuanbaojian.xyz:6379").setPassword("****").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

3. 使用鎖

   @Autowired
    Redisson redisson;

 public Integer getNumberByRedisson(){
        String redPacket = "redPacket";
        Integer redPacketNumber;
        RLock redissonLock = redisson.getLock(redPacket);
        String vauleId = UUID.randomUUID().toString();
        try{
            redissonLock.lock(30, TimeUnit.SECONDS);
            log.info("{}進入redisson分佈式鎖的接口中",Thread.currentThread().getName());
            log.info("《idea redPacket》   請求紅包數量");
            redPacketNumber = Integer.valueOf(stringRedisTemplate.opsForValue().get(RED_PACKET_NUMBER));
            if(redPacketNumber > 0){
                stringRedisTemplate.opsForValue().set(RED_PACKET_NUMBER, String.valueOf(--redPacketNumber));
                log.info("扣除成功,剩餘庫存 {}", redPacketNumber);
            } else {
                log.warn("扣除失敗, 數量爲{}", redPacketNumber);
            }
        } finally{
           redissonLock.unlock();
        }
        return redPacketNumber;
    }


作者:少安
鏈接:https://juejin.im/post/5f0414155188252e937be22b
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章