Redis實(shí)現(xiàn)分布式鎖全解析之從原理到實(shí)踐過(guò)程
在分布式系統(tǒng)開(kāi)發(fā)的廣袤領(lǐng)域中,資源競(jìng)爭(zhēng)問(wèn)題宛如隱藏在暗處的礁石,時(shí)刻威脅著系統(tǒng)的穩(wěn)定性與數(shù)據(jù)一致性。當(dāng)多個(gè)服務(wù)實(shí)例如同脫韁野馬般同時(shí)沖向同一份共享數(shù)據(jù),試圖進(jìn)行修改操作時(shí),一場(chǎng)混亂的 “數(shù)據(jù)搶奪戰(zhàn)” 便悄然上演。此時(shí),分布式鎖如同一位公正的裁判,站出來(lái)維護(hù)秩序,確保同一時(shí)刻僅有一個(gè)實(shí)例能夠?qū)Y源進(jìn)行操作,成為保障分布式系統(tǒng)正常運(yùn)轉(zhuǎn)的關(guān)鍵要素。
一、背景介紹
在分布式架構(gòu)這一復(fù)雜生態(tài)中,不同的服務(wù)器節(jié)點(diǎn)猶如散布在各地的獨(dú)立個(gè)體,它們?cè)诟髯缘膬?nèi)存空間中運(yùn)行,彼此之間難以直接感知對(duì)方的狀態(tài)。
這就導(dǎo)致傳統(tǒng)單機(jī)鎖機(jī)制,例如 Java 里的 synchronized 關(guān)鍵字,在分布式場(chǎng)景下如同折翼的飛鳥(niǎo),失去了原有的效用。而 Redis,憑借其卓越的分布式緩存能力,以高可用性、高性能以及豐富的數(shù)據(jù)結(jié)構(gòu),成為構(gòu)建分布式鎖的理想基石,為解決分布式環(huán)境下的資源競(jìng)爭(zhēng)難題帶來(lái)了曙光。
二、解決方案
(一)使用 SETNX 命令
SETNX(SET if Not eXists)堪稱(chēng) Redis 實(shí)現(xiàn)分布式鎖的核心原子性命令。當(dāng)執(zhí)行 SETNX key value 操作時(shí),Redis 會(huì)進(jìn)行一次原子化的檢查與設(shè)置。
若指定的 key 在數(shù)據(jù)庫(kù)中尚未存在,那么設(shè)置操作將成功執(zhí)行,同時(shí)返回 1,意味著鎖被成功獲??;反之,若 key 已然存在,設(shè)置操作則不會(huì)生效,返回 0,表明鎖已被其他實(shí)例持有。通過(guò)這一特性,我們得以初步構(gòu)建起分布式鎖的雛形。
以 Python 結(jié)合 Redis - py 庫(kù)為例,代碼如下:
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
lock_key = "distributed_lock"
lock_value = "unique_value"
if r.setnx(lock_key, lock_value):
try:
# 這里寫(xiě)需要加鎖執(zhí)行的業(yè)務(wù)邏輯
print("獲取到鎖,執(zhí)行任務(wù)")
finally:
r.delete(lock_key)
else:
print("未能獲取到鎖")在這段代碼中,當(dāng)程序嘗試獲取鎖時(shí),首先調(diào)用setnx方法。若返回值為 True,即獲取鎖成功,隨后在try塊中執(zhí)行需要加鎖保護(hù)的業(yè)務(wù)邏輯,執(zhí)行完畢后,無(wú)論是否發(fā)生異常,都會(huì)在finally塊中釋放鎖,以確保鎖資源不會(huì)被長(zhǎng)時(shí)間占用。
(二)設(shè)置鎖的過(guò)期時(shí)間
盡管 SETNX 命令為我們提供了基本的鎖獲取機(jī)制,但在實(shí)際應(yīng)用中,它仍存在一個(gè)潛在的風(fēng)險(xiǎn)。倘若獲取到鎖的節(jié)點(diǎn)突發(fā)故障,例如硬件崩潰、網(wǎng)絡(luò)中斷等,且未主動(dòng)釋放鎖,那么這把鎖將如同被遺忘在角落的珍寶,一直處于被占用狀態(tài),導(dǎo)致其他節(jié)點(diǎn)在無(wú)盡的等待中無(wú)法獲取鎖,嚴(yán)重影響系統(tǒng)的正常運(yùn)行。為化解這一隱患,我們需要為鎖設(shè)置合理的過(guò)期時(shí)間。
在 Redis 中,通過(guò) SET key value EX seconds 命令,我們能夠輕松為鎖設(shè)置過(guò)期時(shí)間。例如:
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
lock_key = "distributed_lock"
lock_value = "unique_value"
if r.set(lock_key, lock_value, ex=10, nx=True):
try:
# 這里寫(xiě)需要加鎖執(zhí)行的業(yè)務(wù)邏輯
print("獲取到鎖,執(zhí)行任務(wù)")
finally:
r.delete(lock_key)
else:
print("未能獲取到鎖")上述代碼中,ex=10表示為鎖設(shè)置了 10 秒的過(guò)期時(shí)間。若在 10 秒內(nèi),持有鎖的節(jié)點(diǎn)正常完成業(yè)務(wù)操作并釋放鎖,一切安好;若 10 秒內(nèi)節(jié)點(diǎn)未完成操作或發(fā)生故障,鎖將自動(dòng)過(guò)期,其他節(jié)點(diǎn)便有機(jī)會(huì)獲取鎖,從而避免了因鎖長(zhǎng)時(shí)間被占用而導(dǎo)致的系統(tǒng)僵局。
(三)解決鎖的誤刪問(wèn)題
在分布式系統(tǒng)復(fù)雜多變的運(yùn)行環(huán)境下,鎖的誤刪問(wèn)題猶如一顆隱藏的定時(shí)炸彈,隨時(shí)可能引爆數(shù)據(jù)一致性的危機(jī)。設(shè)想這樣一個(gè)場(chǎng)景:節(jié)點(diǎn) A 成功獲取到鎖,并設(shè)置了 10 秒的過(guò)期時(shí)間。然而,由于業(yè)務(wù)邏輯的復(fù)雜性或外部因素干擾,節(jié)點(diǎn) A 執(zhí)行任務(wù)的時(shí)間超過(guò)了 10 秒,此時(shí)鎖自動(dòng)過(guò)期并被釋放。緊接著,節(jié)點(diǎn) B 順利獲取到鎖并開(kāi)始執(zhí)行任務(wù)。就在這時(shí),節(jié)點(diǎn) A 完成任務(wù),準(zhǔn)備釋放鎖,由于它并不知曉鎖已經(jīng)過(guò)期并被重新分配,便貿(mào)然執(zhí)行了釋放操作,結(jié)果誤刪了節(jié)點(diǎn) B 持有的鎖,這無(wú)疑將引發(fā)一系列不可預(yù)測(cè)的后果。
為了精準(zhǔn)拆除這顆 “定時(shí)炸彈”,我們需要在設(shè)置鎖時(shí),為鎖值賦予一個(gè)獨(dú)一無(wú)二的標(biāo)識(shí)。這樣在釋放鎖之前,先仔細(xì)判斷當(dāng)前鎖值是否與自己當(dāng)初設(shè)置的一致。只有當(dāng)兩者匹配時(shí),才執(zhí)行釋放操作,從而有效避免誤刪他人鎖的情況發(fā)生。
借助 Python 與 Redis - py 庫(kù),代碼調(diào)整如下:
import redis
import uuid
r = redis.Redis(host='localhost', port=6379, db=0)
lock_key = "distributed_lock"
lock_value = str(uuid.uuid4())
if r.set(lock_key, lock_value, ex=10, nx=True):
try:
# 這里寫(xiě)需要加鎖執(zhí)行的業(yè)務(wù)邏輯
print("獲取到鎖,執(zhí)行任務(wù)")
finally:
if r.get(lock_key) == lock_value.encode('utf-8'):
r.delete(lock_key)
else:
print("未能獲取到鎖")在這段優(yōu)化后的代碼中,lock_value通過(guò)uuid.uuid4()生成一個(gè)全球唯一的標(biāo)識(shí)符。在釋放鎖時(shí),先通過(guò)r.get(lock_key)獲取當(dāng)前鎖值,并與最初設(shè)置的lock_value進(jìn)行比對(duì),只有當(dāng)兩者完全一致時(shí),才調(diào)用r.delete(lock_key)釋放鎖,極大地提升了鎖操作的安全性與準(zhǔn)確性。
(四)Redis 集群環(huán)境下的分布式鎖實(shí)現(xiàn)
在實(shí)際的生產(chǎn)環(huán)境中,為了應(yīng)對(duì)高并發(fā)、大規(guī)模的業(yè)務(wù)需求,Redis 往往以集群的形式部署。在 Redis 集群模式下實(shí)現(xiàn)分布式鎖,相較于單機(jī)環(huán)境更為復(fù)雜,需要考慮數(shù)據(jù)分片、節(jié)點(diǎn)故障轉(zhuǎn)移等諸多因素。
Redis 集群采用分片機(jī)制,將數(shù)據(jù)分散存儲(chǔ)在多個(gè)節(jié)點(diǎn)上。當(dāng)我們嘗試在集群環(huán)境中獲取分布式鎖時(shí),需要確保鎖的相關(guān)操作能夠在整個(gè)集群范圍內(nèi)保持一致性。一種常見(jiàn)的做法是使用 Redlock 算法。
Redlock 算法的核心思想是:客戶(hù)端需要向集群中的多個(gè) Redis 節(jié)點(diǎn)同時(shí)發(fā)起獲取鎖的請(qǐng)求。假設(shè)集群中有 N 個(gè)節(jié)點(diǎn),當(dāng)客戶(hù)端成功從超過(guò)半數(shù)(即大于等于 (N + 1) / 2)的節(jié)點(diǎn)獲取到鎖時(shí),才認(rèn)為鎖獲取成功。并且,每個(gè)鎖都設(shè)置了一個(gè)較短的有效期,以應(yīng)對(duì)節(jié)點(diǎn)故障或網(wǎng)絡(luò)分區(qū)等異常情況。在釋放鎖時(shí),客戶(hù)端需要向所有獲取過(guò)鎖的節(jié)點(diǎn)發(fā)送釋放請(qǐng)求,確保鎖被徹底釋放。
以 Python 實(shí)現(xiàn) Redlock 算法為例,可借助redlock - py庫(kù):
from redlock import Redlock
# 定義Redis節(jié)點(diǎn)列表
nodes = [
{
"host": "localhost",
"port": 6379,
"db": 0
},
{
"host": "localhost",
"port": 6380,
"db": 0
},
{
"host": "localhost",
"port": 6381,
"db": 0
}
]
# 創(chuàng)建Redlock實(shí)例
redlock = Redlock(nodes)
lock_key = "distributed_lock"
lock_value = str(uuid.uuid4())
lock_acquired = redlock.lock(lock_key, lock_value, 1000)
if lock_acquired:
try:
# 執(zhí)行加鎖后的業(yè)務(wù)邏輯
print("獲取到鎖,執(zhí)行任務(wù)")
finally:
redlock.unlock(lock_key, lock_value)
else:
print("未能獲取到鎖")在上述代碼中,首先定義了 Redis 集群中的節(jié)點(diǎn)列表,然后創(chuàng)建Redlock實(shí)例。通過(guò)調(diào)用lock方法嘗試獲取鎖,當(dāng)成功獲取到鎖后,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯,最后在業(yè)務(wù)完成時(shí),調(diào)用unlock方法釋放鎖。Redlock 算法通過(guò)多節(jié)點(diǎn)交互,增強(qiáng)了分布式鎖在集群環(huán)境中的可靠性與健壯性。
(五)分布式鎖的性能優(yōu)化
在高并發(fā)的分布式系統(tǒng)中,分布式鎖的性能表現(xiàn)直接影響著系統(tǒng)的整體吞吐量與響應(yīng)速度。為了提升分布式鎖的性能,我們可以從以下幾個(gè)方面著手優(yōu)化:
- 減少網(wǎng)絡(luò)開(kāi)銷(xiāo):盡量減少客戶(hù)端與 Redis 服務(wù)器之間的網(wǎng)絡(luò)請(qǐng)求次數(shù)。例如,在獲取鎖時(shí),可以一次性將鎖的相關(guān)信息(如鎖值、過(guò)期時(shí)間等)發(fā)送給 Redis,避免多次往返請(qǐng)求。
- 優(yōu)化鎖的粒度:合理劃分鎖的作用范圍,避免設(shè)置過(guò)大粒度的鎖,導(dǎo)致并發(fā)性能下降。如果業(yè)務(wù)允許,可以將大的業(yè)務(wù)操作拆分成多個(gè)小的操作,分別使用細(xì)粒度的鎖進(jìn)行保護(hù),從而提高并發(fā)執(zhí)行效率。
- 緩存鎖狀態(tài):對(duì)于一些頻繁獲取鎖的場(chǎng)景,可以在客戶(hù)端緩存鎖的狀態(tài)。在嘗試獲取鎖之前,先檢查本地緩存中的鎖狀態(tài),若鎖處于未被占用狀態(tài),再向 Redis 發(fā)送獲取鎖的請(qǐng)求,這樣可以減少對(duì) Redis 的壓力,提升系統(tǒng)性能。
總結(jié)
通過(guò) Redis 實(shí)現(xiàn)分布式鎖,為分布式系統(tǒng)中的資源競(jìng)爭(zhēng)問(wèn)題提供了行之有效的解決方案。然而,在實(shí)際應(yīng)用中,從鎖的基本獲取與釋放機(jī)制,到應(yīng)對(duì)鎖的過(guò)期、誤刪等復(fù)雜情況,再到 Redis 集群環(huán)境下的鎖實(shí)現(xiàn)以及性能優(yōu)化,每一個(gè)環(huán)節(jié)都充滿(mǎn)了挑戰(zhàn)與機(jī)遇。廣大開(kāi)發(fā)人員們,在你們使用 Redis 實(shí)現(xiàn)分布式鎖的征程中,或許也遇到過(guò)各種各樣的難題。希望大家能在評(píng)論區(qū)踴躍分享自己的經(jīng)驗(yàn)與困惑,讓我們攜手共進(jìn),不斷探索如何更高效、更可靠地運(yùn)用 Redis,為分布式系統(tǒng)打造堅(jiān)不可摧的防護(hù)壁壘,共同推動(dòng)分布式技術(shù)的蓬勃發(fā)展。
集群環(huán)境下的分布式鎖實(shí)現(xiàn)以及性能優(yōu)化等內(nèi)容,文章深度有所提升。你看看是否符合你對(duì)深度的要求?要是還有其他想法,比如再補(bǔ)充某些特定場(chǎng)景下的案例等,都可以提出來(lái)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Redis實(shí)現(xiàn)高并發(fā)計(jì)數(shù)器
這篇文章主要為大家詳細(xì)介紹了Redis實(shí)現(xiàn)高并發(fā)計(jì)數(shù)器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
Redis實(shí)現(xiàn)用戶(hù)簽到的示例代碼
Redis的位圖可以高效實(shí)現(xiàn)用戶(hù)簽到功能,每個(gè)bit位對(duì)應(yīng)一個(gè)簽到狀態(tài),節(jié)省存儲(chǔ)空間,利用SETBIT、GETBIT等命令操作簽到數(shù)據(jù),可統(tǒng)計(jì)連續(xù)簽到天數(shù)和本月簽到情況,感興趣的可以了解一下2024-09-09
Redis如何實(shí)現(xiàn)數(shù)據(jù)庫(kù)讀寫(xiě)分離詳解
Redis的主從架構(gòu),能幫助我們實(shí)現(xiàn)讀多,寫(xiě)少的情況,下面這篇文章主要給大家介紹了關(guān)于Redis如何實(shí)現(xiàn)數(shù)據(jù)庫(kù)讀寫(xiě)分離的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
Redis基礎(chǔ)學(xué)習(xí)之管道機(jī)制詳析
這篇文章主要給大家介紹了關(guān)于Redis基礎(chǔ)學(xué)習(xí)之管道機(jī)制的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
Redis bitmap 實(shí)現(xiàn)簽到案例(最新推薦)
這篇文章主要介紹了Redis bitmap 實(shí)現(xiàn)簽到案例,通過(guò)設(shè)計(jì)簽到功能對(duì)應(yīng)的數(shù)據(jù)庫(kù)表,結(jié)合sql語(yǔ)句給大家講解的非常詳細(xì),具體示例代碼跟隨小編一起看看吧2024-07-07

