Java實現(xiàn)分布式鎖的3種方法總結(jié)
分布式鎖是一種用于保證分布式系統(tǒng)中多個進程或線程同步訪問共享資源的技術(shù)。同時它又是面試中的常見問題,所以我們本文就重點來看分布式鎖的具體實現(xiàn)(含實現(xiàn)代碼)。
在分布式系統(tǒng)中,由于各個節(jié)點之間的網(wǎng)絡通信延遲、故障等原因,可能會導致數(shù)據(jù)不一致的問題。分布式鎖通過協(xié)調(diào)多個節(jié)點的行為,保證在任何時刻只有一個節(jié)點可以訪問共享資源,以避免數(shù)據(jù)的不一致性和沖突。
1.分布式鎖要求
分布式鎖通常需要滿足以下幾個要求:
- 互斥性:在任意時刻只能有一個客戶端持有鎖。
- 不會發(fā)生死鎖:即使持有鎖的客戶端發(fā)生故障,也能保證鎖最終會被釋放。
- 具有容錯性:分布式鎖需要能夠容忍節(jié)點故障等異常情況,保證系統(tǒng)的穩(wěn)定性。
2.實現(xiàn)方案
在 Java 中,實現(xiàn)分布式鎖的方案有多種,包括:
- 基于數(shù)據(jù)庫實現(xiàn)的分布式鎖:可以通過數(shù)據(jù)庫的樂觀鎖或悲觀鎖實現(xiàn)分布式鎖,但是由于數(shù)據(jù)庫的 IO 操作比較慢,不適合高并發(fā)場景。
- 基于 ZooKeeper 實現(xiàn)的分布式鎖:ZooKeeper 是一個高可用性的分布式協(xié)調(diào)服務,可以通過它來實現(xiàn)分布式鎖。但是使用 ZooKeeper 需要部署額外的服務,增加了系統(tǒng)復雜度。
- 基于 Redis 實現(xiàn)的分布式鎖:Redis 是一個高性能的內(nèi)存數(shù)據(jù)庫,支持分布式部署,可以通過Redis的原子操作實現(xiàn)分布式鎖,而且具有高性能和高可用性。
3.數(shù)據(jù)庫分布式鎖
數(shù)據(jù)庫的樂觀鎖或悲觀鎖都可以實現(xiàn)分布式鎖,下面分別來看。
3.1 悲觀鎖
在數(shù)據(jù)庫中使用 for update 關鍵字可以實現(xiàn)悲觀鎖,我們在 Mapper 中添加 for update 即可對數(shù)據(jù)加鎖,實現(xiàn)代碼如下:
<!--?UserMapper.xml?--> <select?id="selectByIdForUpdate"?resultType="User"> ????SELECT?*?FROM?user?WHERE?id?=?#{id}?FOR?UPDATE </select>
在 Service 中調(diào)用 Mapper 方法,即可獲取到加鎖的數(shù)據(jù):
@Transactional public?void?updateWithPessimisticLock(int?id,?String?name)?{ ????User?user?=?userMapper.selectByIdForUpdate(id); ????if?(user?!=?null)?{ ????????user.setName(name); ????????userMapper.update(user); ????}?else?{ ????????throw?new?RuntimeException("數(shù)據(jù)不存在"); ????} }
3.2 樂觀鎖
在 MyBatis 中,可以通過給表添加一個版本號字段來實現(xiàn)樂觀鎖。在 Mapper 中,使用標簽定義更新語句,同時使用 set 標簽設置版本號的增量。
<!--?UserMapper.xml?--> <update?id="updateWithOptimisticLock"> ????UPDATE?user?SET ????name?=?#{name}, ????version?=?version?+?1 ????WHERE?id?=?#{id}?AND?version?=?#{version} </update>
在 Service 中調(diào)用 Mapper 方法,需要傳入更新數(shù)據(jù)的版本號。如果更新失敗,說明數(shù)據(jù)已經(jīng)被其他事務修改,具體實現(xiàn)代碼如下:
@Transactional public?void?updateWithOptimisticLock(int?id,?String?name,?int?version)?{ ????User?user?=?userMapper.selectById(id); ????if?(user?!=?null)?{ ????????user.setName(name); ????????user.setVersion(version); ????????int?rows?=?userMapper.updateWithOptimisticLock(user); ????????if?(rows?==?0)?{ ????????????throw?new?RuntimeException("數(shù)據(jù)已被其他事務修改"); ????????} ????}?else?{ ????????throw?new?RuntimeException("數(shù)據(jù)不存在"); ????} }
4.Zookeeper 分布式鎖
在 Spring Boot 中,可以使用 Curator 框架來實現(xiàn) ZooKeeper 分布式鎖,具體實現(xiàn)分為以下 3 步:
- 引入 Curator 和 ZooKeeper 客戶端依賴;
- 配置 ZooKeeper 連接信息;
- 編寫分布式鎖實現(xiàn)類。
4.1 引入 Curator 和 ZooKeeper
<dependency> ????<groupId>org.apache.curator</groupId> ????<artifactId>curator-framework</artifactId> ????<version>latest</version> </dependency> <dependency> ????<groupId>org.apache.curator</groupId> ????<artifactId>curator-recipes</artifactId> ????<version>latest</version> </dependency> <dependency> ????<groupId>org.apache.zookeeper</groupId> ????<artifactId>zookeeper</artifactId> ????<version>latest</version> </dependency>
4.2 配置 ZooKeeper 連接
在 application.yml 中添加 ZooKeeper 連接配置:
spring: ??zookeeper: ????connect-string:?localhost:2181 ????namespace:?demo
4.3 編寫分布式鎖實現(xiàn)類
@Component public?class?DistributedLock?{ ????@Autowired ????private?CuratorFramework?curatorFramework; ????/** ?????*?獲取分布式鎖 ?????* ?????*?@param?lockPath???鎖路徑 ?????*?@param?waitTime???等待時間 ?????*?@param?leaseTime??鎖持有時間 ?????*?@param?timeUnit???時間單位 ?????*?@return?鎖對象 ?????*?@throws?Exception?獲取鎖異常 ?????*/ ????public?InterProcessMutex?acquire(String?lockPath,?long?waitTime,?long?leaseTime,?TimeUnit?timeUnit)?throws?Exception?{ ????????InterProcessMutex?lock?=?new?InterProcessMutex(curatorFramework,?lockPath); ????????if?(!lock.acquire(waitTime,?timeUnit))?{ ????????????throw?new?RuntimeException("獲取分布式鎖失敗"); ????????} ????????if?(leaseTime?>?0)?{ ????????????lock.acquire(leaseTime,?timeUnit); ????????} ????????return?lock; ????} ????/** ?????*?釋放分布式鎖 ?????* ?????*?@param?lock?鎖對象 ?????*?@throws?Exception?釋放鎖異常 ?????*/ ????public?void?release(InterProcessMutex?lock)?throws?Exception?{ ????????if?(lock?!=?null)?{ ????????????lock.release(); ????????} ????} }
5.Redis 分布式鎖
我們可以使用 Redis 客戶端 Redisson 實現(xiàn)分布式鎖,它的實現(xiàn)步驟如下:
- 添加 Redisson 依賴
- 配置 Redisson 連接信息
- 編寫分布式鎖代碼類
5.1 添加 Redisson 依賴
在 pom.xml 中添加如下配置:
<!--?https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter?--> <dependency> ????<groupId>org.redisson</groupId> ????<artifactId>redisson-spring-boot-starter</artifactId> ????<version>3.20.0</version> </dependency>
5.2 配置 Redisson 連接
在 Spring Boot 項目的配置文件 application.yml 中添加 Redisson 配置:
spring: ??data: ????redis: ??????host:?localhost ??????port:?6379 ??????database:?0 redisson: ??codec:?org.redisson.codec.JsonJacksonCodec ??single-server-config: ????address:?"redis://${spring.data.redis.host}:${spring.redis.port}" ????database:?"${spring.data.redis.database}" ????password:?"${spring.data.redis.password}"
5.3 編寫分布式鎖代碼類
import?jakarta.annotation.Resource; import?org.redisson.Redisson; import?org.redisson.api.RLock; import?org.springframework.stereotype.Service; import?java.util.concurrent.TimeUnit; @Service public?class?RedissonLockService?{ ????@Resource ????private?Redisson?redisson; ????/** ?????*?加鎖 ?????* ?????*?@param?key?????分布式鎖的?key ?????*?@param?timeout?超時時間 ?????*?@param?unit????時間單位 ?????*?@return ?????*/ ????public?boolean?tryLock(String?key,?long?timeout,?TimeUnit?unit)?{ ????????RLock?lock?=?redisson.getLock(key); ????????try?{ ????????????return?lock.tryLock(timeout,?unit); ????????}?catch?(InterruptedException?e)?{ ????????????Thread.currentThread().interrupt(); ????????????return?false; ????????} ????} ????/** ?????*?釋放分布式鎖 ?????* ?????*?@param?key?分布式鎖的?key ?????*/ ????public?void?unlock(String?key)?{ ????????RLock?lock?=?redisson.getLock(key); ????????lock.unlock(); ????} }
6.Redis VS Zookeeper
Redis 和 ZooKeeper 都可以用來實現(xiàn)分布式鎖,它們在實現(xiàn)分布式鎖的機制和原理上有所不同,具體區(qū)別如下:
- 數(shù)據(jù)存儲方式:Redis 將鎖信息存儲在內(nèi)存中,而 ZooKeeper 將鎖信息存儲在 ZooKeeper 的節(jié)點上,因此 ZooKeeper 需要更多的磁盤空間。
- 鎖的釋放:Redis 的鎖是通過設置鎖的過期時間來自動釋放的,而 ZooKeeper 的鎖需要手動釋放,如果鎖的持有者出現(xiàn)宕機或網(wǎng)絡中斷等情況,需要等待鎖的超時時間才能自動釋放。
- 鎖的競爭機制:Redis 使用的是單機鎖,即所有請求都直接連接到同一臺 Redis 服務器,容易發(fā)生單點故障;而 ZooKeeper 使用的是分布式鎖,即所有請求都連接到 ZooKeeper 集群,具有較好的可用性和可擴展性。
- 一致性:Redis 的鎖是非嚴格意義下的分布式鎖,因為在多臺機器上運行多個進程時,由于 Redis 的主從同步可能會存在數(shù)據(jù)不一致的問題;而 ZooKeeper 是強一致性的分布式系統(tǒng),保證了數(shù)據(jù)的一致性。
- 性能:Redis 的性能比 ZooKeeper 更高,因為 Redis 將鎖信息存儲在內(nèi)存中,而 ZooKeeper 需要進行磁盤讀寫操作。
總之,Redis 適合實現(xiàn)簡單的分布式鎖場景,而 ZooKeeper 適合實現(xiàn)復雜的分布式協(xié)調(diào)場景,也就是 ZooKeeper 適合強一致性的分布式系統(tǒng)。
強一致性是指系統(tǒng)中的所有節(jié)點在任何時刻看到的數(shù)據(jù)都是一致的。ZooKeeper 中的數(shù)據(jù)是有序的樹形結(jié)構(gòu),每個節(jié)點都有唯一的路徑標識符,所有節(jié)點都共享同一份數(shù)據(jù),當任何一個節(jié)點對數(shù)據(jù)進行修改時,所有節(jié)點都會收到通知,更新數(shù)據(jù),并確保數(shù)據(jù)的一致性。在 ZooKeeper 中,強一致性體現(xiàn)在數(shù)據(jù)的讀寫操作上。ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)協(xié)議來保證數(shù)據(jù)的一致性,該協(xié)議確保了數(shù)據(jù)更新的順序,所有的數(shù)據(jù)更新都需要經(jīng)過集群中的大多數(shù)節(jié)點確認,保證了數(shù)據(jù)的一致性和可靠性。
小結(jié)
在 Java 中,使用數(shù)據(jù)庫、ZooKeeper 和 Redis 都可以實現(xiàn)分布式鎖。但數(shù)據(jù)庫 IO 操作比較慢,不適合高并發(fā)場景;Redis 執(zhí)行效率最高,但在主從切換時,可能會出現(xiàn)鎖丟失的情況;ZooKeeper 是一個高可用性的分布式協(xié)調(diào)服務,可以保證數(shù)據(jù)的強一致性,但是使用 ZooKeeper 需要部署額外的服務,增加了系統(tǒng)復雜度。所以沒有最好的解決方案,只有最合適自己的解決方案。
以上就是Java實現(xiàn)分布式鎖的3種方法總結(jié)的詳細內(nèi)容,更多關于Java分布式鎖的資料請關注腳本之家其它相關文章!
相關文章
Spring+Http請求+HttpClient實現(xiàn)傳參
這篇文章主要介紹了Spring+Http請求+HttpClient實現(xiàn)傳參,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03mybatis中的mapper.xml使用循環(huán)語句
這篇文章主要介紹了mybatis中的mapper.xml使用循環(huán)語句,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02Java實現(xiàn)動態(tài)規(guī)劃背包問題
本文主要介紹使用java實現(xiàn)動態(tài)規(guī)劃的背包問題,詳細使用圖文和多種案例進行解析,幫助理解該算法2021-06-06SpringBoot利用隨機鹽值實現(xiàn)密碼的加密與驗證
這篇文章主要為大家詳細介紹了SpringBoot如何利用隨機鹽值實現(xiàn)密碼的加密與驗證,文中的示例代碼講解詳細,有需要的小伙伴可以參考下2024-02-02利用Jmeter發(fā)送Java請求的實戰(zhàn)記錄
JMeter是Apache組織的開放源代碼項目,它是功能和性能測試的工具,100%的用java實現(xiàn),下面這篇文章主要給大家介紹了關于如何利用Jmeter發(fā)送Java請求的相關資料,需要的朋友可以參考下2021-09-09mybatis源碼解讀-Java中executor包的語句處理功能
這篇文章主要介紹了Java中executor包的語句處理功能,在mybatis映射文件中傳參數(shù),主要用到#{}或者${},下文圍繞相關資料展開詳細內(nèi)容,需要的小伙伴可以參考一下2022-02-02利用java讀取web項目中json文件為map集合方法示例
這篇文章主要給大家介紹了關于利用java讀取web項目中json文件為map集合的相關資料,文中通過示例代碼給大家介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-08-08