SpringBoot項目中實現(xiàn)Redis分布式鎖的方法與最佳實踐
引言
在 Spring Boot 項目中實現(xiàn) Redis 分布式鎖,主要有手動實現(xiàn)和使用 ?Redisson? 兩種主流方式。下面我來為你詳細解釋這兩種方式的原理、實現(xiàn)方法和最佳實踐。
| 特性維度 | 手動實現(xiàn) (RedisTemplate) | Redisson (推薦) |
|---|---|---|
| ?實現(xiàn)復(fù)雜度? | 較高,需處理所有細節(jié) | 低,開箱即用,API 簡潔 |
| ?可重入性? | 需自行實現(xiàn) | ?原生支持? |
| ?鎖續(xù)期(Watchdog)?? | 需自行實現(xiàn) | ?原生支持? |
| ?超時與等待? | 需自行實現(xiàn) | ?原生支持多種加鎖策略 |
| ?公平鎖/讀寫鎖? | 不支持 | ?支持? |
| ?集群支持(RedLock)?? | 實現(xiàn)復(fù)雜 | ?原生支持? |
| ?可靠性? | 一般,需自行規(guī)避各種邊界問題 | ?高,久經(jīng)考驗 |
手動實現(xiàn)基于 RedisTemplate
這種方式讓你更接近底層原理,但需要自己處理鎖續(xù)期、可重入等復(fù)雜問題。
?添加依賴?
在 pom.xml中添加 Spring Data Redis 依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
?配置 Redis 連接?
在 application.yml中配置:
spring:
redis:
host: localhost
port: 6379
# password: yourpassword # 如果設(shè)置了密碼
?分布式鎖工具類?
核心是使用 SET key value NX EX seconds命令原子性地獲取鎖,并用 Lua 腳本保證釋放鎖的原子性(判斷值再刪除)。
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "lock:";
/**
* 嘗試獲取分布式鎖
* @param lockKey 鎖的業(yè)務(wù)Key,如 "order:123"
* @param requestId 請求標(biāo)識(可用UUID),用于安全釋放鎖
* @param expireTime 鎖的過期時間(毫秒)
* @param waitTime 獲取鎖的最大等待時間(毫秒)
* @return 是否獲取成功
*/
public boolean tryLock(String lockKey, String requestId, long expireTime, long waitTime) throws InterruptedException {
String fullKey = LOCK_PREFIX + lockKey;
long end = System.currentTimeMillis() + waitTime;
while (System.currentTimeMillis() < end) { // 在等待時間內(nèi)循環(huán)嘗試
// 使用 SET NX EX 命令原子性地嘗試獲取鎖
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(fullKey, requestId, expireTime, TimeUnit.MILLISECONDS);
if (Boolean.TRUE.equals(success)) {
return true; // 獲取鎖成功
}
Thread.sleep(50); // 短暫休眠,避免活鎖和CPU空轉(zhuǎn)
}
return false; // 超時仍未獲取到鎖
}
/**
* 釋放分布式鎖 (使用Lua腳本保證原子性)
* @param lockKey 鎖的業(yè)務(wù)Key
* @param requestId 請求標(biāo)識,必須與加鎖時一致
* @return 是否釋放成功
*/
public boolean unlock(String lockKey, String requestId) {
String fullKey = LOCK_PREFIX + lockKey;
// Lua腳本:如果鍵存在且值與預(yù)期相符,則刪除它
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(fullKey), requestId);
return result != null && result == 1;
}
}
?關(guān)鍵點:??
- ?NX (Not eXists)??:確保只有鎖不存在時才能設(shè)置成功,實現(xiàn)互斥。
- ?EX (Expire)??:設(shè)置過期時間,?這是防止死鎖的關(guān)鍵。即使客戶端崩潰,鎖也會自動釋放。
- ?唯一值 (requestId)??:使用唯一值(如UUID)標(biāo)識鎖的持有者,確保只能由加鎖的客戶端來解鎖,?防止誤釋鎖。
- ?Lua 腳本?:釋放鎖時,判斷值和刪除必須是原子操作,否則可能誤刪其他客戶端的鎖。
?在業(yè)務(wù)中使用?
@Service
public class OrderService {
@Autowired
private RedisDistributedLock redisLock;
public void createOrder(String orderId) {
String lockKey = "order_create:" + orderId;
String requestId = UUID.randomUUID().toString(); // 生成唯一請求ID
boolean locked = false;
try {
// 嘗試獲取鎖,等待5秒,鎖過期時間10秒
locked = redisLock.tryLock(lockKey, requestId, 10000, 5000);
if (locked) {
// 成功獲取鎖,執(zhí)行核心業(yè)務(wù)邏輯
System.out.println("成功獲取鎖,執(zhí)行訂單創(chuàng)建邏輯...");
// ... (你的業(yè)務(wù)代碼)
} else {
// 獲取鎖失敗,處理異?;蛑卦?
throw new RuntimeException("系統(tǒng)繁忙,請稍后再試");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("獲取鎖被中斷", e);
} finally {
// 釋放鎖,務(wù)必放在finally塊中確保執(zhí)行
if (locked) {
redisLock.unlock(lockKey, requestId);
}
}
}
}
使用 Redisson(推薦)
Redisson 是一個強大的 Redis Java 客戶端,它提供了直接可用的分布式鎖實現(xiàn),解決了手動實現(xiàn)的所有痛點,如可重入鎖、鎖續(xù)期、等待機制等。
?添加依賴?
在 pom.xml中添加 Redisson 依賴:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.0</version> <!-- 請使用最新穩(wěn)定版本 -->
</dependency>
?基本配置 (application.yml)??
spring:
redis:
host: localhost
port: 6379
# Redisson 通常會自動使用Spring Redis的配置
?使用 Redisson 的 RLock?
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class OrderServiceRedisson {
@Autowired
private RedissonClient redissonClient; // 直接注入RedissonClient
public void createOrder(String orderId) {
String lockKey = "order:lock:" + orderId;
RLock lock = redissonClient.getLock(lockKey); // 獲取RLock對象
try {
// 嘗試加鎖,最多等待5秒,鎖持有時間10秒后自動解鎖
// boolean isLocked = lock.tryLock(5, 10, TimeUnit.SECONDS);
// 更常用的方式是設(shè)置等待時間,但讓看門狗自動續(xù)期(lockTime傳入-1或使用無參lock())
boolean isLocked = lock.tryLock(5, TimeUnit.SECONDS);
if (isLocked) {
try {
// 成功獲取鎖,執(zhí)行業(yè)務(wù)邏輯
System.out.println("Redisson鎖獲取成功,執(zhí)行邏輯...");
// ... (你的業(yè)務(wù)邏輯,看門狗會為沒有顯式設(shè)置leaseTime的鎖自動續(xù)期)
} finally {
// 釋放鎖
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} else {
throw new RuntimeException("系統(tǒng)繁忙,請稍后再試");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("獲取鎖被中斷", e);
}
}
}
?Redisson 核心優(yōu)勢:??
- ?看門狗機制 (Watchdog)??:如果你沒有顯式指定
leaseTime(鎖持有時間),Redisson 會啟動一個看門狗線程,在業(yè)務(wù)執(zhí)行期間自動定期續(xù)期鎖,防止業(yè)務(wù)執(zhí)行時間超時導(dǎo)致鎖意外釋放。這是手動實現(xiàn)非常容易忽略且難以正確實現(xiàn)的一點。 - ?可重入?:同一個線程可以多次獲取同一把鎖。
- ?豐富的鎖類型?:支持可重入鎖、公平鎖、聯(lián)鎖、紅鎖(RedLock)、讀寫鎖等。
注意事項與最佳實踐
?鎖的粒度?:鎖的 Key 要精確,例如 "order:123"比 "order"更好,減少競爭,提升并發(fā)度。
?超時時間?:手動實現(xiàn)時,過期時間不宜過短或過長。過短可能導(dǎo)致業(yè)務(wù)未完成鎖就釋放;過長則故障恢復(fù)慢。Redisson 的看門狗機制能很好地解決這個問題。
?務(wù)必釋放鎖?:釋放鎖必須放在 finally代碼塊中執(zhí)行,確保即使業(yè)務(wù)異常也能釋放鎖。
?Redis 模式?:
- 單機/主從模式:上述方法適用,但主從異步復(fù)制下,主節(jié)點宕機可能導(dǎo)致鎖失效(從節(jié)點可能無鎖數(shù)據(jù))。
- ?紅鎖 (RedLock)??:為解決上述問題,可在多個獨立的 Redis 主節(jié)點上嘗試獲取鎖,超過半數(shù)成功才算真正獲取。Redisson 提供了紅鎖實現(xiàn),但更復(fù)雜,通常用于對一致性要求極高的場景。
總結(jié)
對于大多數(shù) Spring Boot 項目,?我強烈推薦直接使用 Redisson。它功能完善、久經(jīng)考驗,能讓你遠離分布式鎖的各種復(fù)雜細節(jié),更專注于業(yè)務(wù)邏輯開發(fā)。如果你是為了學(xué)習(xí)底層原理或?qū)崿F(xiàn)一個非常簡單的鎖,那么可以嘗試手動實現(xiàn)的方式。
以上就是SpringBoot項目中實現(xiàn)Redis分布式鎖的方法與最佳實踐的詳細內(nèi)容,更多關(guān)于SpringBoot實現(xiàn)Redis分布式鎖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
登錄EasyConnect后無法通過jdbc訪問服務(wù)器數(shù)據(jù)庫問題的解決方法
描述一下近期使用EasyConnect遇到的問題,下面這篇文章主要給大家介紹了關(guān)于登錄EasyConnect后無法通過jdbc訪問服務(wù)器數(shù)據(jù)庫問題的解決方法,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-02-02
Java使用html2image將html生成縮略圖圖片的實現(xiàn)示例
本文主要介紹了Java使用html2image將html生成縮略圖圖片的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12
Mybatis利用分頁插件PageHelper快速實現(xiàn)分頁查詢
如果你也在用MyBatis,建議嘗試該分頁插件,這一定是最方便使用的分頁插件,這篇文章主要給大家介紹了關(guān)于Mybatis利用分頁插件PageHelper快速實現(xiàn)分頁查詢的相關(guān)資料,PageHelper是一個Mybatis的分頁插件,負責(zé)將已經(jīng)寫好的sql語句,進行分頁加工,需要的朋友可以參考下2021-08-08

