分布式鎖詳解以及Spring?Boot實戰(zhàn)示例代碼
前言
在分布式系統(tǒng)中,多個服務實例可能同時操作共享資源(如數(shù)據(jù)庫中的同一訂單、庫存記錄),若缺乏協(xié)調機制,會導致數(shù)據(jù)不一致(如超賣、重復下單)。分布式鎖正是解決這類問題的核心技術,它能保證同一時間只有一個服務實例執(zhí)行特定臨界區(qū)代碼。
一、分布式鎖的核心特性
一個可靠的分布式鎖需滿足以下特性:
- 互斥性:任意時刻只有一個線程持有鎖。
- 安全性:鎖只能被持有它的線程釋放。
- 可用性:即使部分節(jié)點故障,鎖仍能正常獲取和釋放。
- 防死鎖:避免因線程崩潰導致鎖永久無法釋放。
- 冪等性:重復獲取 / 釋放鎖不會產(chǎn)生副作用。
二、分布式鎖的實現(xiàn)方案及原理
常見實現(xiàn)方式包括基于數(shù)據(jù)庫、Redis、ZooKeeper 等,不同方式的原理各有不同。
1. 基于數(shù)據(jù)庫的分布式鎖
原理:利用數(shù)據(jù)庫的唯一索引或悲觀鎖來實現(xiàn)。例如,創(chuàng)建一張鎖表,包含資源標識、持有線程標識、過期時間等字段,給資源標識字段創(chuàng)建唯一索引。當需要獲取鎖時,向表中插入一條記錄,若插入成功則表示獲取到鎖;釋放鎖時,刪除該記錄。為防止死鎖,可定期清理過期未釋放的鎖。
優(yōu)缺點:
- 優(yōu)點:實現(xiàn)簡單,無需額外中間件。
- 缺點:性能較差,數(shù)據(jù)庫壓力大;易出現(xiàn)鎖表問題;不支持鎖自動續(xù)期等高級特性。
2. 基于 Redis 的分布式鎖
原理:利用 Redis 的SET命令的原子性。核心命令如下:
# 僅當key不存在時設置值,過期時間10秒,返回OK表示獲取鎖成功 SET lock:resource true NX PX 10000
- NX:僅在鍵不存在時才設置(保證互斥性)。
- PX 10000:設置鍵的過期時間為 10 秒(防死鎖)。
釋放鎖時,需通過 Lua 腳本保證原子性,先判斷鎖是否由當前線程持有,再刪除鎖:
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end優(yōu)缺點:
- 優(yōu)點:性能高,操作簡單;支持過期時間設置。
- 缺點:在 Redis 集群環(huán)境下,可能存在主從同步延遲導致的鎖丟失問題;需自行處理鎖續(xù)期等問題。
由于 Redis 的高性能和易用性,它成為分布式鎖的主流選擇,本文后續(xù)重點介紹基于 Redis 的分布式鎖實現(xiàn)。
3. 基于 ZooKeeper 的分布式鎖
原理:利用 ZooKeeper 的節(jié)點特性和 Watcher 機制。ZooKeeper 的節(jié)點分為持久節(jié)點、臨時節(jié)點、持久順序節(jié)點、臨時順序節(jié)點。分布式鎖通常使用臨時順序節(jié)點,當需要獲取鎖時,在指定節(jié)點下創(chuàng)建一個臨時順序節(jié)點,然后判斷當前節(jié)點是否為序號最小的節(jié)點,若是則獲取到鎖;若不是,則監(jiān)聽序號比當前節(jié)點小的最后一個節(jié)點,當該節(jié)點被刪除時,重新判斷。釋放鎖時,刪除創(chuàng)建的臨時節(jié)點,由于是臨時節(jié)點,當持有鎖的線程崩潰時,節(jié)點會自動刪除,避免死鎖。
優(yōu)缺點:
- 優(yōu)點:可靠性高,不存在鎖丟失問題;支持公平鎖;自帶 Watcher 機制,可實現(xiàn)鎖的自動釋放和喚醒。
- 缺點:性能相對 Redis 較低;部署和維護成本高。
三、Spring Boot 集成分布式鎖的相關依賴及對比
1. 基于 Redis 的依賴
- spring-boot-starter-data-redis:
- 提供了 Redis 的基本操作模板(RedisTemplate),可用于手動實現(xiàn)分布式鎖。
- 需自行處理鎖的獲取、釋放、續(xù)期等邏輯,實現(xiàn)相對復雜,但靈活性高。
- redisson-spring-boot-starter:
- 是 Redis 官方推薦的 Java 客戶端,內置了分布式鎖的完整實現(xiàn),支持自動續(xù)期、公平鎖、可重入鎖等高級特性。
- 封裝了復雜的底層邏輯,使用簡單,適合生產(chǎn)環(huán)境。
2. 基于 ZooKeeper 的依賴
- spring-cloud-starter-zookeeper-discovery:
- 主要用于服務發(fā)現(xiàn),但也可借助 ZooKeeper 客戶端操作 ZooKeeper 實現(xiàn)分布式鎖。
- 需要自行基于 ZooKeeper 的 API 實現(xiàn)鎖的邏輯,較為繁瑣。
- curator-recipes:
- 是 ZooKeeper 的客戶端框架,提供了分布式鎖等常用功能的封裝,如 InterProcessMutex 等類可直接用于實現(xiàn)分布式鎖。
- 簡化了 ZooKeeper 分布式鎖的實現(xiàn),可靠性高。
3. 依賴對比
依賴 | 基于中間件 | 特點 | 適用場景 |
spring-boot-starter-data-redis | Redis | 基礎操作支持,需自行實現(xiàn)鎖邏輯 | 簡單場景,對靈活性要求高 |
redisson-spring-boot-starter | Redis | 內置完整鎖實現(xiàn),支持高級特性 | 生產(chǎn)環(huán)境,復雜業(yè)務場景 |
spring-cloud-starter-zookeeper-discovery | ZooKeeper | 主要用于服務發(fā)現(xiàn),鎖實現(xiàn)需自行開發(fā) | 已使用 ZooKeeper 做服務發(fā)現(xiàn),簡單鎖場景 |
curator-recipes | ZooKeeper | 封裝了分布式鎖功能,可靠性高 | 對鎖可靠性要求高的場景 |
四、Spring Boot 集成 Redis 分布式鎖實戰(zhàn)
1. 環(huán)境準備
pom.xml 依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId> <!-- 推薦使用Redisson簡化鎖操作 -->
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version>
</dependency>Redis 配置(application.yml):
spring:
redis:
host: localhost
port: 6379
database: 0
timeout: 3000ms2. 基于 Redisson 的分布式鎖實現(xiàn)
Redisson 是 Redis 官方推薦的 Java 客戶端,內置了分布式鎖的完整實現(xiàn),支持自動續(xù)期、公平鎖、可重入鎖等高級特性。
分布式鎖工具類:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
private final RedissonClient redissonClient;
public RedisDistributedLock(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
/**
* 獲取分布式鎖
* @param lockKey 鎖標識
* @param waitTime 等待時間(獲取鎖的最大等待時長)
* @param leaseTime 鎖持有時間(自動釋放時間)
* @return 鎖對象
*/
public RLock lock(String lockKey, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 嘗試獲取鎖,最多等待waitTime,持有l(wèi)easeTime后自動釋放
boolean isLocked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
if (isLocked) {
return lock;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return null;
}
/**
* 釋放鎖
* @param lock 鎖對象
*/
public void unlock(RLock lock) {
if (lock != null && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}3. 業(yè)務場景示例:庫存扣減
Service 層代碼:
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class InventoryService {
@Resource
private RedisDistributedLock distributedLock;
@Resource
private InventoryMapper inventoryMapper; // 假設已實現(xiàn)數(shù)據(jù)庫操作
/**
* 扣減商品庫存
* @param productId 商品ID
* @param quantity 扣減數(shù)量
* @return 操作結果
*/
public boolean deductInventory(Long productId, int quantity) {
// 鎖標識:通常用業(yè)務資源唯一標識(如商品ID)
String lockKey = "lock:inventory:" + productId;
RLock lock = null;
try {
// 獲取鎖:最多等待3秒,持有10秒后自動釋放
lock = distributedLock.lock(lockKey, 3, 10);
if (lock == null) {
// 獲取鎖失?。ㄈ绯瑫r)
return false;
}
// 臨界區(qū)代碼:查詢庫存并扣減
int currentStock = inventoryMapper.selectStockByProductId(productId);
if (currentStock >= quantity) {
inventoryMapper.updateStock(productId, currentStock - quantity);
return true;
} else {
// 庫存不足
return false;
}
} finally {
// 確保鎖釋放
distributedLock.unlock(lock);
}
}
}五、關鍵注意事項
- 鎖的粒度:鎖標識應精準到具體資源(如lock:order:123而非lock:order),避免鎖范圍過大導致性能瓶頸。
- 過期時間設置:需大于業(yè)務執(zhí)行時間,Redisson 的watch dog機制會自動續(xù)期(默認每 30 秒續(xù)期一次)。
- 異常處理:必須在finally塊中釋放鎖,避免因業(yè)務異常導致鎖泄漏。
- 重試機制:獲取鎖失敗時可添加有限重試邏輯(如循環(huán) 3 次),提高成功率。
六、進階優(yōu)化方向
- 公平鎖:通過redissonClient.getFairLock(lockKey)實現(xiàn),避免線程饑餓。
- 紅鎖(RedLock):在多 Redis 節(jié)點環(huán)境中,通過多個實例獲取鎖提高可靠性(適合極高一致性場景)。
- 緩存與數(shù)據(jù)庫一致性:結合本地鎖(synchronized)與分布式鎖,減少分布式鎖的使用頻率。
通過不同的分布式鎖實現(xiàn)方式和相關依賴,Spring Boot 應用可在分布式環(huán)境中安全地操作共享資源。在實際開發(fā)中,需根據(jù)業(yè)務場景和性能需求選擇合適的實現(xiàn)方式和依賴,Redisson 等成熟工具可大幅降低實現(xiàn)復雜度,建議在生產(chǎn)環(huán)境中優(yōu)先采用。
總結
到此這篇關于分布式鎖詳解以及Spring Boot實戰(zhàn)的文章就介紹到這了,更多相關分布式鎖及Spring Boot實戰(zhàn)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決idea 通過build project 手動觸發(fā)熱部署失敗的問題
在debug運行項目的過程中,并且保證(不添加方法,不修改方法名)一定的規(guī)則的情況下,可以通過build project 來手動熱部署項目,本文給大家介紹解決idea 通過build project 手動觸發(fā)熱部署失敗的問題,感興趣的朋友一起看看吧2023-12-12
Java生產(chǎn)者和消費者例子_動力節(jié)點Java學院整理
生產(chǎn)者-消費者(producer-consumer)問題,也稱作有界緩沖區(qū)(bounded-buffer)問題,兩個進程共享一個公共的固定大小的緩沖區(qū)。下文通過實例給大家介紹java生產(chǎn)者和消費者,感興趣的朋友一起學習吧2017-05-05
原理分析SonarQube中IdentityProvider賬戶互斥現(xiàn)象
這篇文章主要為大家介紹分析SonarQube中IdentityProvider賬戶互斥現(xiàn)象原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-02-02
關于mybatis if else if 條件判斷SQL片段表達式取值和拼接問題
這篇文章主要介紹了mybatis if else if 條件判斷SQL片段表達式取值和拼接,文章通過自己真實使用的例子給大家詳細介紹,需要的朋友可以參考下2021-09-09

