redis分布式鎖實現方案和原理解析

2021-10-21 23:50:09 字數 4166 閱讀 4594

分布式鎖的在分布式集群環境中有著至關重要的作用,為了保證高併發場景下資料的一致性,確保業務能夠安全的執行,為了防止分布式系統中的多個程序之間相互干擾,我們需要一種分布式協調技術來對這些程序進行排程。而這個分布式協調技術的核心就是來實現這個分布式鎖。

首先我們看一段很普通的**:

@autowired

stringredistemplate stringredistemplate;

protected

final

static string product =

"123456789";(

"/order"

)public string submitorder()

else

return

"end"

;}

上面的**想必一看就知道了,就是個簡單的扣減庫存的操作,從快取中拿到庫存量,然後每次請求去獲取到庫存進行對應的扣減,但是當我們寫完**之後應該從幾個方面做乙個思考:

1.**執行的角度

實際情況下,如果多個請求同時過來,都從快取中讀取資料,能不能正確的扣減庫存呢?顯然是不能保證,肯定會有執行緒不安全問題,這裡我們採用jemeter進行乙個簡單的併發測試。

這裡簡單做個測試,在0秒內同時發起5個請求,觀察庫存扣減是否正確。

結果清晰可見,明顯出現資料扣減出錯,我們該如何去解決呢?開始分布式鎖的引入,首先介紹乙個redis的命令

setnx:if not exists key,如果redis中不存在該key的時候才能後設定成功,這樣我們就可以利用這個指令去實現鎖,第乙個執行緒到了之後,該命令正常執行,redis中多了一對鍵值對,其他執行緒來的時候則無法正確設定,也就是無法拿到鎖。

我們對上面**做乙個改進,新增鎖機制。

@autowired

stringredistemplate stringredistemplate;

protected

final

static string product =

"123456789";(

"/order"

)public string submitorder()

tryelse

}finally

return

"end"

;}

但是以上的寫法就很完美了嗎?顯然不是,從另一外乙個角度進行考慮

2.開發整合的角度

實際開發過程中我們會有很多地方可能用到分布式鎖的地方,不能每次都去寫同一段相同的**,我們應該提高**的重用性,增加可讀性,不至於那麼臃腫,那該怎麼操作呢?我們應該考慮這幾點

抽取成api的話,會出現乙個問題,就是可能其他的開發者不清楚api的作用,導致呼叫發生問題,你仔細想一想,如果他在其他地方其他執行緒先呼叫了釋放鎖的操作,那麼在此時還未等真正釋放鎖,就乘虛而入,一夜回到解放前了,所以我們需要把獲取鎖和釋放鎖和當前執行緒做乙個繫結,設計乙個標識,保證只有拿到鎖的那個執行緒才能去釋放鎖。

這個時候就可以利用上我們的threadlocal類了,看我操作。

@autowired

private stringredistemplate stringredistemplate;

private threadlocal

threadlocal =

newthreadlocal

<

>()

;@override

public

boolean

trylock

(string key,

long timeout, timeunit unit)

throws interruptedexception

else

return lock;

}@override

public

void

releaselock

(string key)

}

以上就是實踐,但是真的就考慮周全了嗎,顯然乙個好的程式是不可能就這樣完事了,我們還需要考慮乙個問題,如果在業務**中就比如

servicea->serviceb有這樣的呼叫,恰恰serviceb中也需要使用到分布式鎖,那麼久會有重入鎖的問題,關於重入鎖,如果大家看過並發包的一些實現,就會有很大的啟發,其實也比較簡單,這裡就不去實現了,我們可以定義個變數state,每次加鎖進行加一操作,解鎖進行加一操作,就可以滿足要求了。

最後乙個思考,從業務真正上線的角度還有什麼可以優化的?

3.業務的角度

我們實現的是非阻塞的鎖,而且沒有拿到鎖直接就返回了error,這顯然就是很不友好的,非常影響業務價值,如果乙個客戶沒有拿到鎖你返回個error,客戶會以為是不是出問題了還是沒有庫存了,造成使用者流失這不是我們想看到的。

實際情況沒有拿到鎖應該是阻塞狀態,沒有拿到鎖的使用者讓他進行乙個自旋等待,一直去獲取鎖,但是進行乙個自旋去獲取鎖看似可以解決問題,但是會導致cpu崩潰,redis效能也會有很大的問題,怎麼有個很好的解決呢?

這時我們應該想到並發包中的訊號量semaphore,如果你不了解這個類的話?

我簡單口述一下,他的作用其實就是控制線程的訪問數量,比如:

semaphore semaphore = new semaphore (3);

只能同時三個執行緒進行操作資源,需要等待其中任何執行緒執行完畢,其他執行緒才有機會。

看我具體實現。

@autowired

private stringredistemplate stringredistemplate;

private threadlocal

threadlocal =

newthreadlocal

<

>()

;private semaphore semaphore =

newsemaphore(0

);@override

public

boolean

trylock

(string key,

long timeout, timeunit unit)

throws interruptedexception

trycatch

(interruptedexception e)}}

else

return lock;

}@override

public

void

releaselock

(string key)

}}

上面是業務需要考慮的乙個方面。

你想過嗎?如果業務的執行時間已經超過了key的有效時間,這個時候其他執行緒進來了還是會導致執行緒的問題,怎麼解決?

我們可以採用開啟乙個定時的非同步執行緒去監視鎖的過期時間還剩多少,每10秒進行去檢視一下,然後延長過期時間,這樣就算業務執行多久,永遠都是占有鎖的狀態,不會過期,這也叫非同步續命。

以上大概就差不多了,其實這都是個人實現,現在已經有很好的開源框架有了很好的實現,比我的好一萬倍,效能也有很大提公升,但是我想講的是主要思路都是差不多的,相信你看完這篇再去看原始碼會簡單很多,這是依賴。

org.redisson<

/groupid>

redisson<

/artifactid>

2.2.13

<

/version>

<

/dependency>

但是官網分布式鎖的有個問題需要說一下,就是在redis主從架構中,大家知道redis是保證可用的,如果主節點宕機了,這時候鎖沒有及時同步到從節點,而新選舉出來的主節點,又可以重新獲取鎖,這又導致了多個執行緒同時拿到了鎖,出現執行緒安全問題,官網也給出了解決方案,總結一下就是過半演算法,但是建議沒啥大事別用,因為每次都要去請求多個節點,會有很大效能問題,演算法鏈結官網位址。

分布式鎖 使用Redis實現分布式鎖

關於分布式鎖的實現,我的前一篇文章講解了如何使用zookeeper實現分布式鎖。關於分布式鎖的背景此處不再做贅述,我們直接討論下如何使用redis實現分布式鎖。關於redis,筆主不打算做長篇大論的介紹,只介紹下redis優秀的特性。支援豐富的資料型別,如string list map set zs...

分布式鎖實現方案

在這種情況下,如果我們實現鎖可以使用synchronized或reentrantlock,但是在分布式情況下,它們最多只能鎖住當前jvm的執行緒,對於其它server的執行緒無能為力。那麼怎麼處理呢?一般是通過為資料庫表新增乙個 version 欄位來實現讀取出資料時,將此版本號一同讀出,之後更新時...

redis實現分布式鎖

隨便 系統越來越大,各功能模組除了垂直切割以外,同時也得做集群處理,那麼問題來了,在多執行緒情況下對於資源的競爭就需要乙個統一的訪問限制。以選課系統為例子,集群中各節點對課程可選數量同時操作,這裡就需要同步了,否則會導致最後選到的數量比可選的數量大,這裡我們的分布式鎖就派上用場了。利用redis來實...