目錄
一、分布式鎖的定義與核心作用
二、分布式鎖與普通鎖的核心區別
三、分布式鎖的底層原理與實現方式
1. 核心實現原理
2. 主流實現方案對比
3. 關鍵技術細節
四、典型問題與解決方案
五、總結
六、具體代碼實現
一、分布式鎖的定義與核心作用
分布式鎖是一種在分布式系統中協調多進程/節點對共享資源進行互斥訪問的機制。其核心作用是確保同一時間只有一個進程能夠操作共享資源,解決分布式環境下的并發沖突問題(如超賣、數據覆蓋等)。
二、分布式鎖與普通鎖的核心區別
對比維度 | 普通鎖(線程/進程鎖) | 分布式鎖 |
作用范圍 | 單機環境(同一JVM或進程內) | 跨機器、跨JVM的分布式環境 |
數據存儲 | 基于內存(如synchronized、Lock) | 基于外部存儲(如Redis、ZooKeeper、數據庫) |
鎖失效風險 | 無網絡延遲或節點故障風險 | 需處理網絡分區、節點宕機、時鐘同步等問題 |
典型應用場景 | 單機多線程資源競爭 | 分布式服務、微服務集群、數據庫流量控制等 |
三、分布式鎖的底層原理與實現方式
1. 核心實現原理
- 互斥性:通過唯一標識(如Redis的Key、ZooKeeper節點路徑)確保同一時間僅有一個客戶端持有鎖。
- 超時機制:設置鎖的過期時間,避免死鎖(如Redis的
PX
參數)。 - 原子性操作:加鎖、解鎖需通過原子命令(如Redis的
SETNX
+EXPIRE
組合或Lua腳本)實現。
2. 主流實現方案對比
實現方式 | 原理 | 優點 | 缺點 |
Redis | 基于 命令,結合唯一值(UUID)和Lua腳本保證原子性 。 | 高性能、易擴展 | 主從切換可能導致鎖失效(需RedLock或Redisson優化) |
ZooKeeper | 基于臨時有序節點 ,最小序號節點獲得鎖,通過Watcher監聽節點變化 。 | 強一致性、自動釋放鎖(節點斷開則刪除臨時節點) | 性能較低、實現復雜 |
數據庫 | 通過唯一約束(如MySQL行鎖、樂觀鎖)或專用鎖表 。 | 簡單易用 | 性能差、高并發場景易成瓶頸 |
3. 關鍵技術細節
- 鎖續期(看門狗機制):Redisson通過后臺線程定期檢查并延長鎖有效期,避免業務未完成時鎖過期。
- 可重入性:通過記錄線程標識和重入次數(如Redis的Hash結構)支持同一線程多次加鎖。
- 容錯設計:
-
- Redis的RedLock算法需半數以上節點加鎖成功,避免主從切換問題。
- ZooKeeper通過臨時節點自動清理解決進程宕機導致的死鎖。
四、典型問題與解決方案
- 鎖過期但業務未完成
-
- 方案:使用守護線程續期(如Redisson的看門狗)或超時回滾+告警。
- 鎖誤刪(非持有者釋放鎖)
-
- 方案:解鎖時校驗唯一標識(如UUID),并通過Lua腳本保證原子性。
五、總結
分布式鎖通過外部存儲系統實現跨進程資源互斥,需權衡性能、一致性和復雜度。Redis適合高頻低一致性要求的場景,ZooKeeper適用于強一致性但低并發場景,而數據庫鎖僅作為簡單場景的備選。實際選型需結合業務需求和容錯能力(如Redisson整合Redis的方案較優)。
六、具體代碼實現
我們這邊是查詢數據
首先如果緩存命中 就直接返回數據
否則是要去數據庫查詢數據
使用分布式鎖 讓同一時間只能允許一個線程更新緩存
防止碰巧有寫入緩存的線程結束
我們可以進行一個二次檢查 防止那個碰巧情況
因為緩存一旦存在 再次寫入 數據會進行疊加
確認了在分布式鎖內 緩存依舊為空
之后我們就可以去數據庫查詢數據
@Override// 這邊我們使用redis來輔助mysql查詢 因為數據庫壓力實在是太大了(服務器帶寬太低)public List<GetAllContentResp> getAll() {// 異常處理try {// 1. 構建帶業務標識的復合KeyString cacheKey = "balloonSentences:all" + DATA_VERSION;// 2. 帶熔斷的緩存讀取 如果緩存擊中 直接返回即可 返回的是所有數據List<GetAllContentResp> cachedData = redisService.getList(cacheKey, 0, -1);if (cachedData != null) {if (cachedData.isEmpty()) { // 空值緩存處理return Collections.emptyList();}elasticsearchService.saveProduct(cachedData); // 寫到elasticsearch里面去return cachedData;} else {// 3. 分布式鎖防穿透 同一時間只允許一個線程更新緩存RLock lock = redissonClient.getLock("lock:" + cacheKey);try {lock.lock(5, TimeUnit.SECONDS);// 二次檢查cachedData = redisService.getList(cacheKey, 0, -1);if (cachedData != null) return cachedData;// 4. 數據庫查詢List<GetAllContentResp> dbData = tSentencesMapper.getAll();// 5. 異步寫緩存和elasticsearch(保證數據庫操作成功)CompletableFuture.runAsync(() -> {// 隨機化TTL防雪崩redisService.setList(cacheKey, dbData, RandomUtil.randomInt(30, 60), TimeUnit.MINUTES);elasticsearchService.saveProduct(dbData); // 寫到elasticsearch里面去});return dbData;} finally {lock.unlock();}}} catch (Exception e) {e.printStackTrace();}return null;}