Redis setnx分布式鎖與Redisson分布式鎖的實現(xiàn)

使用Redis 對于分布式服務進行加鎖, 防止一個服務多個部署實例,對資源搶占發(fā)生沖突。

在單體應用時,對于并發(fā)場景,讀取公共資源如扣庫存,買車票等,使用簡單的加鎖即可實現(xiàn),Java本身提供了很多并發(fā)處理的API,如Synchronized。
但是對于分布式服務來說, 一個服務可能被部署在多臺機器中,形成多個實例,之前的單進程多線程變成了多進程多線程
Java提供的API就不足以解決此類問題

單體應用

單個鎖實現(xiàn)單進程多線程并發(fā)訪問問題
@GetMapping("/getNumber")
    public Integer getNumber(){
        Integer redPacketNumber;
        synchronized (this){
            log.info("《idea redPacket》   請求紅包數(shù)量");
            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("扣除失敗, 數(shù)量為{}", redPacketNumber);
            }
        }
        return redPacketNumber;
    }

分布式應用

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

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

注意:

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

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

 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》   請求紅包數(shù)量");
            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("扣除失敗, 數(shù)量為{}", redPacketNumber);
            }
        } finally{
            //釋放鎖(為當前鎖)
            if(stringRedisTemplate.opsForValue().get(redPacket).equals(vauleId)){
                stringRedisTemplate.delete(redPacket);
            }
        }
        return redPacketNumber;
    }

Redisson客戶端

高性能的異步,無鎖的redis客戶端 成熟的分布式鎖解決方案, 可以減少開發(fā)人員的工作量,且性能優(yōu)異。 這個方法可以保證較高的可用性 推薦使用
Redisson 架設在 redis 基礎上的 Java 駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid),基于NIO的 Netty 框架上,利用了 redis 鍵值數(shù)據(jù)庫。功能非常強大,解決了很多分布式架構中的問題。

Github的wiki地址:github.com/redisson/re…

官方文檔:github.com/redisson/re…

  • maven依賴
<dependency>
 <groupId>org.redisson</groupId>
 <artifactId>redisson</artifactId>
 <version>3.13.2</version>
</dependency>  
  • 注入實例
@Bean
    public Redisson redisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("****").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
  • 使用鎖
   @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》   請求紅包數(shù)量");
            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("扣除失敗, 數(shù)量為{}", redPacketNumber);
            }
        } finally{
           redissonLock.unlock();
        }
        return redPacketNumber;
    }
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容