網(wǎng)站首頁 編程語言 正文
1. 什么是分布式鎖
當我們在編寫多線程代碼的時候,不同的線程可能會發(fā)生資源的爭奪,為了避免資源爭奪造成的錯誤,我們會對資源上鎖,只有獲得鎖的線程才能繼續(xù)往下執(zhí)行。
進程中的鎖,本質(zhì)就是內(nèi)存中一個變量,當一個線程執(zhí)行某個操作申請加鎖時,如果能成功把代表鎖的變量值設置為1,則表示獲得了鎖,其他線程想要獲得鎖時會阻塞,而擁有鎖的線程執(zhí)行完操作后,再把鎖的值設置為0,則表示釋放了鎖。
上面我們說的是在一臺服務器的進程內(nèi)不同線程之間的鎖,這個鎖是放在內(nèi)存中的,而對于分布式應用程序來說,不同的應用(進程或線程)部署在不同的服務器上,這樣就不能通過內(nèi)存中的變量來表示鎖。
即然在一臺服務器上可以通過內(nèi)存這塊共享的空間來表示鎖,那么對于分布式應用程序來說,可以共享存儲系統(tǒng)來存儲一個共享鎖,這就是分布式鎖,而Redis
作為內(nèi)存數(shù)據(jù)庫,執(zhí)行非常快,很適合作為實現(xiàn)分布式鎖的共享存儲系統(tǒng)。
2. 使用Redis實現(xiàn)分布式鎖
對于一個鎖來說,其實只有兩個操作,加鎖和釋放鎖,下面我們看來看通過Redis
要怎么實現(xiàn)?
2.1 加鎖
Redis
的setnx
命令會判斷鍵值是否存在,如果存在則不做任何操作,并返回0,如果不存在,則創(chuàng)建并賦值,并返回1,因此我們可以執(zhí)行setnx
為一個代表鎖鍵設置值,如果能設置成功,則表示獲得鎖,失敗則無法獲得鎖。
# 使用key為lock來表示一個鎖 setnx lock 1
2.2 釋放鎖
當執(zhí)行好操作之后,要釋放鎖的時候直接把Redis
里的鍵值lock
刪除就可以了,這樣其他進程才能通過setnx
命令重新設置并獲得該鎖。
# 釋放鎖 del lock
通過上面兩個命令,我們實現(xiàn)了一個簡單的分布式鎖,但這里就出現(xiàn)了一個問題:如果一個進程通過setnx
命令加鎖之后,在執(zhí)行具體操作出錯了,沒有辦法及時釋放鎖,那么其他進程就無法獲得該鎖,系統(tǒng)便無法繼續(xù)往下執(zhí)行,解決這個問題的辦法就是為鎖設置一個有效期,在這個有效期之后,自動釋放鎖。
2.3 給鎖設置有效期
給鎖設置有效期非常簡單,直接使用Redis
的expire
命令就可以了,如:
# 加鎖 setnx lock 1 # 給鎖設置10s有效期 expire lock 10
但是,現(xiàn)在又出現(xiàn)另一個問題了,如果我們在設置了鎖之后,執(zhí)行expire
命令之前該進程掛掉了,那么expire
就沒有執(zhí)行成功,鎖一樣是沒有被釋放掉的,所以一定要保證上面兩個命令要一起執(zhí)行,怎么保證呢?
有兩個方法,一個是使用LUA
語言編寫的腳本,另一個是使用Redis
的set
命令,set
命令后面跟nx
參數(shù)后,執(zhí)行的效果與setnx
一致,且set
命令可以跟ex
參數(shù)來設置過期時間,所以我們可以使用set
命令把setnx
和expire
兩個合并在一起,這樣就可以保證執(zhí)行的原子性了。
# 判斷是否鍵值是否存在,ex后面跟著的是鍵值的有效期,10s set lock 1 nx ex 10
解決了鎖的有效問題,現(xiàn)在我們再來看另外一個問題。
如上圖所示,現(xiàn)在有A
,B
,C
三個不同服務器上的進程在執(zhí)行某個操作都需要獲得鎖,執(zhí)行后要釋放鎖。
現(xiàn)在的情況是進程A
執(zhí)行第2步時卡頓了(上面綠色區(qū)域所示),且時間超出了鎖有效期,所以進程A
設置的鎖自動釋放了,這時候進程B
獲得了鎖,并開始執(zhí)行操作,但由于進程A
只是卡頓了而已,所以會繼續(xù)執(zhí)行的時候,在第3步的時候會手動釋放鎖,但是這個時候,鎖由線程B
所擁有,也就是說進程A刪除的不是自己的鎖,而進程B的鎖,這時候進程B
還沒執(zhí)行完,但鎖被釋放后,進程C
可以加鎖,也就是說由于進程A卡頓釋放錯了鎖,導致進程B和進程C可以同時獲得鎖。
怎么避免這種情況呢?如何區(qū)分其他進程的鎖,避免刪除其他進程的鎖呢?答案就是每個進程在加鎖的時候,給鎖設置一個唯一值,并在釋放鎖的時候,判斷是不是自己設置的鎖。
2.4 給鎖設置唯一值
給鎖設置唯一值的時候,一樣是使用set
命令,唯一的不同是將鍵值1改為一個隨機生成的唯一值,比如uuid。
# rand_uid表示唯一id set lock rand_id nx ex 10
當鎖里的值由進程設置后,釋放鎖的時候,就需要判斷鎖是不是自己的,步驟如下:
- 通過
Redis
的get
命令獲得鎖的值 - 根據(jù)獲得的值,判斷鎖是不是自己設置的
- 如果是,通過
del
命令釋放鎖。
此時我們看到,釋放鎖需要執(zhí)行三個操作,如果三個操作依次執(zhí)行的話,是沒有辦法保證原子性的,比如進程A
在執(zhí)行到第2步后,準備開始執(zhí)行del
命令時,而鎖由時有效期到了,被自動釋放了,并被其他服務器上的進程B
獲得鎖,但這時候線程A
執(zhí)行del
還是把線程B
的鎖給刪掉了。
解決這個問題的辦法就是保證上述三個操作執(zhí)行的原子性,即在執(zhí)行釋放鎖的三個操作中,其他進程不可以獲得鎖,想要做到這一點,需要使用到LUA腳本。
2.5 通過LUA腳本實現(xiàn)釋放鎖的原子性
Redis
支持LUA
腳本,LUA
腳里的代碼執(zhí)行的時候,其他客戶端的請求不會被執(zhí)行,這樣可以保證原子性操作,所以我們可以使用下面腳本進行鎖的釋放:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
將上述腳本保存為腳本后,可以調(diào)用Redis
客戶端命令redis-cli
來執(zhí)行,如下:
# lock為key,rand_id表示key里保存的值 redis-cli --eval unlock.lua lock , rand_id
3. 小結
無論是本地鎖還是分布式鎖,鎖的本質(zhì)就是一個共享的變量,只是在實現(xiàn)分布式鎖時候,把這個變量移到了Redis
服務器所在的內(nèi)存中。
在上面實現(xiàn)分布式鎖的過程中我們碰到了以下幾個問題:
- 如何保證加鎖操作的原子性?
- 如何保證進程崩潰自動釋放鎖?
- 如何避免刪錯其他進程的鎖?
- 如何保證釋放鎖操作的原子性?
在解決上述問題的時候,我們也一步步完善一個可以在實際開發(fā)中應用的Redis
分布式鎖。
原文鏈接:https://juejin.cn/post/7139879580109635597
相關推薦
- 2023-01-12 C語言中數(shù)組排序淺析_C 語言
- 2023-03-29 golang?channel讀取數(shù)據(jù)的幾種情況_Golang
- 2022-11-05 Nginx反向代理location和proxy_pass配置規(guī)則詳細總結_nginx
- 2022-05-12 Android 截屏實現(xiàn)、屏幕截圖、MediaProjection、ImageReader
- 2022-08-13 Linux內(nèi)核中container_of宏定義講解
- 2022-12-23 解析ROC曲線繪制(python+sklearn+多分類)_python
- 2022-09-10 Python并發(fā)編程多進程,多線程及GIL全局解釋器鎖_python
- 2022-11-11 Android利用Canvas類繪制圖形_Android
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支