1. 程式人生 > 實用技巧 >Spring Boot Redis 實現分散式鎖,真香!!(轉載)

Spring Boot Redis 實現分散式鎖,真香!!(轉載)

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

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

分散式鎖介紹

Spring Boot 實現 Redis 分散式鎖在spring-integration這個專案中,參考:

https://docs.spring.io/spring-integration/docs/5.3.1.RELEASE/reference/html/redis.html#redis-lock-registry

首先來看下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
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.integration</groupId>
	<artifactId>spring-integration-redis</artifactId>
</dependency>

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

配置分散式鎖

@Bean(destroyMethod = "destroy")
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
    return new RedisLockRegistry(redisConnectionFactory, "lock");
}

使用示例

@GetMapping("/redis/lock")
public String lock(@RequestParam("key") String key) {
    for (int i = 0; i < 10; i++) {
        new Thread(() -> {
            redisLockService.lock(key);
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            redisLockService.unlock(key);
        }
        ).start();
    }
    return "OK";
}

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

https://github.com/javastacks/spring-boot-best-practice

測試:

http://localhost:8080/redis/lock?key=yeah

輸出:

2020-06-23 11:15:34
2020-06-23 11:15:37
2020-06-23 11:15:40
2020-06-23 11:15:43
2020-06-23 11:15:46
2020-06-23 11:15:49
2020-06-23 11:15:52
2020-06-23 11:15:55
2020-06-23 11:15:58
2020-06-23 11:16:01

可以看到每個執行緒需要等上一個執行緒休眠 3 秒後才能獲取到鎖。

原始碼分析

整合完了,會使用了,還得研究下RedisLockRegistry的原始碼,不然遇到什麼坑還得再踩一篇。

RedisLockRegistry有兩個類構造器:

  • connectionFactory:Redis 連線工廠
  • registryKey:鎖字首
  • expireAfter:失效時間(非必須項,預設60秒)

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

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

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

private final Map<String, RedisLock> locks = new ConcurrentHashMap<>();
  • clientId

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

  • locks

用來在記憶體中快取當前鎖註冊例項所有鎖物件。

獲取鎖物件

如下面獲取鎖物件原始碼所示:

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

這個鎖物件(RedisLock)其實也是實現了 Java 中的java.util.concurrent.locks.Lock鎖介面:

鎖物件(RedisLock)也有兩個很重要的成員變數:

private final ReentrantLock localLock = new ReentrantLock();

private volatile long lockedAt;
  • localLock

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

  • lockedAt

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

阻塞上鎖

RedisLock#lock():

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

主要看下設定 Redis 鎖的 Lua 指令碼:

原文連結:https://www.cnblogs.com/javastack/p/13303926.html