分布式鎖原來實現(xiàn)起來這么簡單
本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。
阿粉最近迷上了 Redis,為什么呢?感覺 Redis 確實功能很強大呀,一個基于內(nèi)存的 Key-Value 存儲的數(shù)據(jù)庫,竟然有這么多的功能,而阿粉也要實實在在的把 Redis 來弄一下,畢竟面試的時候,Redis 可以說是一個非常不錯的加分項。
分布式鎖
為什么需要分布式鎖?
目前很多的大型項目全部都是基于分布式的,而分布式場景中的數(shù)據(jù)一致性問題一直是一個不可忽視的問題,大家知道關(guān)于分布式的 CAP 理論么?
CAP 理論就是說任何一個分布式系統(tǒng)都無法同時滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯性(Partition tolerance),最多只能同時滿足兩項。
而我們的系統(tǒng)最終滿足的永遠都是最終一致性,而這種最終一致性,有些時候有人會喜歡問關(guān)于分布式事務(wù),而有些人則偏重在分布式鎖上。
分布式鎖的種類
- 數(shù)據(jù)庫實現(xiàn)分布式鎖
- 緩存實現(xiàn)分布式鎖
- Zookeeper實現(xiàn)分布式鎖
但是阿粉選擇的就是使用緩存來實現(xiàn)分布式鎖,也就是我們在項目中最經(jīng)常使用的 Redis ,談到 Redis,那真是可以用在太多地方了,比如說:
- 會話緩存
- 消息隊列
- 分布式鎖
- 發(fā)布,訂閱消息
- 商品列表,評論列表
我們今天就來實現(xiàn)用 Redis 來實現(xiàn)分布式鎖,并且要學(xué)會怎么使用。
準(zhǔn)備工作
1.準(zhǔn)備使用 Jedis 的 jar 包,在項目中導(dǎo)入 jar 包。
- <!--jedis-->
- <dependency>
- <groupId>redis.clients</groupId>
- <artifactId>jedis</artifactId>
- <version>2.9.0</version>
- </dependency>
直接來寫個工具類吧
- public class RedisPoolUtil {
- private static final String LOCK_SUCCESS = "OK";
- private static final String SET_IF_NOT_EXIST = "NX";
- private static final String SET_WITH_EXPIRE_TIME = "PX";
- private RedisPoolUtil(){}
- /**
- *
- * @param jedis
- * @param lockKey 加鎖
- * @param requestId 請求的標(biāo)志位
- * @param expireTime 超時時間
- * @return
- */
- public static boolean tryGetDistributedLock(Jedis jedis,String lockKey, String requestId, int expireTime) {
- String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
- if (LOCK_SUCCESS.equals(result)) {
- return true;
- }else{
- try{
- Thread.sleep(10);//休眠100毫秒
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- return false;
- }
- }
jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); 這個加鎖的姿勢才是我們最需要了解的,不然你用的時候都不知道怎么使用。
key:加鎖的鍵,實際上就是相當(dāng)于一個唯一的標(biāo)志位,不同的業(yè)務(wù),你可以使用不同的標(biāo)志位進行加鎖。
requestId:這個東西實際上就是用來標(biāo)識他是哪一個請求進行的加鎖,因為在分布式鎖中,我們要知道一件事,就是加鎖的和解鎖的,必須是同一個客戶端才可以。
而且還有一種比較經(jīng)典的就是 B 把 A 的鎖給釋放了,導(dǎo)致釋放混亂,如果你不加相同的請求,A 線程處理業(yè)務(wù),執(zhí)行了加鎖,鎖的過期時間是5s, B線程嘗試獲取鎖,如果 A 處理業(yè)務(wù)時間超過5s,這時候 A 就要開始釋放鎖,而B在這時候沒有檢測到這個鎖,從而進行了加鎖,這時候加鎖的時候,A還沒處理完對應(yīng)業(yè)務(wù),當(dāng)他處理完了之后,再釋放鎖的話,要是就是直接把 B 剛加的鎖釋放了,要么就是壓根都沒辦法釋放鎖。
SET_IF_NOT_EXIST:看字面意思,如果 key 不存在,我們進行Set操作,如果存在,啥都不干,也就不在進行加鎖。
SET_WITH_EXPIRE_TIME:是否過期
expireTime:這是給 key 設(shè)置一個過期的時間,萬一你這業(yè)務(wù)一直被鎖著了,然后之后的業(yè)務(wù)想加鎖,你直接給一直持有這個這個鎖,不進行過期之后的釋放,那豈不是要涼了。
上面的方法中 tryGetDistributedLock 這個方法也就是我們通常使用的加鎖的方法。
解鎖
- public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
- String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
- Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
- if ("OK".equals(result)) {
- return true;
- }
- return false;
- }
大家看到這個 script的時候,會感覺有點奇怪,實際上他就是一個 Lua 的腳本,而 Lua 腳本的意思也比較簡單。
- 先獲取鎖對應(yīng)的value值,檢查是否與requestId相等
- 如果相等則刪除鎖(解鎖)
- 執(zhí)行eval()方法
其實這時候就有些人說,直接 del 刪除不行么?你試試你如果這么寫的話,你們的領(lǐng)導(dǎo)會不會把你的腿給你打斷。
這種不先判斷鎖的擁有者而直接解鎖的方式,會導(dǎo)致任何客戶端都可以隨時進行解鎖,也就是說,這鎖就算不是我加的,我都能開,這怎么能行呢?
在這里給大家放一段使用的代碼,比較簡單,但是可以直接用到你們的項目當(dāng)中
- try{
- Boolean result = RedisPoolUtil.tryGetDistributedLock(jedis, "xxxxx", uuid, 5000);
- if(result) {
- xxxx代碼片段
- }else{
- }
- }catch(){
- }finally{
- RedisPoolUtil.releaseDistributedLock(jedis,"xxxxx", uuid);
- }
分布式鎖的要求
滿足互斥性。也就是說不管在什么時候,只有一個客戶端能夠持有鎖,不能是多個客戶端。
不能出現(xiàn)死鎖。就是說,如果要實現(xiàn)分布式鎖,不能說當(dāng)一個鎖沒有釋放的時候,其他的客戶端不能進行加鎖,要保證不影響其他的客戶端加鎖。
加鎖和解鎖必須是同一個客戶端
分布式的CAP理論
我們先把這個實現(xiàn)方式實現(xiàn)了,然后我們再來說說大家最不愿意看的理論知識,畢竟這理論知識是你面試的時候經(jīng)常會被問到的。
分布式CAP理論:
加州大學(xué)伯克利分校的 Eric Brewer 教授在 ACM PODC 會議上提出 CAP 猜想。2年后,麻省理工學(xué)院的 Seth Gilbert 和 Nancy Lynch 從理論上證明了 CAP。之后,CAP 理論正式成為分布式計算領(lǐng)域的公認定理。
也就是說,在二十年前的時候,CAP 理論只是個猜想。結(jié)果兩年之后被證實了,于是,大家在考慮分布式的時候,就有根據(jù)來想了,不再是空想了。
什么是分布式的 CAP 理論 ?
一個分布式系統(tǒng)最多只能同時滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯性(Partition tolerance)這三項中的兩項
這個和(Atomicity)不太一樣,因為之前看有些人說,在 CAP 理論中的 A 和數(shù)據(jù)庫事務(wù)中的 A 是一樣的,單詞都不一樣,那能一樣么?
Availability :分布式中的 A 表示的是可用性,也就是說服務(wù)一直可用,而且是正常響應(yīng)時間。
而你在搭建分布式系統(tǒng)的時候,要保證每個節(jié)點都是穩(wěn)定的,不然你的可用性就沒有得到相對應(yīng)的保證,也談不上是什么分布式了。只能稱之為一個偽分布式。
Consistency:一致性
也就是說你的更新操作成功并返回客戶端完成后,所有節(jié)點在同一時間的數(shù)據(jù)完全一致,這個如果你在使用 Redis 做數(shù)據(jù)展示的時候,很多面試官都會問你,那你們是怎么保證數(shù)據(jù)庫和緩存的一致性的呢?
畢竟你只是讀取的話,沒什么問題,但是設(shè)計到更新的時候,不管是先寫數(shù)據(jù)庫,再刪除緩存;還是先刪除緩存,再寫庫,都有可能出現(xiàn)數(shù)據(jù)不一致的情況。
所以如果你對這個很感興趣,可以研究一下,比如說:
- 延時雙刪策略
- 懶加載 懶加載可采取雙刪+TTL失效來實現(xiàn)
- 主動加載
如果你能在面試的時候把這些都給面試官說清楚,至少感覺你應(yīng)該能達到你自己的工資要求。
Partition tolerance:分區(qū)容錯性
分布式系統(tǒng)在遇到某節(jié)點或網(wǎng)絡(luò)分區(qū)故障的時候,仍然能夠?qū)ν馓峁M足一致性和可用性的服務(wù)。
其實在 CAP 理論當(dāng)中,我們是沒有辦法同時滿足一致性、可用性和分區(qū)容錯性這三個特性,所以有所取舍就可以了。
關(guān)于使用 Redis 分布式鎖,大家學(xué)會了么?