使用redis實現分散式鎖實踐
分散式鎖在多實例部署,分散式系統中經常會使用到,這是因為基於jvm的鎖無法滿足多實例中鎖的需求,本篇將講下redis如何通過Lua腳本實現分散式鎖,不同於網上的redission,完全是手動實現的
我們先來看一個無鎖的情況下會導致什麼問題:
這是一個普通的更新用戶年齡的功能,各層代碼如下,訪問controller層,一個更新,一個查詢
這是service層,我們使用contdownlatch發令槍來模擬線程同時並發的情況,發令槍設為32,即32個線程同時去請求修改年齡,
這裡使用線程池來提交多線程任務,看代碼知道,這裡我們已經有了判斷年齡的操作,當查詢用戶查詢大於0時,才去調更新用戶年齡-1的方法,等下看看有沒有用
這裡是sql,可以看到兩個sql,一個查詢用戶年齡,一個會執行用戶年齡每次減1 ,
這裡是用戶數據,我們可以看到,用戶UID為UR12324的用戶,他的年齡是30,接著我們來調32個線程來操作減他年齡
我們請求下這個方法
然後看看結果:
可以看到庫中年齡已被減為-2,在未加鎖的情況下,查詢較驗並沒有什麼作用,此時如果加個synchronized或lock鎖肯定能避免這種情況,但我們本文討論的是多實例或分散式環境中,此加鎖方式仍然會產生問題,感興趣的可以試下是不是
下面我們開始實現一個redis分散式鎖,來避免這種情況發生,先說說實現思路:
1,線程請求訪問前先調用加鎖的方法,加鎖就去里生成一個隨機數同時保存在線程本地變數和redis的某key中,此key設有效期為200ms,具體值根據業務執行時間自行調整,加鎖成功;
2.其它線程試著訪問拿出它本地變數與redis中某key進行比較,如果不一致,則說明有鎖,此線程休眠一段時間,再試著加鎖;
3.加鎖成功的線程在操作結束後刪掉它持有鎖(用lua實現,保證原子性,在它比對和刪除鎖的過程中,其它線程不會加鎖成功),讓其它線程再次加鎖以執行任務;
說明:鎖的時間為200ms可預防線程掛掉之後死鎖,200ms後會自動釋放
下面看看我們寫的鎖代碼:
片段1:使用redislock 實現lock來複寫它的方法
片段2:試著加鎖的方法
片段3:解鎖方法,此處首先從線程本地變數獲取它的隨機數,然後調用lua腳本,與redis中key相比較,如果相同則刪除,否則返回0;
此為lua腳本方法,用此方法可以保證判斷和刪除的原子性,在此過程中沒有線程可以操作此key
到此為止,我們鎖基本寫完,來測試下有沒有用:
我們在此方法前後分別加入加鎖和解鎖方法,使用方式和lock鎖一樣, 我們重新把年齡恢復到30後來測試一下吧
先看看日誌
這裡可以看到各個線程爭奪鎖的情況,再看看執行結果
這裡我們可以看到雖然是32個線程並發執行,但此值並不會變為負數,加鎖成功.
我們可以看到最後2個線程並沒有執行方法
此時說明加鎖成功,大家可以在分散式環境中測試更明顯,有關極端情況下解鎖失敗後應該做什麼也可以由我們自己決定,比redission要靈活,帶鎖的redis最好是單實例,在集群中可能會出問題,有機會我們再用zk實現下.


TAG:java高級開發 |