基于redis實現分布式鎖方案實戰

分布式鎖的進階實現與優化方案

作為Java高級開發工程師,我將為您提供更完善的Redis分布式鎖實現方案,包含更多生產級考量。

1. 生產級Redis分布式鎖實現

1.1 完整實現類(支持可重入、自動續約)

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;public class RedisDistributedLock {private final JedisPool jedisPool;private final ScheduledExecutorService scheduler;// 本地存儲鎖的持有計數(用于可重入)private final ThreadLocal<Map<String, LockEntry>> lockHoldCounts = ThreadLocal.withInitial(HashMap::new);// 鎖續約的Future集合private final ConcurrentMap<String, ScheduledFuture<?>> renewalFutures = new ConcurrentHashMap<>();private static class LockEntry {final String requestId;final AtomicInteger holdCount;LockEntry(String requestId) {this.requestId = requestId;this.holdCount = new AtomicInteger(1);}}public RedisDistributedLock(JedisPool jedisPool) {this.jedisPool = jedisPool;this.scheduler = Executors.newScheduledThreadPool(4);}public boolean tryLock(String lockKey, String requestId, int expireTime) {return tryLock(lockKey, requestId, expireTime, 0, 0);}public boolean tryLock(String lockKey, String requestId, int expireTime, long maxWaitTime, long retryInterval) {long startTime = System.currentTimeMillis();try {// 檢查是否已經持有鎖(可重入)LockEntry entry = lockHoldCounts.get().get(lockKey);if (entry != null && entry.requestId.equals(requestId)) {entry.holdCount.incrementAndGet();return true;}// 嘗試獲取鎖while (true) {if (acquireLock(lockKey, requestId, expireTime)) {// 獲取成功,記錄持有信息lockHoldCounts.get().put(lockKey, new LockEntry(requestId));// 啟動自動續約scheduleRenewal(lockKey, requestId, expireTime);return true;}// 檢查是否超時if (maxWaitTime > 0 && System.currentTimeMillis() - startTime > maxWaitTime) {return false;}// 等待重試if (retryInterval > 0) {try {Thread.sleep(retryInterval);} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}}} catch (Exception e) {// 異常處理return false;}}private boolean acquireLock(String lockKey, String requestId, int expireTime) {try (Jedis jedis = jedisPool.getResource()) {SetParams params = SetParams.setParams().nx().px(expireTime);return "OK".equals(jedis.set(lockKey, requestId, params));}}private void scheduleRenewal(String lockKey, String requestId, int expireTime) {// 計算續約間隔(通常是過期時間的1/3)long renewalInterval = expireTime * 2 / 3;ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> {try {renewLock(lockKey, requestId, expireTime);} catch (Exception e) {// 續約失敗,取消任務renewalFutures.remove(lockKey);}}, renewalInterval, renewalInterval, TimeUnit.MILLISECONDS);renewalFutures.put(lockKey, future);}public boolean renewLock(String lockKey, String requestId, int expireTime) {try (Jedis jedis = jedisPool.getResource()) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('pexpire', KEYS[1], ARGV[2]) " +"else " +"return 0 " +"end";Object result = jedis.eval(script, Collections.singletonList(lockKey), Arrays.asList(requestId, String.valueOf(expireTime)));return "OK".equals(result);}}public boolean releaseLock(String lockKey, String requestId) {try {LockEntry entry = lockHoldCounts.get().get(lockKey);if (entry == null || !entry.requestId.equals(requestId)) {return false;}// 減少持有計數if (entry.holdCount.decrementAndGet() > 0) {return true;}// 完全釋放鎖lockHoldCounts.get().remove(lockKey);// 取消續約任務ScheduledFuture<?> future = renewalFutures.remove(lockKey);if (future != null) {future.cancel(false);}// 釋放Redis鎖try (Jedis jedis = jedisPool.getResource()) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) " +"else " +"return 0 " +"end";Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));return Long.valueOf(1L).equals(result);}} catch (Exception e) {return false;}}public void shutdown() {scheduler.shutdown();try {if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {scheduler.shutdownNow();}} catch (InterruptedException e) {scheduler.shutdownNow();Thread.currentThread().interrupt();}}
}

1.2 鎖工廠模式(支持多種鎖類型)

public interface DistributedLock {boolean tryLock(String lockKey, String requestId, int expireTime);boolean tryLock(String lockKey, String requestId, int expireTime, long maxWaitTime, long retryInterval);boolean releaseLock(String lockKey, String requestId);
}public class RedisLockFactory {private final JedisPool jedisPool;public RedisLockFactory(JedisPool jedisPool) {this.jedisPool = jedisPool;}public DistributedLock createSimpleLock() {return new SimpleRedisLock(jedisPool);}public DistributedLock createReentrantLock() {return new ReentrantRedisLock(jedisPool);}public DistributedLock createReadWriteLock() {return new RedisReadWriteLock(jedisPool);}private static class SimpleRedisLock implements DistributedLock {// 簡單實現(同前面基礎實現)}private static class ReentrantRedisLock extends RedisDistributedLock {// 可重入實現(同前面完整實現)}private static class RedisReadWriteLock implements DistributedLock {// 讀寫鎖實現}
}

2. 高級特性實現

2.1 讀寫鎖實現

public class RedisReadWriteLock {private final JedisPool jedisPool;private static final String READ_LOCK_PREFIX = "READ_LOCK:";private static final String WRITE_LOCK_PREFIX = "WRITE_LOCK:";public RedisReadWriteLock(JedisPool jedisPool) {this.jedisPool = jedisPool;}public boolean tryReadLock(String lockKey, String requestId, int expireTime) {try (Jedis jedis = jedisPool.getResource()) {// 檢查是否有寫鎖if (jedis.exists(WRITE_LOCK_PREFIX + lockKey)) {return false;}// 獲取讀鎖String readLockKey = READ_LOCK_PREFIX + lockKey;Long count = jedis.incr(readLockKey);if (count == 1L) {// 第一次獲取讀鎖,設置過期時間jedis.pexpire(readLockKey, expireTime);}return true;}}public boolean tryWriteLock(String lockKey, String requestId, int expireTime) {try (Jedis jedis = jedisPool.getResource()) {// 檢查是否有讀鎖if (jedis.exists(READ_LOCK_PREFIX + lockKey)) {return false;}// 獲取寫鎖SetParams params = SetParams.setParams().nx().px(expireTime);return "OK".equals(jedis.set(WRITE_LOCK_PREFIX + lockKey, requestId, params));}}// 釋放方法類似...
}

2.2 公平鎖實現(基于Redis列表)

public class RedisFairLock {private final JedisPool jedisPool;private static final String QUEUE_PREFIX = "LOCK_QUEUE:";private static final String LOCK_PREFIX = "LOCK:";public RedisFairLock(JedisPool jedisPool) {this.jedisPool = jedisPool;}public boolean tryLock(String lockKey, String requestId, int expireTime, long maxWaitTime) {long startTime = System.currentTimeMillis();String queueKey = QUEUE_PREFIX + lockKey;String lockRealKey = LOCK_PREFIX + lockKey;try (Jedis jedis = jedisPool.getResource()) {// 加入等待隊列jedis.rpush(queueKey, requestId);try {while (true) {// 檢查是否輪到自己String firstRequestId = jedis.lindex(queueKey, 0);if (requestId.equals(firstRequestId)) {// 嘗試獲取鎖SetParams params = SetParams.setParams().nx().px(expireTime);if ("OK".equals(jedis.set(lockRealKey, requestId, params))) {return true;}}// 檢查超時if (System.currentTimeMillis() - startTime > maxWaitTime) {// 從隊列中移除自己jedis.lrem(queueKey, 0, requestId);return false;}// 短暫等待Thread.sleep(100);}} finally {// 確保最終從隊列中移除(防止異常情況)jedis.lrem(queueKey, 0, requestId);}} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}public void releaseLock(String lockKey, String requestId) {try (Jedis jedis = jedisPool.getResource()) {String lockRealKey = LOCK_PREFIX + lockKey;String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) " +"else " +"return 0 " +"end";jedis.eval(script, Collections.singletonList(lockRealKey), Collections.singletonList(requestId));}}
}

3. 生產環境最佳實踐

3.1 配置建議

  1. 鎖過期時間

    • 根據業務操作的最長時間設置,通常為業務平均耗時的3倍
    • 例如:業務平均耗時200ms,可設置鎖過期時間為600ms
  2. 續約間隔

    • 設置為過期時間的1/3到1/2
    • 例如:過期時間600ms,續約間隔200-300ms
  3. 重試策略

    • 初始重試間隔50-100ms
    • 可考慮指數退避策略

3.2 監控與告警

public class LockMonitor {private final JedisPool jedisPool;private final MeterRegistry meterRegistry; // 假設使用Micrometerpublic LockMonitor(JedisPool jedisPool, MeterRegistry meterRegistry) {this.jedisPool = jedisPool;this.meterRegistry = meterRegistry;}public void monitorLockStats() {// 監控鎖獲取成功率Timer lockAcquireTimer = Timer.builder("redis.lock.acquire.time").description("Time taken to acquire redis lock").register(meterRegistry);// 監控鎖等待時間DistributionSummary waitTimeSummary = DistributionSummary.builder("redis.lock.wait.time").description("Time spent waiting for redis lock").register(meterRegistry);// 監控鎖競爭情況Gauge.builder("redis.lock.queue.size", () -> {try (Jedis jedis = jedisPool.getResource()) {return jedis.llen("LOCK_QUEUE:important_lock");}}).description("Number of clients waiting for lock").register(meterRegistry);}
}

3.3 異常處理策略

  1. Redis不可用時的降級策略
    • 本地降級鎖(僅適用于單機或可以接受短暫不一致的場景)
    • 快速失敗,避免系統雪崩
public class DegradableRedisLock implements DistributedLock {private final DistributedLock redisLock;private final ReentrantLock localLock = new ReentrantLock();private final CircuitBreaker circuitBreaker;public DegradableRedisLock(DistributedLock redisLock) {this.redisLock = redisLock;this.circuitBreaker = CircuitBreaker.ofDefaults("redisLock");}@Overridepublic boolean tryLock(String lockKey, String requestId, int expireTime) {return CircuitBreaker.decorateSupplier(circuitBreaker, () -> {try {return redisLock.tryLock(lockKey, requestId, expireTime);} catch (Exception e) {// Redis不可用,降級到本地鎖return localLock.tryLock();}}).get();}// 其他方法實現類似...
}

4. 測試方案

4.1 單元測試

public class RedisDistributedLockTest {private RedisDistributedLock lock;private JedisPool jedisPool;@BeforeEachvoid setUp() {jedisPool = new JedisPool("localhost");lock = new RedisDistributedLock(jedisPool);}@Testvoid testLockAndUnlock() {String lockKey = "test_lock";String requestId = UUID.randomUUID().toString();assertTrue(lock.tryLock(lockKey, requestId, 10000));assertTrue(lock.releaseLock(lockKey, requestId));}@Testvoid testReentrantLock() {String lockKey = "test_reentrant_lock";String requestId = UUID.randomUUID().toString();assertTrue(lock.tryLock(lockKey, requestId, 10000));assertTrue(lock.tryLock(lockKey, requestId, 10000)); // 可重入assertTrue(lock.releaseLock(lockKey, requestId));assertTrue(lock.releaseLock(lockKey, requestId)); // 需要釋放兩次}// 更多測試用例...
}

4.2 并發測試

@Test
void testConcurrentLock() throws InterruptedException {String lockKey = "concurrent_test_lock";int threadCount = 10;CountDownLatch latch = new CountDownLatch(threadCount);AtomicInteger successCount = new AtomicInteger();for (int i = 0; i < threadCount; i++) {new Thread(() -> {String requestId = UUID.randomUUID().toString();if (lock.tryLock(lockKey, requestId, 1000, 5000, 100)) {try {successCount.incrementAndGet();Thread.sleep(100); // 模擬業務處理} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {lock.releaseLock(lockKey, requestId);}}latch.countDown();}).start();}latch.await();assertEquals(1, successCount.get()); // 確保只有一個線程獲取到鎖
}

5. 性能優化建議

  1. 連接池優化

    • 合理配置Jedis連接池大小
    • 使用try-with-resources確保連接釋放
  2. Lua腳本優化

    • 預加載常用Lua腳本
    • 減少腳本復雜度
  3. 批量操作

    • 對于RedLock等多節點場景,考慮使用管道(pipeline)
  4. 本地緩存

    • 對于頻繁使用的鎖信息,可考慮本地緩存

6. 替代方案對比

方案優點缺點適用場景
Redis單節點實現簡單,性能高單點故障,可靠性較低對可靠性要求不高的場景
Redis集群+RedLock可靠性較高實現復雜,性能較低對可靠性要求高的場景
Zookeeper可靠性高,原生支持臨時節點性能較低,依賴Zookeeper強一致性要求的場景
數據庫實現無需額外組件性能差,容易成為瓶頸簡單場景,并發量低

這個進階方案提供了生產環境所需的完整功能,包括可重入鎖、讀寫鎖、公平鎖等高級特性,以及監控、降級等生產級考量。您可以根據實際項目需求選擇合適的實現方式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/81170.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/81170.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/81170.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

XML簡要介紹

實際上現在的Java Web項目中更多的是基于springboot開發的&#xff0c;所以很少再使用xml去配置項目。所以我們的目的就是盡可能快速的去了解如何讀懂和使用xml文件&#xff0c;對于DTD&#xff0c;XMLSchema這類約束的學習可以放松&#xff0c;主要是確保自己知道這里面的大致…

UI自動化測試中,一個完整的斷言應所需要考慮的問題

在UI自動化測試中,一個完整的斷言應全面覆蓋用戶界面(UI)的功能性、交互性和視覺正確性。以下是斷言需要包含的核心內容及詳細說明: 一、基礎元素驗證 存在性斷言 驗證元素存在于DOM中示例代碼(Python + Selenium):assert driver.find_element(By.ID, "submit_btn&…

[Java][Leetcode middle] 238. 除自身以外數組的乘積

第一個想法是&#xff1a; 想求出所有元素乘積&#xff0c;然后除以i對應的元素本書&#xff1b;這個想法是完全錯誤的&#xff1a; nums[I] 可能有0題目要求了不能用除法 第二個想法是&#xff1a; 其實寫之前就知道會超時&#xff0c;但是我什么都做不到啊&#xff01; 雙…

5.16本日總結

一、英語 背誦list30&#xff0c;復習list1 二、數學 學習14講部分內容&#xff0c;訂正30講13講題目 三、408 學習計網5.3知識點&#xff0c;完成5.1&#xff0c;5.2題目并訂正 四、總結 高數對于基本定義概念類題目掌握不好&#xff0c;做題時往往不會下手&#xff0c…

深度學習---常用優化器

優化器一&#xff1a;Adam&#xff08;Adaptive Moment Estimation&#xff09; 一、適用場景總結&#xff08;實踐導向&#xff09; 場景是否推薦用 Adam說明小模型訓練&#xff08;如 MLP、CNN&#xff09;???穩定、無需復雜調參&#xff0c;適合快速實驗初學者使用或結構…

SparkSQL 連接 MySQL 并添加新數據:實戰指南

SparkSQL 連接 MySQL 并添加新數據&#xff1a;實戰指南 在大數據處理中&#xff0c;SparkSQL 作為 Apache Spark 的重要組件&#xff0c;能夠方便地與外部數據源進行交互。MySQL 作為廣泛使用的關系型數據庫&#xff0c;與 SparkSQL 的結合可以充分發揮兩者的優勢。本文將詳細…

基于對抗性后訓練的快速文本到音頻生成:stable-audio-open-small 模型論文速讀

Fast Text-to-Audio Generation with Adversarial Post-Training 論文解析 一、引言與背景 文本到音頻系統的局限性&#xff1a;當前文本到音頻生成系統性能雖佳&#xff0c;但推理速度慢&#xff08;需數秒至數分鐘&#xff09;&#xff0c;限制了其在創意領域的應用。 研究…

AI畫圖Stable Diffusion web UI學習筆記(中)

本文記錄講解AI畫圖工具Stable Diffusion web UI的部分基本使用方法&#xff0c;以便進行學習。AI畫圖Stable Diffusion web UI學習筆記分為上、中、下三篇文章。 我在 AI畫圖Stable Diffusion web UI學習筆記&#xff08;上&#xff09;_webui-CSDN博客 這篇文章中介紹了Stabl…

安全與智能的雙向奔赴,安恒信息先行一步

人類文明發展的長河中&#xff0c;每一次技術變革都重新書寫了安全的定義。 從蒸汽機的轟鳴到電力的普及&#xff0c;從互聯網的誕生到人工智能的崛起&#xff0c;技術創新與變革從未停止對于安全的挑戰。今天&#xff0c;我們又站在一個關鍵的歷史節點&#xff1a;AI大模型的…

【Reality Capture 】02:Reality Capture1.5中文版軟件設置與介紹

文章目錄 一、如何設置中文二、如何設置界面分區三、如何切換二三維窗口四、工具欄有多個視圖選項卡RealityCapture是虛幻引擎旗下一款三維建模軟件,跟我們常用的三維建模軟件一樣,可以從圖像或激光掃描中創建實景三維模型和正射影像等產品。可用于建筑、測繪、游戲和視覺特效…

真題卷001——算法備賽

藍橋杯2024年C/CB組國賽卷 1.合法密碼 問題描述 小藍正在開發自己的OJ網站。他要求用戶的密碼必須符合一下條件&#xff1a; 長度大于等于8小于等于16必須包含至少一個數字字符和至少一個符號字符 請計算一下字符串&#xff0c;有多少個子串可以當作合法密碼。字符串為&am…

17.three官方示例+編輯器+AI快速學習webgl_buffergeometry_lines

本實例主要講解內容 這個Three.js示例展示了如何使用BufferGeometry創建大量線段&#xff0c;并通過**變形目標(Morph Targets)**實現動態變形效果。通過隨機生成的點云數據&#xff0c;結合頂點顏色和變形動畫&#xff0c;創建出一個視覺效果豐富的3D線條場景。 核心技術包括…

InfluxDB 2.7 連續查詢實戰指南:Task 替代方案詳解

InfluxDB 2.7 引入了 Task 功能&#xff0c;作為連續查詢&#xff08;CQ&#xff09;的現代替代方案。本文詳細介紹了如何使用 Task 實現傳統 CQ 的功能&#xff0c;包括語法解析、示例代碼、參數對比以及典型應用場景。通過實際案例和最佳實踐&#xff0c;幫助開發者高效遷移并…

Pytorch張量和損失函數

文章目錄 張量張量類型張量例子使用概率分布創建張量正態分布創建張量 (torch.normal)正態分布創建張量示例標準正態分布創建張量標準正態分布創建張量示例均勻分布創建張量均勻分布創建張量示例 激活函數常見激活函數 損失函數(Pytorch API)L1范數損失函數均方誤差損失函數交叉…

大模型在數據分析領域的研究綜述

大模型在業務指標拆解中的應用場景與方法研究 隨著人工智能技術的快速發展&#xff0c;大模型&#xff08;Large Language Models, LLMs&#xff09;在數據分析領域的應用日益廣泛。尤其是在業務指標拆解這一復雜任務中&#xff0c;大模型展現了其獨特的價值和潛力。通過對多維…

JAVA:ResponseBodyEmitter 實現異步流式推送的技術指南

1、簡述 在許多場景下,我們希望后端能夠以流式、實時的方式推送數據給前端,比如消息通知、日志實時展示、進度條更新等。Spring Boot 提供了 ResponseBodyEmitter 機制,可以讓我們在 Controller 中異步地推送數據,從而實現實時流式輸出。 樣例代碼:https://gitee.com/lh…

Spring Boot循環依賴的陷阱與解決方案:如何打破“Bean創建死循環”?

引言 在Spring Boot開發中&#xff0c;你是否遇到過這樣的錯誤信息&#xff1f; The dependencies of some of the beans in the application context form a cycle 這表示你的應用出現了循環依賴。盡管Spring框架通過巧妙的機制解決了部分循環依賴問題&#xff0c;但在實際開…

如何閱讀、學習 Tcc (Tiny C Compiler) 源代碼?如何解析 Tcc 源代碼?

閱讀和解析 TCC&#xff08;Tiny C Compiler&#xff09; 的源代碼需要對編譯器的基本工作原理和代碼結構有一定的了解。以下是分步驟的指南&#xff0c;幫助你更高效地學習和理解 TCC 的源代碼&#xff1a; 1. 前置知識準備 C 語言基礎&#xff1a;TCC 是用 C 語言編寫的&…

Java Set系列集合詳解:HashSet、LinkedHashSet、TreeSet底層原理與使用場景

Java Set系列集合詳解&#xff1a;HashSet、LinkedHashSet、TreeSet底層原理與使用場景 一、Set系列集合概述 1. 核心特點 無序性&#xff1a;存取順序不一致&#xff08;LinkedHashSet除外&#xff09;。唯一性&#xff1a;元素不重復。無索引&#xff1a;無法通過索引直接訪…

解決 CentOS 7 鏡像源無法訪問的問題

在國內使用 CentOS 系統時&#xff0c;經常會遇到鏡像源無法訪問或者下載速度慢的問題。尤其是默認的 CentOS 鏡像源通常是國外的&#xff0c;如果你的網絡環境無法直接訪問國外服務器&#xff0c;就會出現無法下載包的情況。本文將介紹如何修改 CentOS 7 的鏡像源為國內鏡像源…