基于Redis方案的分布式鎖的Java實現

前期, 我們介紹了什么是分布式鎖及分布式鎖應用場景, 今天我們基于Redis方案來實現分布式鎖的應用。

1. 基于Redis分布式鎖方案介紹

基于Redis實現的分布式鎖是分布式系統中控制資源訪問的常用方案,利用Redis的原子操作和高性能特性實現跨進程的鎖機制。我們需根據業務特性合理設計,避免引入新的問題(如死鎖、鎖失效)。

基于Redis方案的分布式具有以下特點:

  • 可重入性:同一線程可多次獲取同一把鎖,通過 ThreadLocal 計數。
  • 鎖續期(看門狗機制):自動延長鎖的過期時間,防止業務未執行完鎖就過期。
  • 公平 / 非公平鎖:支持通過構造函數指定是否公平鎖。
  • 異常處理:包含完整的異常處理和資源釋放邏輯。
  • 原子操作:能夠使用 Lua 腳本保證解鎖的原子性。

分布式鎖

1.1 核心原理

  1. 原子性加鎖
    通過Redis的原子命令SET key value NX PX timeout實現:

    • NX(Not eXists):僅當key不存在時設置值,保證互斥性。
    • PX timeout:設置鎖的過期時間(毫秒),避免死鎖(如持有鎖的進程崩潰時自動釋放)。
  2. 唯一標識
    鎖的value使用唯一ID(如UUID),確保鎖只能被持有者釋放,防止誤刪。

  3. 原子性解鎖
    使用Lua腳本保證解鎖的原子性:

    if redis.call("GET", KEYS[1]) == ARGV[1] thenreturn redis.call("DEL", KEYS[1])
    elsereturn 0
    end
    

    先驗證鎖的持有者,再刪除鎖,避免誤釋放其他進程持有的鎖。

1.2 優缺點

  • 優點

    • 高性能:Redis基于內存操作,加鎖和解鎖延遲極低。
    • 高可用:通過Redis集群(如Sentinel、Cluster)避免單點故障。
    • 靈活配置:支持設置鎖的過期時間、重試策略等參數。
  • 缺點

    • 主從復制延遲:主從架構中,主節點寫入鎖后未同步到從節點就崩潰,可能導致鎖丟失(Redlock算法可部分解決)。
    • 過期時間難控制:若業務執行時間超過鎖的過期時間,可能導致多個進程同時持有鎖。

1.3 典型實現方式

  1. 簡單實現(原生Redis命令)
    直接使用Redis客戶端(如Jedis、Lettuce)調用SET和Lua腳本,適合輕量級場景。

  2. Redisson框架
    提供分布式鎖的高級抽象(如可重入鎖、公平鎖、讀寫鎖),內置看門狗機制自動續期鎖:

    // Redisson可重入鎖示例
    RLock lock = redissonClient.getLock("myLock");
    lock.lock(); // 自動續期,默認30秒
    try {// 業務邏輯
    } finally {lock.unlock();
    }
    
  3. Redlock算法
    針對主從復制缺陷,在多個獨立的Redis節點上獲取鎖,多數節點成功時才認為加鎖成功,提升可靠性,但犧牲部分性能。

1.4 關鍵參數配置

  • 鎖過期時間:需根據業務執行時間合理設置,避免過短導致鎖提前釋放,或過長導致資源長時間被占用。
  • 重試策略:獲取鎖失敗時的重試次數和間隔,避免頻繁重試耗盡資源。
  • 續期機制:通過看門狗自動延長鎖的過期時間,確保業務執行期間鎖不會過期。

1.5 適用場景

  • 高并發場景:如秒殺、庫存扣減,利用Redis高性能快速響應。
  • 異步任務:如定時任務去重執行,通過鎖避免多個節點重復處理。
  • 緩存重建:防止緩存失效時多個請求同時重建緩存,造成緩存擊穿。

1.6 注意事項

  1. 避免鎖粒度過大:只在關鍵操作上加鎖,減少鎖持有時間。
  2. 異常處理:使用try-finally確保鎖最終被釋放。
  3. 監控與告警:監控鎖的持有時間、競爭情況,及時發現異常。

2. 實現代碼

以下是基于Redis方案的分布式鎖的重要實現代碼片段(僅供參考)。

使用時需要在Maven文件中添加 Jedis 組件依賴:

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.3</version>
</dependency>

RedisDistributedLock.java

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** Redis分布式鎖實現,基于Redis的SETNX和Lua腳本機制* 支持可重入、鎖續期(看門狗機制)和公平鎖特性*/
public class RedisDistributedLock implements Lock {private static final String LOCK_SUCCESS = "OK";private static final Long RELEASE_SUCCESS = 1L;private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";// 鎖續期的間隔時間(毫秒),默認是鎖超時時間的1/3private static final long RENEWAL_INTERVAL_RATIO = 3;private final JedisPool jedisPool;private final String lockKey;private final String clientId; // 客戶端唯一標識private final long expireTime; // 鎖超時時間(毫秒)private final boolean isFair; // 是否公平鎖// 可重入計數private final ThreadLocal<AtomicInteger> reentrantCount = ThreadLocal.withInitial(() -> new AtomicInteger(0));// 看門狗線程private ThreadLocal<WatchDog> watchDog = new ThreadLocal<>();public RedisDistributedLock(JedisPool jedisPool, String lockKey, long expireTime, boolean isFair) {this.jedisPool = jedisPool;this.lockKey = lockKey;this.clientId = generateClientId();this.expireTime = expireTime;this.isFair = isFair;}@Overridepublic void lock() {if (!tryLock()) {// 等待并重試waitAndRetry();}}@Overridepublic void lockInterruptibly() throws InterruptedException {if (Thread.interrupted()) {throw new InterruptedException();}if (!tryLock()) {waitAndHandleInterrupt();}}@Overridepublic boolean tryLock() {// 檢查是否已持有鎖(可重入)if (isHeldByCurrentThread()) {reentrantCount.get().incrementAndGet();return true;}try (Jedis jedis = jedisPool.getResource()) {// SET key value NX PX expireTimeString result = jedis.set(lockKey, clientId, new SetParams().nx().px(expireTime));if (LOCK_SUCCESS.equals(result)) {reentrantCount.get().set(1);startWatchDog(); // 啟動看門狗線程return true;}return false;}}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {long startTime = System.currentTimeMillis();long timeoutMillis = unit.toMillis(time);if (tryLock()) {return true;}while (System.currentTimeMillis() - startTime < timeoutMillis) {if (Thread.interrupted()) {throw new InterruptedException();}if (tryLock()) {return true;}Thread.sleep(100); // 避免CPU空轉}return false;}@Overridepublic void unlock() {if (!isHeldByCurrentThread()) {throw new IllegalMonitorStateException("Attempt to unlock lock, not locked by current thread");}// 可重入鎖計數減1int count = reentrantCount.get().decrementAndGet();if (count > 0) {return; // 仍持有鎖,不釋放}try {// 釋放鎖前停止看門狗stopWatchDog();// 使用Lua腳本保證原子性釋放鎖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(clientId));if (!RELEASE_SUCCESS.equals(result)) {throw new IllegalMonitorStateException("Failed to release lock");}}} finally {// 清理ThreadLocalreentrantCount.remove();watchDog.remove();}}@Overridepublic Condition newCondition() {throw new UnsupportedOperationException("Conditions are not supported by this lock");}// 生成客戶端唯一標識private String generateClientId() {return Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-" + (int)(Math.random() * 10000);}// 判斷當前線程是否持有鎖private boolean isHeldByCurrentThread() {AtomicInteger count = reentrantCount.get();return count.get() > 0;}// 等待并重試獲取鎖(非公平)private void waitAndRetry() {while (true) {if (tryLock()) {return;}try {Thread.sleep(100); // 避免CPU空轉} catch (InterruptedException e) {Thread.currentThread().interrupt();return;}}}// 等待并處理中斷private void waitAndHandleInterrupt() throws InterruptedException {while (true) {if (Thread.interrupted()) {throw new InterruptedException();}if (tryLock()) {return;}Thread.sleep(100); // 避免CPU空轉}}// 啟動看門狗線程,自動續期鎖private void startWatchDog() {WatchDog dog = new WatchDog(expireTime / RENEWAL_INTERVAL_RATIO);watchDog.set(dog);dog.start();}// 停止看門狗線程private void stopWatchDog() {WatchDog dog = watchDog.get();if (dog != null) {dog.interrupt();}}/*** 看門狗線程,負責自動續期鎖*/private class WatchDog extends Thread {private final long renewalInterval;private boolean running = true;public WatchDog(long renewalInterval) {this.renewalInterval = renewalInterval;setDaemon(true);setName("LockWatchDog-" + lockKey);}@Overridepublic void run() {while (running && !isInterrupted()) {try {Thread.sleep(renewalInterval);// 續期鎖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";jedis.eval(script, Collections.singletonList(lockKey), List.of(clientId, String.valueOf(expireTime)));}} catch (InterruptedException e) {running = false;Thread.currentThread().interrupt();} catch (Exception e) {// 記錄異常但繼續運行e.printStackTrace();}}}}
}    

RedisLockExample.java

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/*** Redis分布式鎖使用示例*/
public class RedisLockExample {private static final String REDIS_HOST = "localhost";private static final int REDIS_PORT = 6379;private static final String LOCK_KEY = "distributed:lock:example";private static final long LOCK_EXPIRE_TIME = 30000; // 30秒public static void main(String[] args) {// 創建Redis連接池JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(100);poolConfig.setMaxIdle(20);poolConfig.setMinIdle(5);poolConfig.setTestOnBorrow(true);JedisPool jedisPool = new JedisPool(poolConfig, REDIS_HOST, REDIS_PORT);// 創建分布式鎖實例RedisDistributedLock lock = new RedisDistributedLock(jedisPool, LOCK_KEY, LOCK_EXPIRE_TIME, false);// 模擬多線程競爭ExecutorService executor = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {executor.submit(() -> {try {// 獲取鎖(帶超時)if (lock.tryLock(5, TimeUnit.SECONDS)) {try {System.out.println(Thread.currentThread().getName() + " 獲取到鎖");// 模擬業務操作Thread.sleep(2000);} finally {lock.unlock();System.out.println(Thread.currentThread().getName() + " 釋放鎖");}} else {System.out.println(Thread.currentThread().getName() + " 獲取鎖超時");}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}executor.shutdown();try {executor.awaitTermination(1, TimeUnit.MINUTES);} catch (InterruptedException e) {e.printStackTrace();}// 關閉Redis連接池jedisPool.close();}
}    

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

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

相關文章

Kafka源碼P2-生產者緩沖區

歡迎來到啾啾的博客&#x1f431;。 記錄學習點滴。分享工作思考和實用技巧&#xff0c;偶爾也分享一些雜談&#x1f4ac;。 有很多很多不足的地方&#xff0c;歡迎評論交流&#xff0c;感謝您的閱讀和評論&#x1f604;。 目錄 1 引言2 緩沖區2.1 消息在Partition內有序2.2 批…

力扣網C語言編程題:三數之和

一. 簡介 本文記錄力扣網上的邏輯編程題&#xff0c;涉及數組方面的&#xff0c;這里記錄一下 C語言實現和Python實現。 二. 力扣網C語言編程題&#xff1a;三數之和 題目&#xff1a;三數之和 給你一個整數數組 nums &#xff0c;判斷是否存在三元組 [nums[i], nums[j], nu…

2.2 Windows MSYS2編譯FFmpeg 4.4.1

一、安裝編譯工具 # 更換pacman源 sed -i "s#mirror.msys2.org/#mirrors.ustc.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist* pacman -Sy# 安裝依賴 pacman -S --needed base-devel mingw-w64-x86_64-toolchain pacman -S mingw-w64-x86_64-nasm mingw-w64-x86_64-ya…

驅動開發,隊列,環形緩沖區:以GD32 CAN 消息處理為例

對環形緩沖區進行進一步的優化和功能擴展&#xff0c;以應對更復雜的實際應用場景&#xff0c;特別是針對 CAN 總線消息處理的場景。 一、優化點 1&#xff1a;動態配置環形緩沖區大小在原始實現中&#xff0c;我們固定了緩沖區大小為 RINGBUFF_LEN 64。這種方式雖然簡單&am…

SQL基礎知識,MySQL學習(長期更新)

1、基本操作&#xff0c;增刪查改 INSERT INTO 表名 (字段1, 字段2, ...) VALUES (值1, 值2, ...); DELETE FROM 表名 WHERE 條件 SELECT * FROM 表名 WHERE 條件 UPDATE 表名 SET 字段1 值, 字段2 值, ... WHERE 條件; SELECT * INTO 新表 FROM 舊表 WHERE… INSERT INTO 語…

Git(一):初識Git

文章目錄 Git(一)&#xff1a;初識GitGit簡介核心功能分布式特性結構與操作優勢與適用場景 創建本地倉庫git init配置name與email--global 工作區、暫存區與版本庫git addgit commitcommit后.git的變化 Git(一)&#xff1a;初識Git Git簡介 Git 是一個分布式版本控制系統&…

第19天:初級數據庫學習筆記3

分組函數&#xff08;多行處理函數&#xff09; 即多個輸入對應一個輸出。前面講的數據處理函數是單行處理函數。&#xff08;在公司中常說單&#xff0c;多行處理函數&#xff09; 分組函數包括五個&#xff1a; max&#xff1a;最大值min&#xff1a;最小值avg&#xff1a…

Windows11下搭建Raspberry Pi Pico編譯環境

1. 系統與工具要求 PC平臺&#xff1a; Windows 11 專業版 Windows GCC: gcc-15.1.0-64.exe GNU Make: 4.3 Git: 2.49.0 cmake: 4.0.2 python:3.12.11 Arm GNU Toolchain Downloads – Arm Developer 2. 工具安裝與驗證 2.1 工具安裝 winget安裝依賴工具&#xff08;Windows …

【C語言極簡自學筆記】重講運算符

一、算術操作符 算術操作符描述把兩個操作數相加-第一個操作數減去第二個操作數*把兩個操作數相乘/分子除以分母%取模運算符&#xff0c;整除后的余數 注意&#xff1a;1.除號的兩端都是整數的時候執行的是整數的除法&#xff0c;兩端只要有一個浮點數&#xff0c;就執行浮點…

持續集成 CI/CD-Jenkins持續集成GitLab項目打包docker鏡像推送k8s集群并部署至rancher

Jenkins持續集成GitLab項目 GitLab提交分支后觸發Jenkis任務 之前是通過jar包在shell服務器上進行手動部署&#xff0c;麻煩且耗時。現通過Jenkins進行持續集成實現CI/CD。以test分支為例 提交即部署。 由于是根據自己實際使用過程 具體使用到了 gitlabjenkinsdockerharborra…

Apache Iceberg與Hive集成:非分區表篇

引言 在大數據處理領域&#xff0c;Apache Iceberg憑借其先進的表格式設計&#xff0c;為大規模數據分析帶來了新的可能。當Iceberg與Hive集成時&#xff0c;這種強強聯合為數據管理與分析流程提供了更高的靈活性和效率。本文將聚焦于Iceberg與Hive集成中的非分區表場景&#…

webpack 如何區分開發環境和生產環境

第一種方法: 方法出處&#xff1a;命令行接口&#xff08;CLI&#xff09; | webpack 中文文檔 1.利用webpack.config.js 返回的是個函數&#xff0c;利用函數的參數&#xff0c;來區分環境 具體步驟 1&#xff09; package.json文件&#xff1a;在npm scripts 命令后面追加 …

React組件通信——context(提供者/消費者)

Context 是 React 提供的一種組件間通信方式&#xff0c;主要用于解決跨層級組件 props 傳遞的問題。它允許數據在組件樹中"跨級"傳遞&#xff0c;無需顯式地通過每一層 props 向下傳遞。 一、Context 核心概念 1. 基本組成 React.createContext&#xff1a;創建 C…

“微信短劇小程序開發指南:從架構設計到上線“

1. 引言&#xff1a;短劇市場的機遇與挑戰 近年來&#xff0c;短視頻和微短劇市場呈現爆發式增長&#xff0c;用戶碎片化娛樂需求激增。短劇小程序憑借輕量化、社交傳播快、變現能力強等特點&#xff0c;成為內容創業的新風口。然而&#xff0c;開發一個穩定、流暢且具備商業價…

RPC與RESTful對比:兩種API設計風格的核心差異與實踐選擇

# RPC與RESTful對比&#xff1a;兩種API設計風格的核心差異與實踐選擇 ## 一、架構哲學與設計目標差異 1. **RPC&#xff08;Remote Procedure Call&#xff09;** - **核心思想**&#xff1a;將遠程服務調用偽裝成本地方法調用&#xff08;方法導向&#xff09; - 典型行為…

【pytest進階】pytest之鉤子函數

什么是 hook (鉤子)函數 經常會聽到鉤子函數(hook function)這個概念,最近在看目標檢測開源框架mmdetection,里面也出現大量Hook的編程方式,那到底什么是hook?hook的作用是什么? what is hook ?鉤子hook,顧名思義,可以理解是一個掛鉤,作用是有需要的時候掛一個東西…

深度學習計算——動手學深度學習5

環境&#xff1a;PyCharm python3.8 1. 層和塊 塊&#xff08;block&#xff09;可以描述 單個層、由多個層組成的組件或整個模型本身。 使用塊進行抽象的好處&#xff1a; 可將塊組合成更大的組件(這一過程通常是遞歸) 如 圖5.1.1所示。通過定義代碼來按需生成任意復雜度…

NodeJS的fs模塊的readFile和createReadStream區別以及常見方法

Node.js 本身沒有像 Java 那樣嚴格區分字符流和字節流&#xff0c;區別主要靠編碼&#xff08;encoding&#xff09;來控制數據是以 Buffer&#xff08;二進制字節&#xff09;形式還是字符串&#xff08;字符&#xff09;形式處理。 詳細解釋&#xff1a; 方面JavaNode.js字節…

基于二進制XOR運算的機器人運動軌跡與對稱圖像自動生成算法

原創&#xff1a;項道德&#xff08;daode3056,daode1212&#xff09; 新的算法出現&#xff0c;往往能給某些行業與產業帶來革命與突破。為探索機器人運動軌跡與對稱圖像自動生成算法&#xff0c;本人已經通過18種算法的測試&#xff0c;最終&#xff0c;以二進制的XOR運算為…

Spring AI 項目實戰(七):Spring Boot + Spring AI Tools + DeepSeek 智能工具平臺(附完整源碼)

系列文章 序號文章名稱1Spring AI 項目實戰(一):Spring AI 核心模塊入門2Spring AI 項目實戰(二):Spring Boot + AI + DeepSeek 深度實戰(附完整源碼)3Spring AI 項目實戰(三):Spring Boot + AI + DeepSeek 打造智能客服系統(附完整源碼)4Spring AI 項目實戰(四…