當前位置:首頁 » 編程語言 » java分布式鎖

java分布式鎖

發布時間: 2022-12-21 09:08:02

㈠ Redis的Setnx命令實現分布式鎖

首先,分布式鎖和我們平常講到的鎖原理基本一樣,目的就是確保在多個線程並發時,只有一個線程在同一刻操作這個業務或者說方法、變數。

在一個進程中,也就是一個jvm或者說應用中,我們很容易去處理控制,在 java.util 並發包中已經為我們提供了這些方法去加鎖,比如 synchronized 關鍵字或者 Lock 鎖,都可以處理。

但是如果在分布式環境下,要保證多個線程同時只有1個能訪問某個資源,就需要用到分布式鎖。這里我們將介紹用Redis的 setnx 命令來實現分布式鎖。

其實目前通常所說的 setnx 命令,並非單指redis的 setnx key value 這條命令,這條命令可能會在後期redis版本中刪除。

一般代指redis中對 set 命令加上 nx 參數進行使用, set 這個命令,目前已經支持這么多參數可選:

從 Redis 2.6.12 版本開始, SET 命令的行為可以通過一系列參數來修改:

注入bean

這里同時啟動5個線程並發往redis中存儲 lock 這個key(key可以自定義,但需要一致),同時設置10秒的過期時間。
setIfAbsent 這個函數實現的功能與 setnx 命令一樣,代表如果沒有這個key則set成功獲取到鎖,否則set失敗沒有獲取到鎖。
獲得鎖後進行資源的操作,最後釋放鎖。

執行效果

可以看到同時只有1個線程能夠獲取到鎖。

使用 setnx 命令方式雖然操作比較簡單方便,但是會有如下問題:

可以在再次獲取鎖時,如果鎖被佔用就get值,判斷值是否是當前線程存的隨機值,如果是則再次執行 set 命令重新上鎖;當然為了保證原子性這些操作都要用 lua 腳本來執行。

可以使用 while 循環重復執行 setnx 命令,並設置一個超時時間退出循環。

可以盡量把鎖自動過期的時間設的冗餘一些。但也不能徹底解決。

可以在刪除鎖的時候先get值,判斷值是否是當前線程存的隨機值,只有相同才執行刪鎖的操作;當然也要使用 lua 腳本執行來保證原子性。

分布式鎖需要滿足的特性

綜上:使用 setnx 命令來實現分布式鎖並不是一個很嚴謹的方案,如果是Java技術棧,我們可以使用 Redisson 庫來解決以上問題,接下來的文章會介紹如何使用。

Redisson實現分布式鎖
Redlock實現分布式鎖

㈡ Zookeeper + Curator實現分布式鎖

在分布式系統下,使用Java中的synchronized或者Lock已經不能滿足需求了。關於分布式鎖的實現,我們可以利用Mysql的唯一索引去實現,也可以利用Redis的SETNX,同樣也可以使用Zookeeper的節點唯一路徑去實現。

(1)線程先去 /locks 路徑下面創建一個帶序號的臨時節點。

(2)判斷自己創建的這個節點是不是 /locks 路徑下序號最小的節點,如果是,則獲取鎖;如果不是,則監聽自己的前一個節點。

(3)獲取到鎖後,處理自己的業務邏輯,然後刪除自己創建的節點。監聽它的後一個節點收到通知後,執行步驟(2)

上面的過程是不是跟AQS的同步隊列有點像,判斷自己是不是隊列的頭結點,如果是就去獲取鎖,不是就等待。

按照上面的思路,我們可以很快的使用zookeeper相關的api實現分布式鎖。

通過在zookeeper中創建帶序號的臨時節點,然後判斷當前線程創建的臨時節點序號是不是最小的,如果是則獲得鎖,否則監聽前一節點。

為什麼要創建臨時節點,就是怕創建完後,zookeeper伺服器又掛了,這時候如果是永久節點,那麼就會死鎖了。而臨時節點在關閉伺服器後就會被刪除。

這里使用 CountDownLatch 在監聽節點的時候進行 await 。節點發生變化時,會調用 process 方法,在 process 方法中進行 countDown 。

進行測試

創建兩個線程進行測試,看控制列印輸出

官方文檔

使用原生API存在的問題

基於以上,一般實際開發都是用Curator去實現,畢竟別人的輪子又大又安全,何必自己搞個破破爛爛的輪子上路呢。

Curator主要實現了下面四種鎖

首先需要在項目中添加依賴

然後實現即可

查看控制台輸出

㈢ Springboot使用redis的setnx和getset實現並發鎖、分布式鎖

在日常開發中,很多業務場景必須保證原子性。舉幾個例子:

如果你只有一台伺服器,只運行一個Java程序,那麼可以使用Java語言自身的一些鎖來實現原子性。但如果我們有多台伺服器,甚至不同伺服器上跑的是不同的語言。那這時候,我們就需要一個跨平台、跨語言的加鎖方式。redis就是其中最方便的一種。

使用redis實現並發鎖,主要是靠兩個redis的命令:setnx和getset。

那我們的設計思路就是:

上面的代碼使用了一個RedisService的類,裡面主要是簡單封裝了一下redis的操作,你可以替換為自己的service。代碼如下:

以上代碼有任何疑問,可以點擊右側邊欄聯系作者。收費5毛~交個朋友,歡迎來撩!

版權聲明:《Springboot使用redis的setnx和getset實現並發鎖、分布式鎖》為CoderBBB作者「ʘᴗʘ」的原創文章,轉載請附上原文出處鏈接及本聲明。

原文鏈接:https://www.coderbbb.com/articles/2

㈣ 高並發沒鎖可不行,三種分布式鎖詳解

Java中的鎖主要包括synchronized鎖和JUC包中的鎖,這些鎖都是針對單個JVM實例上的鎖,對於分布式環境如果我們需要加鎖就顯得無能為力。在單個JVM實例上,鎖的競爭者通常是一些不同的線程,而在分布式環境中,鎖的競爭者通常是一些不同的線程或者進程。如何實現在分布式環境中對一個對象進行加鎖呢?答案就是分布式鎖。

目前分布式鎖的實現方案主要包括三種:

基於資料庫實現分布式鎖主要是利用資料庫的唯一索引來實現,唯一索引天然具有排他性,這剛好符合我們對鎖的要求:同一時刻只能允許一個競爭者獲取鎖。加鎖時我們在資料庫中插入一條鎖記錄,利用業務id進行防重。當第一個競爭者加鎖成功後,第二個競爭者再來加鎖就會拋出唯一索引沖突,如果拋出這個異常,我們就判定當前競爭者加鎖失敗。防重業務id需要我們自己來定義,例如我們的鎖對象是一個方法,則我們的業務防重id就是這個方法的名字,如果鎖定的對象是一個類,則業務防重id就是這個類名。

基於緩存實現分布式鎖:理論上來說使用緩存來實現分布式鎖的效率最高,加鎖速度最快,因為Redis幾乎都是純內存操作,而基於資料庫的方案和基於Zookeeper的方案都會涉及到磁碟文件IO,效率相對低下。一般使用Redis來實現分布式鎖都是利用Redis的 SETNX key value 這個命令,只有當key不存在時才會執行成功,如果key已經存在則命令執行失敗。

基於Zookeeper:Zookeeper一般用作配置中心,其實現分布式鎖的原理和Redis類似,我們在Zookeeper中創建瞬時節點,利用節點不能重復創建的特性來保證排他性。

在實現分布式鎖的時候我們需要考慮一些問題,例如:分布式鎖是否可重入,分布式鎖的釋放時機,分布式鎖服務端是否有單點問題等。

上面已經分析了基於資料庫實現分布式鎖的基本原理:通過唯一索引保持排他性,加鎖時插入一條記錄,解鎖是刪除這條記錄。下面我們就簡要實現一下基於資料庫的分布式鎖。

id欄位是資料庫的自增id,unique_mutex欄位就是我們的防重id,也就是加鎖的對象,此對象唯一。在這張表上我們加了一個唯一索引,保證unique_mutex唯一性。holder_id代表競爭到鎖的持有者id。

如果當前sql執行成功代表加鎖成功,如果拋出唯一索引異常(DuplicatedKeyException)則代表加鎖失敗,當前鎖已經被其他競爭者獲取。

解鎖很簡單,直接刪除此條記錄即可。

是否可重入 :就以上的方案來說,我們實現的分布式鎖是不可重入的,即是是同一個競爭者,在獲取鎖後未釋放鎖之前再來加鎖,一樣會加鎖失敗,因此是不可重入的。解決不可重入問題也很簡單:加鎖時判斷記錄中是否存在unique_mutex的記錄,如果存在且holder_id和當前競爭者id相同,則加鎖成功。這樣就可以解決不可重入問題。

鎖釋放時機 :設想如果一個競爭者獲取鎖時候,進程掛了,此時distributed_lock表中的這條記錄就會一直存在,其他競爭者無法加鎖。為了解決這個問題,每次加鎖之前我們先判斷已經存在的記錄的創建時間和當前系統時間之間的差是否已經超過超時時間,如果已經超過則先刪除這條記錄,再插入新的記錄。另外在解鎖時,必須是鎖的持有者來解鎖,其他競爭者無法解鎖。這點可以通過holder_id欄位來判定。

資料庫單點問題 :單個資料庫容易產生單點問題:如果資料庫掛了,我們的鎖服務就掛了。對於這個問題,可以考慮實現資料庫的高可用方案,例如MySQL的MHA高可用解決方案。

使用Jedis來和Redis通信。

可以看到,我們加鎖就一行代碼:
jedis.set(String key, String value, String nxxx, String expx, int time);
這個set()方法一共五個形參:
第一個為key,我們使用key來當鎖,因為key是唯一的。
第二個為value,這里寫的是鎖競爭者的id,在解鎖時,我們需要判斷當前解鎖的競爭者id是否為鎖持有者。
第三個為nxxx,這個參數我們填的是NX,意思是SET IF NOT EXIST,即當key不存在時,我們進行set操作;若key已經存在,則不做任何操作。
第四個為expx,這個參數我們傳的是PX,意思是我們要給這個key加一個過期時間的設置,具體時間由第五個參數決定;
第五個參數為time,與第四個參數相呼應,代表key的過期時間。
總的來說,執行上面的set()方法就只會導致兩種結果:1.當前沒有鎖(key不存在),那麼久進行加鎖操作,並對鎖設置一個有效期,同時value表示加鎖的客戶端。2.已經有鎖存在,不做任何操作。
上述解鎖請求中, SET_IF_NOT_EXIST (不存在則執行)保證了加鎖請求的排他性,緩存超時機制保證了即使一個競爭者加鎖之後掛了,也不會產生死鎖問題:超時之後其他競爭者依然可以獲取鎖。通過設置value為競爭者的id,保證了只有鎖的持有者才能來解鎖,否則任何競爭者都能解鎖,那豈不是亂套了。

解鎖的步驟:

注意到這里解鎖其實是分為2個步驟,涉及到解鎖操作的一個原子性操作問題。這也是為什麼我們解鎖的時候用Lua腳本來實現,因為Lua腳本可以保證操作的原子性。那麼這里為什麼需要保證這兩個步驟的操作是原子操作呢?
設想:假設當前鎖的持有者是競爭者1,競爭者1來解鎖,成功執行第1步,判斷自己就是鎖持有者,這是還未執行第2步。這是鎖過期了,然後競爭者2對這個key進行了加鎖。加鎖完成後,競爭者1又來執行第2步,此時錯誤產生了:競爭者1解鎖了不屬於自己持有的鎖。可能會有人問為什麼競爭者1執行完第1步之後突然停止了呢?這個問題其實很好回答,例如競爭者1所在的JVM發生了GC停頓,導致競爭者1的線程停頓。這樣的情況發生的概率很低,但是請記住即使只有萬分之一的概率,在線上環境中完全可能發生。因此必須保證這兩個步驟的操作是原子操作。

是否可重入 :以上實現的鎖是不可重入的,如果需要實現可重入,在 SET_IF_NOT_EXIST 之後,再判斷key對應的value是否為當前競爭者id,如果是返回加鎖成功,否則失敗。

鎖釋放時機 :加鎖時我們設置了key的超時,當超時後,如果還未解鎖,則自動刪除key達到解鎖的目的。如果一個競爭者獲取鎖之後掛了,我們的鎖服務最多也就在超時時間的這段時間之內不可用。

Redis單點問題 :如果需要保證鎖服務的高可用,可以對Redis做高可用方案:Redis集群+主從切換。目前都有比較成熟的解決方案。

利用Zookeeper創建臨時有序節點來實現分布式鎖:

其基本思想類似於AQS中的等待隊列,將請求排隊處理。其流程圖如下:


解決不可重入 :客戶端加鎖時將主機和線程信息寫入鎖中,下一次再來加鎖時直接和序列最小的節點對比,如果相同,則加鎖成功,鎖重入。

鎖釋放時機 :由於我們創建的節點是順序臨時節點,當客戶端獲取鎖成功之後突然session會話斷開,ZK會自動刪除這個臨時節點。

單點問題 :ZK是集群部署的,主要一半以上的機器存活,就可以保證服務可用性。

Zookeeper第三方客戶端curator中已經實現了基於Zookeeper的分布式鎖。利用curator加鎖和解鎖的代碼如下:

㈤ 分布式鎖有哪些

單體架構的應用可以直接使用synchronized或者ReentrantLock就可以解決多線程資源競爭的問題。如果公司業務發展較快,可以通過部署多個服務節點來提高系統的並行處理能力。由於本地鎖的作用范圍只限於當前應用的線程。高並發場景下,集群中某個應用的本地鎖並不會對其它應用的資源訪問產生互斥,就會產生數據不一致的問題,所以分布鎖就派上了用場。

利用select … where … for update 排他鎖

所謂樂觀鎖與前邊最大區別在於基於CAS思想,是不具有互斥性,不會產生鎖等待而消耗資源,操作過程中認為不存在並發沖突,只有update version失敗後才能覺察到。我們的搶購、秒殺就是用了這種實現以防止超賣。通過增加遞增的版本號欄位實現樂觀鎖

思路: 另啟一個服務,利用jdk並發工具來控制唯一資源,如在服務中維護一個concurrentHashMap,其他服務對某個key請求鎖時,通過該服務暴露的埠,以網路通信的方式發送消息,服務端解析這個消息,將concurrentHashMap中的key對應值設為true,分布式鎖請求成功,可以採用基於netty通信調用,當然你想用java的bio、nio或者整合bbo、spring cloud feign來實現通信也沒問題

在高並發場景下,應用程序在執行過程中往往會受到網路、CPU、內存等因素的影響,所以實現一個線程安全的分布式組件,往往需要考慮很多case,這個分布式鎖有 3 個重要的考量點:

下面是redis分布式鎖的各種實現方式和缺點,按照時間的發展排序:

直接利用setnx,執行完業務邏輯後調用del釋放鎖,簡單粗暴!

為了改正第一個方法的缺陷,我們用setnx獲取鎖,然後用expire對其設置一個過期時間,如果服務掛了,過期時間一到自動釋放

redis官方為了解決第二種方式存在的缺點,在2.8版本為set指令添加了擴展參數nx和ex,保證了setnx+expire的原子性,使用方法:set key value ex 5 nx

上面所說的第一個缺點,沒有特別好的解決方法,只能把過期時間盡量設置的長一點,並且最好不要執行耗時任務 第二個缺點,可以理解為當前線程有可能會釋放其他線程的鎖,那麼問題就轉換為保證線程只能釋放當前線程持有的鎖,即setnx的時候將value設為任務的唯一id,釋放的時候先get key比較一下value是否與當前的id相同,是則釋放,否則拋異常回滾,其實也是變相地解決了第一個問題

我們可以用lua來寫一個getkey並比較的腳本,jedis/luttce/redisson對lua腳本都有很好的支持

為了解決上面提到的redis集群中的分布式鎖問題,redis的作者antirez的提出了red lock的概念,假設集群中所有的n個master節點完全獨立,並且沒有主從同步,此時對所有的節點都去setnx,並且設置一個請求過期時間re和鎖的過期時間le,同時re必須小於le(可以理解,不然請求3秒才拿到鎖,而鎖的過期時間只有1秒也太蠢了),此時如果有n / 2 + 1個節點成功拿到鎖,此次分布式鎖就算申請成功

ZooKeeper是一個為分布式應用提供一致性服務的開源組件,它內部是一個分層的文件系統目錄樹結構,規定同一個目錄下只能有一個唯一文件名。基於ZooKeeper實現分布式鎖的步驟如下:

(1)redis set px nx + 唯一id + lua腳本

綜上所得:

沒有絕對完美的實現方式,具體要選擇哪一種分布式鎖,需要結合每一種鎖的優缺點和業務特點而定。

㈥ 分布式鎖--Redis秒殺(互斥鎖)(一)

中秋佳節,進行月餅秒殺,特價,限量1000份,不限每人秒的份數,不要超賣即可。

RedisLock.java

ResultEnum.java

KeyUtil.java

2)無解鎖

3)解鎖

㈦ 多伺服器java毫秒內的重復請求怎麼處理

你好,很高興回答你的問題。
這種問題,有相對成熟的機制來解決。這種機制叫分布式鎖。
其實和單機部署時的同步鎖類似,單機部署是一個線程獲取到鎖之後,另一個線程因為獲取不到鎖就不能和上一個線程同時執行。
分布式鎖道理類似,這個鎖一般會由一個獨立於部署的多個服務實例之外的系統來解決。比如redis,redis有個方法是setNx(key)這個方法是原子性的,如果redis中不存在key對應的數據,則會存入,相當於獲取到鎖,如果redis中已經存在key對應的數據,說明鎖已經被佔用,就會返回false。
放服務實例處理完這個業務功能後可以刪除掉redis中的數據,相當於適當鎖。
為了防止因意外情況導致不會執行釋放鎖的操作,可以給存入redis的數據設置一個過期時間,如果時間到了,數據還沒有被刪除,redis會自行刪除這條數據。
如果有幫助到你,請點擊採納。

㈧ Redis實現分布式鎖與Zookeeper實現分布式鎖區別

前言:

在學習過程中,簡單的整理了一些redis跟zookeeper實現分布式鎖的區別,有需要改正跟補充的地方,希望各位大佬及時指出

Redis實現分布式鎖思路

基於Redis實現分布式鎖(setnx)setnx也可以存入key,如果存入key成功返回1,如果存入的key已經存在了,返回0.

Zookeeper實現分布式鎖思路

基於Zookeeper實現分布式鎖 Zookeeper是一個分布式協調工具,在分布式解決方案中。

多個客戶端(jvm),同時在zookeeper上創建相同的一個臨時節點,因為臨時節點路徑是保證唯一,只要誰能夠創建節點成功,誰就能夠獲取到鎖,沒有創建成功節點,就會進行等待,當釋放鎖的時候,採用事件通知給客戶端重新獲取鎖的資源。

Redis實現分布式鎖與Zookeeper實現分布式鎖區別

相同點

實現分布式鎖最終是通過什麼方式?

在集群環境下,保證只允許有一個jvm進行執行。

不同點

從技術上分析

Redis 是nosql數據,主要特點緩存;

Zookeeper是分布式協調工具,主要用於分布式解決方案。

實現思路

核心通過獲取鎖、釋放鎖、死鎖問題

獲取鎖

Zookeeper

多個客戶端(jvm),會在Zookeeper上創建同一個臨時節點,因為Zookeeper節點命名路徑保證唯一,不允許出現重復,只要誰能夠先創建成功,誰能夠獲取到鎖。

Redis

多個客戶端(jvm),會在Redis使用setnx命令創建相同的一個key,因為Redis的key保證唯一,不允許出現重復,只要誰能夠先創建成功,誰能夠獲取到鎖。

釋放鎖

Zookeeper使用直接關閉臨時節點session會話連接,因為臨時節點生命周期與session會話綁定在一塊,如果session會話連接關閉的話,該臨時節點也會被刪除。

這時候客戶端使用事件監聽,如果該臨時節點被刪除的話,重新進入盜獲取鎖的步驟。

Redis在釋放鎖的時候,為了確保是鎖的一致性問題,在刪除的redis 的key時候,需要判斷同一個鎖的id,才可以刪除。

共同特徵:如何解決死鎖現象問題

Zookeeper使用會話有效期方式解決死鎖現象。

Redis 是對key設置有效期解決死鎖現象

性能角度考慮

因為Redis是NoSQL資料庫,相對比來說Redis比Zookeeper性能要好。

可靠性

從可靠性角度分析,Zookeeper可靠性比Redis更好。

因為Redis有效期不是很好控制,可能會產生有效期延遲;

Zookeeper就不一樣,因為Zookeeper臨時節點先天性可控的有效期,所以相對來說Zookeeper比Redis更好

總結下兩者區別

Redis分布式鎖,必須使用者自己間隔時間輪詢去嘗試加鎖,當鎖被釋放後,存在多線程去爭搶鎖,並且可能每次間隔時間去嘗試鎖的時候,都不成功,對性能浪費很大。

Zookeeper分布鎖,首先創建加鎖標志文件,如果需要等待其他鎖,則添加監聽後等待通知或者超時,當有鎖釋放,無須爭搶,按照節點順序,依次通知使用者。

我在學習過程中整理了一些學習資料,可以分享給做java的工程師朋友們,相互交流學習,需要的可以私信 (資料) 即可免費獲取Java架構學習資料(裡面有高可用、高並發、高性能及分布式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)

其中覆蓋了互聯網的方方面面,期間碰到各種產品各種場景下的各種問題,很值得大家借鑒和學習,擴展自己的技術廣度和知識面。 最後記得幫作者點個關注

㈨ 使用Redisson實現分布式鎖

Redisson的分布式可重入鎖RLock Java對象實現了java.util.concurrent.locks.Lock介面,同時還支持自動過期解鎖。

Redisson同時還為分布式鎖提供了非同步執行的相關方法:

Redisson分布式可重入公平鎖也是實現了java.util.concurrent.locks.Lock介面的一種RLock對象。在提供了自動過期解鎖功能的同時,保證了當多個Redisson客戶端線程同時請求加鎖時,優先分配給先發出請求的線程。

Redisson同時還為分布式可重入公平鎖提供了非同步執行的相關方法:

Redisson的RedissonMultiLock對象可以將多個RLock對象關聯為一個聯鎖,每個RLock對象實例可以來自於不同的Redisson實例。

Redisson的RedissonRedLock對象實現了 Redlock 介紹的加鎖演算法。該對象也可以用來將多個RLock
對象關聯為一個紅鎖,每個RLock對象實例可以來自於不同的Redisson實例。

Redisson的分布式可重入讀寫鎖RReadWriteLock Java對象實現了java.util.concurrent.locks.ReadWriteLock介面。同時還支持自動過期解鎖。該對象允許同時有多個讀取鎖,但是最多隻能有一個寫入鎖。

Redisson的分布式信號量(Semaphore)Java對象RSemaphore採用了與java.util.concurrent.Semaphore相似的介面和用法。

Redisson的可過期性信號量(PermitExpirableSemaphore)實在RSemaphore對象的基礎上,為每個信號增加了一個過期時間。每個信號可以通過獨立的ID來辨識,釋放時只能通過提交這個ID才能釋放。

Redisson的分布式閉鎖(CountDownLatch)Java對象RCountDownLatch採用了與java.util.concurrent.CountDownLatch相似的介面和用法。

Redisson 分布式鎖和同步器

熱點內容
python全局變數文件 發布:2025-05-15 07:35:06 瀏覽:953
位元組和存儲位元組 發布:2025-05-15 07:32:10 瀏覽:520
linux應用開發工程師 發布:2025-05-15 07:32:07 瀏覽:260
sqldcl 發布:2025-05-15 07:29:18 瀏覽:199
canvas的圖像上傳 發布:2025-05-15 07:29:17 瀏覽:102
離線緩存為什麼點不動 發布:2025-05-15 07:27:17 瀏覽:829
釘鼎伺服器出口ip 發布:2025-05-15 07:13:08 瀏覽:279
移動硬碟和光碟哪個存儲時間長 發布:2025-05-15 07:04:25 瀏覽:489
壓縮一定 發布:2025-05-15 06:57:30 瀏覽:289
進棧演算法 發布:2025-05-15 06:56:02 瀏覽:215