Java分布式鎖實現(xiàn)方式詳解

Java分布式鎖詳解
1. 分布式鎖的概念
分布式鎖是一種在分布式系統(tǒng)中協(xié)調(diào)多個進程/服務(wù)對共享資源進行互斥訪問的機制。在單機系統(tǒng)中,我們可以使用Java內(nèi)置的鎖機制(如synchronized、ReentrantLock)來實現(xiàn)線程同步,但在分布式環(huán)境下,這些本地鎖機制無法跨JVM工作,因此需要分布式鎖。
典型應(yīng)用場景包括:
- 防止重復(fù)訂單提交
- 秒殺系統(tǒng)中的庫存扣減
- 定時任務(wù)的分布式調(diào)度
- 分布式環(huán)境下的緩存更新
2. 分布式鎖的實現(xiàn)方式
2.1 基于數(shù)據(jù)庫的實現(xiàn)
實現(xiàn)原理: 利用數(shù)據(jù)庫的唯一性約束或行鎖特性實現(xiàn)分布式鎖。常見方式包括:
- 創(chuàng)建鎖表,利用唯一索引防止重復(fù)獲取鎖
- 使用
SELECT ... FOR UPDATE語句鎖定記錄
示例代碼:
// 基于MySQL實現(xiàn)的分布式鎖
public class DatabaseDistributedLock {
private DataSource dataSource;
public boolean tryLock(String lockName, long timeout) {
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
// 使用FOR UPDATE加行鎖
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM distributed_lock WHERE lock_name = ? FOR UPDATE");
stmt.setString(1, lockName);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
// 鎖不存在則插入
PreparedStatement insertStmt = conn.prepareStatement(
"INSERT INTO distributed_lock(lock_name, owner, create_time) VALUES (?, ?, ?)");
insertStmt.setString(1, lockName);
insertStmt.setString(2, Thread.currentThread().getName());
insertStmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
insertStmt.executeUpdate();
}
conn.commit();
return true;
} catch (SQLException e) {
return false;
}
}
}優(yōu)缺點:
- 優(yōu)點:實現(xiàn)簡單,無需額外中間件
- 缺點:性能較差(數(shù)據(jù)庫I/O開銷大),非阻塞鎖實現(xiàn)復(fù)雜,存在單點故障風(fēng)險
2.2 基于Redis的實現(xiàn)
實現(xiàn)原理: 利用Redis的SETNX(SET if Not eXists)命令實現(xiàn)互斥性,通過設(shè)置過期時間防止死鎖。
RedLock算法(Redis官方推薦的分布式鎖實現(xiàn)):
- 獲取當(dāng)前時間(毫秒)
- 依次嘗試從N個獨立的Redis節(jié)點獲取鎖
- 計算獲取鎖消耗的總時間(小于鎖超時時間)且獲取到大多數(shù)節(jié)點(N/2+1)的鎖才算成功
- 鎖的實際有效時間 = 初始有效時間 - 獲取鎖消耗的時間
- 如果獲取鎖失敗,則向所有節(jié)點發(fā)送釋放鎖命令
示例代碼(使用Redisson客戶端):
// 使用Redisson實現(xiàn)分布式鎖
public class RedisDistributedLockExample {
public void doWithLock() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");
try {
// 嘗試獲取鎖,最多等待100秒,鎖定后10秒自動釋放
boolean isLocked = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (isLocked) {
// 執(zhí)行業(yè)務(wù)邏輯
System.out.println("Lock acquired, doing business logic...");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
redisson.shutdown();
}
}
}Redis分布式鎖的關(guān)鍵問題:
- 原子性:使用Lua腳本確保SETNX和EXPIRE操作的原子性
- 鎖續(xù)期:通過守護線程定期檢查并延長鎖的有效期
- 鎖釋放:確保只有鎖的持有者才能釋放鎖(value中存儲唯一標識)
- 集群容錯:主從切換可能導(dǎo)致鎖丟失,RedLock算法提供了一定解決方案
2.3 基于Zookeeper的實現(xiàn)
實現(xiàn)原理: 利用Zookeeper的臨時順序節(jié)點和Watch機制實現(xiàn)分布式鎖。
實現(xiàn)步驟:
- 在指定路徑下創(chuàng)建臨時順序節(jié)點
- 獲取父節(jié)點下的所有子節(jié)點并排序
- 判斷當(dāng)前節(jié)點是否為序號最小的節(jié)點:
- 是則獲取鎖成功
- 否則對前一個節(jié)點注冊Watcher
- 鎖釋放后,Zookeeper會通知下一個等待的節(jié)點
示例代碼(使用Curator框架):
public class ZookeeperDistributedLock {
private CuratorFramework client;
private InterProcessMutex lock;
public ZookeeperDistributedLock(String connectString, String lockPath) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
client.start();
lock = new InterProcessMutex(client, lockPath);
}
public void doWithLock(Runnable task) throws Exception {
try {
// 獲取鎖,最多等待5秒
if (lock.acquire(5, TimeUnit.SECONDS)) {
task.run();
}
} finally {
if (lock.isAcquiredInThisProcess()) {
lock.release();
}
}
}
}Zookeeper鎖的特點:
- 可靠性高:基于CP模型,數(shù)據(jù)一致性有保障
- 自動釋放:會話結(jié)束或中斷時臨時節(jié)點自動刪除
- 公平鎖:按照節(jié)點創(chuàng)建順序獲取鎖
- 性能較好:相比數(shù)據(jù)庫方案,但比Redis略低
3. 分布式鎖的關(guān)鍵特性
一個完善的分布式鎖實現(xiàn)應(yīng)具備以下特性:
- 互斥性:同一時刻只有一個客戶端能持有鎖
- 避免死鎖:鎖必須有超時機制或自動釋放機制
- 容錯性:即使部分節(jié)點故障,鎖服務(wù)仍然可用
- 可重入性:同一個客戶端可以多次獲取同一把鎖
- 高性能:獲取和釋放鎖的操作要高效
- 公平性:獲取鎖的順序與請求順序一致(可選)
4. 分布式鎖的最佳實踐
鎖粒度的選擇:
- 細粒度鎖:資源競爭精準,但管理復(fù)雜
- 粗粒度鎖:實現(xiàn)簡單,但并發(fā)度低
超時設(shè)置:
- 獲取鎖的超時時間:避免長時間等待
- 鎖持有的超時時間:業(yè)務(wù)操作應(yīng)在該時間內(nèi)完成
鎖的釋放:
- 必須放在finally塊中確保釋放
- 實現(xiàn)鎖的可重入性時要正確維護計數(shù)器
異常處理:
- 網(wǎng)絡(luò)分區(qū)時的處理策略
- 鎖服務(wù)不可用時的降級方案
監(jiān)控與告警:
- 監(jiān)控鎖的獲取成功率
- 監(jiān)控鎖的平均持有時間
5. 常見問題與解決方案
問題1:鎖提前過期
- 現(xiàn)象:業(yè)務(wù)操作未完成鎖已過期
- 解決方案:實現(xiàn)鎖續(xù)期機制(watch dog)
問題2:錯誤釋放他人鎖
- 現(xiàn)象:A客戶端釋放了B客戶端的鎖
- 解決方案:鎖value中存儲唯一標識,釋放時驗證
問題3:網(wǎng)絡(luò)分區(qū)導(dǎo)致腦裂
- 現(xiàn)象:多個客戶端同時持有鎖
- 解決方案:使用fencing token機制
問題4:鎖不可重入
- 現(xiàn)象:同一線程多次獲取鎖導(dǎo)致死鎖
- 解決方案:維護持有者線程和計數(shù)器
6. 總結(jié)與選型建議
- 數(shù)據(jù)庫鎖:適合并發(fā)量低、可靠性要求不高、已有數(shù)據(jù)庫環(huán)境的場景
- Redis鎖:適合高性能、高可用場景,但對一致性要求不能太高
- Zookeeper鎖:適合強一致性、高可靠場景,但性能相對較低
實際項目中,推薦使用成熟的框架如Redisson或Curator,它們已經(jīng)處理了各種邊界條件和異常情況。對于關(guān)鍵業(yè)務(wù),可以考慮結(jié)合多種實現(xiàn)方式提高可靠性。
到此這篇關(guān)于Java分布式鎖詳解的文章就介紹到這了,更多相關(guān)Java分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot 整合 JMSTemplate的示例代碼
這篇文章主要介紹了SpringBoot 整合 JMSTemplate的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
解析SpringBoot整合SpringDataRedis的過程
這篇文章主要介紹了SpringBoot整合SpringDataRedis的過程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
java調(diào)用淘寶api聯(lián)網(wǎng)查詢ip歸屬地
java聯(lián)網(wǎng)查詢IP歸屬地,原理是根據(jù)淘寶提供的service查詢IP的歸屬地并且解析http請求返回的json串2014-03-03
druid多數(shù)據(jù)源配置+Datasurce動態(tài)切換方式
這篇文章主要介紹了druid多數(shù)據(jù)源配置+Datasurce動態(tài)切換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Spring Boot自定義Banner實現(xiàn)代碼
這篇文章主要介紹了Spring Boot自定義Banner實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01

