【Redis分布式鎖實現】基于 Redis 單節點保姆級教程(Spring Boot 示例)
下面我將詳細介紹基于 Redis 單節點實現分布式鎖的原理,并提供一個完整的 Spring Boot 實現示例。
實現原理
核心機制
-
原子獲取鎖:使用
SET key unique_value NX PX milliseconds命令NX:僅當 key 不存在時設置值PX:設置過期時間(毫秒)unique_value:唯一標識客戶端(防止誤刪其他客戶端的鎖)
-
安全釋放鎖:使用 Lua 腳本保證原子性
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end -
鎖續期機制:可選的看門狗(Watchdog)機制,定期延長鎖的有效期
關鍵特性
- 互斥性:同一時刻只有一個客戶端能持有鎖
- 防死鎖:自動過期機制確保鎖最終釋放
- 容錯性:客戶端崩潰后鎖會自動釋放
- 安全性:只有鎖的持有者才能釋放鎖
Spring Boot 實現示例
1. 添加依賴 (pom.xml)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
2. 配置 Redis (application.yml)
spring:
redis:
host: localhost
port: 6379
password:
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
3. Redis 分布式鎖工具類
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
private final RedisTemplate<String, String> redisTemplate;
// 鎖鍵前綴
private static final String LOCK_PREFIX = "lock:";
// 解鎖Lua腳本
private static final String UNLOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
// 續期Lua腳本
private static final String RENEW_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('pexpire', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end";
public RedisDistributedLock(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 嘗試獲取分布式鎖
*
* @param lockKey 鎖的key
* @param requestId 請求標識(可使用UUID)
* @param expireTime 鎖的過期時間(毫秒)
* @param waitTime 等待時間(毫秒)
* @return 是否獲取成功
*/
public boolean tryLock(String lockKey, String requestId, long expireTime, long waitTime) {
String fullKey = LOCK_PREFIX + lockKey;
long end = System.currentTimeMillis() + waitTime;
while (System.currentTimeMillis() < end) {
// 嘗試獲取鎖
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(fullKey, requestId, expireTime, TimeUnit.MILLISECONDS);
if (Boolean.TRUE.equals(success)) {
return true;
}
// 等待隨機時間后重試,避免活鎖
try {
Thread.sleep(50 + (long) (Math.random() * 100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 釋放分布式鎖
*
* @param lockKey 鎖的key
* @param requestId 請求標識
* @return 是否釋放成功
*/
public boolean unlock(String lockKey, String requestId) {
String fullKey = LOCK_PREFIX + lockKey;
// 使用Lua腳本保證原子性
RedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(fullKey), requestId);
return result != null && result == 1;
}
/**
* 鎖續期(看門狗機制)
*
* @param lockKey 鎖的key
* @param requestId 請求標識
* @param expireTime 新的過期時間(毫秒)
* @return 是否續期成功
*/
public boolean renewLock(String lockKey, String requestId, long expireTime) {
String fullKey = LOCK_PREFIX + lockKey;
// 使用Lua腳本保證原子性
RedisScript<Long> script = new DefaultRedisScript<>(RENEW_SCRIPT, Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(fullKey),
requestId,
String.valueOf(expireTime));
return result != null && result == 1;
}
/**
* 獲取鎖(簡化版,帶自動續期)
*
* @param lockKey 鎖的key
* @param expireTime 鎖的過期時間(毫秒)
* @param waitTime 等待時間(毫秒)
* @param task 需要執行的任務
* @return 任務執行結果
*/
public <T> T lockAndExecute(String lockKey, long expireTime, long waitTime, LockTask<T> task) {
String requestId = UUID.randomUUID().toString();
boolean locked = false;
try {
// 嘗試獲取鎖
locked = tryLock(lockKey, requestId, expireTime, waitTime);
if (!locked) {
throw new RuntimeException("獲取分布式鎖失敗");
}
// 啟動看門狗線程定期續期
WatchDog watchDog = new WatchDog(lockKey, requestId, expireTime);
watchDog.start();
try {
// 執行業務邏輯
return task.execute();
} finally {
// 停止看門狗
watchDog.stop();
}
} finally {
// 確保鎖被釋放
if (locked) {
unlock(lockKey, requestId);
}
}
}
// 看門狗線程實現
private class WatchDog {
private final String lockKey;
private final String requestId;
private final long expireTime;
private volatile boolean running = true;
private Thread thread;
public WatchDog(String lockKey, String requestId, long expireTime) {
this.lockKey = lockKey;
this.requestId = requestId;
this.expireTime = expireTime;
}
public void start() {
thread = new Thread(() -> {
// 在過期時間的1/3時進行續期
long sleepTime = expireTime / 3;
while (running) {
try {
Thread.sleep(sleepTime);
if (!renewLock(lockKey, requestId, expireTime)) {
// 續期失敗,可能是鎖已被釋放或過期
break;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "RedisLock-WatchDog");
thread.setDaemon(true);
thread.start();
}
public void stop() {
running = false;
if (thread != null) {
thread.interrupt();
}
}
}
// 鎖任務接口
@FunctionalInterface
public interface LockTask<T> {
T execute();
}
}
4. 業務服務中使用分布式鎖
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final RedisDistributedLock redisLock;
public OrderService(RedisDistributedLock redisLock) {
this.redisLock = redisLock;
}
/**
* 創建訂單(使用分布式鎖保護)
*/
public void createOrder(String orderId) {
// 使用鎖執行關鍵操作
redisLock.lockAndExecute("order_create:" + orderId, 30000, 5000, () -> {
// 在這里執行需要加鎖的業務邏輯
try {
// 1. 檢查訂單是否已存在
if (checkOrderExists(orderId)) {
throw new RuntimeException("訂單已存在");
}
// 2. 執行創建訂單的核心業務
processOrderCreation(orderId);
// 3. 記錄訂單日志
logOrderCreation(orderId);
return null;
} catch (Exception e) {
throw new RuntimeException("訂單創建失敗", e);
}
});
}
private boolean checkOrderExists(String orderId) {
// 實際業務邏輯
return false;
}
private void processOrderCreation(String orderId) {
// 實際業務邏輯
System.out.println("處理訂單創建: " + orderId);
// 模擬耗時操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void logOrderCreation(String orderId) {
// 實際業務邏輯
System.out.println("記錄訂單日志: " + orderId);
}
}
5. 控制器示例
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/order/{orderId}")
public String createOrder(@PathVariable String orderId) {
try {
orderService.createOrder(orderId);
return "訂單創建成功: " + orderId;
} catch (Exception e) {
return "訂單創建失敗: " + e.getMessage();
}
}
}
關鍵注意事項
-
鎖過期時間:
- 設置合理的時間(略大于業務執行時間)
- 過短:業務未完成鎖已釋放 → 數據不一致
- 過長:客戶端崩潰后鎖釋放延遲 → 系統可用性降低
-
唯一標識(requestId):
- 必須保證全局唯一(使用UUID)
- 確保只有鎖的持有者才能釋放鎖
-
看門狗機制:
- 解決業務執行時間超過鎖過期時間的問題
- 定期續期(建議在1/3過期時間時續期)
- 業務完成后立即停止看門狗
-
異常處理:
- 使用try-finally確保鎖最終被釋放
- 避免因異常導致鎖無法釋放
-
重試機制:
- 設置合理的等待時間和重試策略
- 使用隨機退避避免活鎖
潛在缺陷及解決方案
| 缺陷 | 解決方案 |
|---|---|
| 鎖提前過期 | 實現看門狗續期機制 |
| 非原子操作風險 | 使用Lua腳本保證原子性 |
| 單點故障 | 主從復制(但有數據丟失風險)或改用RedLock |
| GC暫停導致鎖失效 | 優化JVM參數,減少GC暫停時間 |
| 時鐘漂移問題 | 使用NTP同步時間,監控時鐘差異 |
| 鎖被誤刪 | 使用唯一標識驗證鎖持有者 |
最佳實踐建議
- 鎖粒度:盡量使用細粒度鎖(如訂單ID而非整個系統鎖)
- 超時設置:根據業務壓力動態調整鎖超時時間
- 監控報警:監控鎖等待時間、獲取失敗率等關鍵指標
- 熔斷機制:當Redis不可用時提供降級方案
- 壓力測試:模擬高并發場景驗證鎖的正確性
- 避免長時間持鎖:優化業務邏輯減少鎖持有時間
這個實現提供了生產環境中使用Redis分布式鎖的完整解決方案,包含了基本的鎖獲取/釋放、看門狗續期機制、以及易用的API封裝。在實際使用中,可以根據具體業務需求調整參數和實現細節。
?? 如果你喜歡這篇文章,請點贊支持! ?? 同時歡迎關注我的博客,獲取更多精彩內容!
本文來自博客園,作者:佛祖讓我來巡山,轉載請注明原文鏈接:http://www.rzrgm.cn/sun-10387834/p/19002364

浙公網安備 33010602011771號