全站資源開放下載,感謝廣大網友的支持
鏈接失效請移步職業司平臺
非盈利平臺

非盈利平臺

只為分享一些優質內容

Java幫幫-微信公眾號

Java幫幫-微信公眾號

將分享做到極致

微信小程序

微信小程序

更方便的閱讀

職業司微信公眾號

職業司微信公眾號

實時動態通知

安卓APP

安卓APP

我們從此不分開

程序員生活志-公眾號

程序員生活志-公眾號

程序員生活學習圈,互聯網八卦黑料

支付寶贊助-Java幫幫社區
微信贊助-Java幫幫社區

Spring Boot Redis實現分布式鎖,真香!!

18
發表時間:2020-07-16 09:08

免費學習視頻歡迎關注云圖智聯:

之前看很多人手寫分布式鎖,其實 Spring Boot 現在已經做的足夠好了,開箱即用,支持主流的 Redis、Zookeeper 中間件,另外還支持 JDBC。

本篇棧長以 Redis 為例(這也是用得最多的方案),教大家如何利用 Spring Boot 集成 Redis 實現緩存,如何簡單、快速實現 Redis 分布式鎖。

分布式鎖介紹

Spring Boot 實現 Redis 分布式鎖在 spring-integration 這個項目中,參考:

首先來看下 LockRegistry 鎖注冊接口的所有實現類結構圖:

DefaultLockRegistry 就是純單機的可重入鎖,PassThruLockRegistry 是一個空實現類,也都沒有什么利用價值。

Spring Integration 4.0 引入了基于 Redis 的分布式鎖:RedisLockRegistry,并且從 5.0 開始實現了 ExpirableLockRegistry 接口,用來移除超時且沒有用的鎖。

分布式鎖實戰

添加依賴

上面提到 Spring Boot 實現 Redis 分布式鎖在 spring-integration 這個項目中,所以需要這三個依賴:

  • spring-boot-starter-data-redis

  • spring-boot-starter-integration

  • spring-integration-redis

  1. <dependency>  

  2. <groupId>org.springframework.boot</groupId>  

  3. <artifactId>spring-boot-starter-data-redis</artifactId>  

  4. </dependency>  

  5. <dependency>  

  6. <groupId>org.springframework.boot</groupId>  

  7. <artifactId>spring-boot-starter-integration</artifactId>

  8. </dependency>  

  9. <dependency>  

  10. <groupId>org.springframework.integration</groupId>  

  11. <artifactId>spring-integration-redis</artifactId>  

  12. </dependency>

Spring Boot 基礎知識就不介紹了,不熟悉的可以關注公眾號Java技術棧,在后臺回復:boot,可以閱讀我寫的歷史實戰教程。

配置分布式鎖

  1. @Bean(destroyMethod = "destroy")  

  2. public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {  

  3.     return new RedisLockRegistry(redisConnectionFactory, "lock");  

  4. }

使用示例

  1. @GetMapping("/redis/lock")  

  2. public String lock(@RequestParam("key") String key) {  

  3.     for (int i = 0; i < 10; i++) {  

  4.         new Thread(() -> {  

  5.             redisLockService.lock(key);  

  6.             try {  

  7.                 Thread.sleep(3000L);  

  8.             } catch (InterruptedException e) {  

  9.                 e.printStackTrace();  

  10.             }  

  11.             System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));  

  12.             redisLockService.unlock(key);  

  13.         }  

  14.         ).start();  

  15.     }  

  16.     return "OK";  

  17. }

RedisLockService 是我封裝了的一個 Redis 鎖服務,代碼有點多,這里就不貼了,完整的代碼示例在 Github 上,大家可以 Star 一下:

測試:

輸出:

  1. 2020-06-23 11:15:34  

  2. 2020-06-23 11:15:37  

  3. 2020-06-23 11:15:40  

  4. 2020-06-23 11:15:43  

  5. 2020-06-23 11:15:46  

  6. 2020-06-23 11:15:49  

  7. 2020-06-23 11:15:52  

  8. 2020-06-23 11:15:55

  9. 2020-06-23 11:15:58  

  10. 2020-06-23 11:16:01

可以看到每個線程需要等上一個線程休眠 3 秒后才能獲取到鎖。

源碼分析

集成完了,會使用了,還得研究下 RedisLockRegistry 的源碼,不然遇到什么坑還得再踩一篇。

RedisLockRegistry 有兩個類構造器:



  • connectionFactory:Redis 連接工廠

  • registryKey:鎖前綴

  • expireAfter:失效時間(非必須項,默認60秒)

所以,我們要注冊 expireAfter 這個選項,默認 60 秒是否滿足業務需要,如果超過默認的 60 少時間,否則將導致鎖失效。

還有兩個和 RedisLockRegistry   相關且很重要的成員變量:


  1. private final String clientId = UUID.randomUUID().toString();  

  2. private final Map<String, RedisLock> locks = new ConcurrentHashMap<>();

  • clientId:

首先用來標識當前 RedisLockRegistry 實例ID,并且在設置、移除鎖的時候都會要用到,用來判斷是不是當前的鎖注冊實例。

  • locks:

用來在內存中緩存當前鎖注冊實例所有鎖對象。

獲取鎖對象

如下面獲取鎖對象源碼所示:

每個 key 在內存中(ConcurrentHashMap)都對應一個鎖對象,鎖對象有生成過就直接返回,沒有就生成再返回,有了這個鎖對象才能進行上鎖和解鎖操作。

這個鎖對象(RedisLock)其實也是實現了 Java 中的 java.util.concurrent.locks.Lock 鎖接口:

鎖對象(RedisLock)也有兩個很重要的成員變量:

  1. private final ReentrantLock localLock = new ReentrantLock();  

  2. private volatile long lockedAt;

  • localLock:

localLock 是一個本地內存可重入鎖,每次去 Redis 上鎖前,都要用本地 localLock 上鎖先,這樣能做到盡可能的少往 Redis 上鎖,也能從一方面提升鎖的性能。

  • lockedAt:

lockedAt 上鎖時間,移除過時鎖會用到。

阻塞上鎖

RedisLock#lock():

每隔 100 毫秒嘗試獲取一次鎖,直到獲取鎖成功為止,不接受打斷異常,遇到其他異常會釋放本地鎖返回異常并結束。

主要看下設置 Redis 鎖的 Lua 腳本:

根據 key 查詢其對應的值:clientId,如果和當前 clientId 一致則延長失效時間,如果 clientId 不存在就直接上鎖,以上都不成立返回 false。

這樣做的好處是,可以將整個 Redis Lua 腳本作為一個原子執行,而不用考慮并發及事務影響。

好了,核心的源碼分析完了,其實也很簡單,大家還不懂的或者有興趣的可以再研究下。

本文完整示例源代碼和上篇 Spring Boot 快速集成 Redis 的示例代碼一起上傳到了 Github,歡迎大家 Star 關注學習。

所以,你還在手寫分布式鎖嗎?趕快 Get 起來吧!

免費學習視頻歡迎關注云圖智聯:


Java幫幫學習群生態

Java幫幫學習群生態

總有一款能幫到你

Java學習群

Java學習群

與大牛一起交流

大數據學習群

大數據學習群

在數據中成長

九點編程學習群

九點編程學習群

深夜九點學編程

python學習群

python學習群

人工智能,爬蟲

測試學習群

測試學習群

感受測試的魅力

Java幫幫生態承諾

Java幫幫生態承諾

一直堅守,不負重望

初心
勤儉
誠信
正義
分享
友鏈交換:加幫主QQ2524138991 留言即可 24小時內答復  
業司
教育資訊
會員登錄
獲取驗證碼
登錄
登錄
我的資料
留言
回到頂部