Redis分布式鎖的實作
- 分布式鎖初級
- 存在問題
- 解決方案
- Redission解決方案優化
- 單機秒殺系統Bug
- 單機版加synchronized
- 單機版架構升級為分布式微服務setnx
- 部署微服務jar包宕機問題
- 判斷加鎖與解鎖不是一個客戶端?
- 集群環境下鎖丟失問題
- Redis分布式鎖如何實作續期?
- 補充
分布式鎖初級

存在問題
模擬場景:執行緒A進入到鎖的業務區域,設定鎖的存活時間為10s,但是執行緒A執行完業務的總時間需要15s,當10s過后執行緒A只執行一半鎖就消失了,此時執行緒B就可以來獲取鎖了,當執行緒B加鎖后,假設執行緒B的執行時間需要8s,但此時執行緒A還在接著運行,問題就在這里,當A執行完15s后,釋放的鎖是執行緒B加的鎖,而執行緒B此時才運行了5s,還沒有運行完畢,此時就會有執行緒C等等等執行緒來加鎖,那么重復剛才執行緒AB的運行程序,最后造成鎖的永久失效,
問題的根本
當前執行緒加的鎖被前一個執行緒釋放掉了
解決方案
設定一個UUID,當執行緒執行這個方法時,將這個UUID記錄到setnx的value中,執行完業務解鎖時,判斷當前執行緒的UUID是否等于setnx中的value

Redission解決方案優化
Redission分布式鎖原理

當執行緒1加鎖成功后會開啟一個子執行緒,子執行緒通常會定時(大約為鎖存活時間的1/3)的查看鎖是否過期,如果鎖沒有過期,那么以當前時間點開始,重新設定鎖的存活時間為30,防止還沒有執行完業務鎖提前消失,
- RLock redissonLock = redission.getLock();
- redissionLock.lock(30,TmieUnit.SECONDS);加鎖并設定鎖的存活時間
- redissionLock.unLock();解鎖
代碼:

這種分布式鎖解決高并發問題只是將并行的執行緒改為串行,會降低程式的效率,增加程式的執行時間,
**解決思路:**比如想要秒殺一件商品,秒殺商品的庫存為1000,如果我們直接操縱這個庫存的話每次只能串行的-1,因此我們可以采用分段機制,將這個商品的庫存分為10份,每份為100件,這樣就可以同時有10個執行緒對庫存進行操作,提升了程式的效率,
單機秒殺系統Bug
單機版加synchronized

缺點:造成執行緒的積壓,大量的執行緒堆在sync鎖的外部,程式會變得非常的慢,最重要的是在分布式架構中仍然不能解決超賣問題,
單機版架構升級為分布式微服務setnx
分布式架構部署后,單機鎖仍然會出現超賣的情況
解決方法: redis分布式鎖setnx

缺點:如果在加鎖后的業務中報例外了,也就執行不到解鎖的命令,需要在代碼層面加一個finally釋放鎖,
部署微服務jar包宕機問題
部署微服務jar包的機器宕機了,代碼層面根本沒機會走到finally,沒辦法洗掉這個key解鎖,此時需要對這個key設定一個過期時間,
缺點:執行緒A進入到鎖的業務區域,設定鎖的存活時間為10s,但是執行緒A執行完業務的總時間需要15s,當10s過后執行緒A只執行一半鎖就消失了,此時執行緒B就可以來獲取鎖了,當執行緒B加鎖后,假設執行緒B的執行時間需要8s,但此時執行緒A還在接著運行,問題就在這里,當A執行完15s后,釋放的鎖是執行緒B加的鎖,而執行緒B此時才運行了5s,還沒有運行完畢,此時就會有執行緒C等等等執行緒來加鎖,那么重復剛才執行緒AB的運行程序,最后造成鎖的永久失效,

解決方式:在解鎖處判斷當前的UUID是否為鎖對應的value

判斷加鎖與解鎖不是一個客戶端?
在Fianlly代碼塊中的判斷UUID和洗掉鎖操作不是原子的,因此有可能會造成客戶端A加鎖,客戶端B解鎖的問題,
解決一:使用LUA腳本

解決二:redis自身的事務
使用Watch監控+事務

集群環境下鎖丟失問題
集群環境:

模擬場景:假設Redis主機加鎖成功后,回傳加鎖成功資訊,但是由于主從復制是異步操作,當主機加鎖成功后還未同步到從機上,主機就宕機了,那么會由哨兵模式在從機中進行選舉新的主機,但是此時的新主機沒被加鎖,就會出現例外,redis異步復制導致鎖的丟失
另外可以使用zookeeper實作,zookeeper和redis的不同點是zookeeper會在主從復制結束后再回傳資訊,因此保證的主從的一致性,但是這個程序是消耗時間的,資料的完整性和效率是不能共存的,可以根據實際情況選擇,
Redis分布式鎖如何實作續期?
RedLock落地實作續期
在執行緒加鎖進入到業務代碼時,會生成一個子執行緒,子執行緒會定期的查看業務是否執行完畢,如果執行完畢就按照主執行緒的邏輯走,如果沒執行完畢,那么子執行緒會將當前key的以當前時間點設定存活時間,存活的時間時間仍為之前的時間,定期查看的時間可以設定為key存活時間的1/3,

使用Redisson實作上面的思想
- RLock redissonLock = redission.getLock();
- redissionLock.lock(30,TmieUnit.SECONDS);加鎖并設定鎖的存活時間
- redissionLock.unLock();解鎖

補充
使用Redission在超高并發的情況下會拋出這樣一個例外:

意思就是加鎖和解鎖不是同一個執行緒操作,就相當于我們使用setnx時,在解鎖前需要判斷一次執行緒UUID,
判斷當前是否還是鎖著的狀態、鎖是當前執行緒持有的,才進行解鎖操作

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/259443.html
標籤:其他
上一篇:紅包系統流量高并發技術詳解
下一篇:Java面經-Dubbo
