Spring Boot Redis 實現分散式鎖,真香!!
阿新 • • 發佈:2020-07-15
之前看很多人手寫分散式鎖,其實 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` 鎖註冊介面的所有實現類結構圖:
![](http://img.javastack.cn/20200624140016.png)
`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
```
```
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` 有兩個類構造器:
![](http://img.javastack.cn/20200624145354.png)
- **connectionFactory**:Redis 連線工廠
- **registryKey**:鎖字首
- **expireAfter**:失效時間(非必須項,預設60秒)
所以,我們要註冊 `expireAfter` 這個選項,預設 60 秒是否滿足業務需要,如果超過預設的 60 少時間,否則將導致鎖失效。
還有兩個和 `RedisLockRegistry` 相關且很重要的成員變數:
```
private final String clientId = UUID.randomUUID().toString();
private f