Redis實現分布式鎖方法詳細

2022-09-20 10:48:10 字數 3258 閱讀 3000

目錄

在單體應用中,如果我們對共享資料不進行加鎖操作,會出現資料一致性問題,我們的解決辦法通常是加鎖。

在分布式架構中,我們同樣會遇到資料共享操作問題,本文章使用redis來解決分布式架構中的資料一致性問題。

單機資料一致性架構如下圖所示:多個可客戶訪問同乙個伺服器,連線同乙個資料庫。

場景描述:客戶端模擬購買商品過程,在redis中設定庫存總數剩100個,多個客戶端同時併發購買。

@restcontroller

public class indexcontroller1 else

return "購買商品失敗,服務埠為8001";}}

使用jmeter模擬高併發場景,測試結果如下:

測試結果出現多個使用者購買同一商品,發生了資料不一致問題!

解決辦法:單體應用的情況下,對併發的操作進行加鎖操作,保證對資料的操作具有原子性

@restcontroller

public class indexcontroller2 else

} catch (exception e) finally

return "購買商品失敗,服務埠為8001";}}

上面解決了單體應用的資料一致性問題,但如果是分布式架構部署呢,架構如下:

提供兩個服務,埠分別為8001、8002,連線同乙個redis服務,在服務前面有一台nginx作為負載均衡

兩台服務**相同,只是埠不同

將8001、8002兩個服務啟動,每個服務依然用reentrantlock加鎖,用jmeter做併發測試,發現會出現資料一致性問題!

取消單機鎖,下面使用redis的set命令來實現分布式加鎖

set key value [ex seconds] [px milliseconds] [nx|xx]

ex seconds 設定指定的到期時間(以秒為單位)

px milliseconds 設定指定的到期時間(以毫秒為單位)

nx 僅在鍵不存在時設定鍵

xx 只有在鍵已存在時才設定

@restcontroller

public class indexcontroller4

system.out.println( value+ " 搶鎖成功");

string result = template.opsforvalue().get("goods:001");

int total = result == null ? 0 : integer.parseint(result);

if (total > 0) else

return "購買商品失敗,服務埠為8001";

}finally }}

上面的**,可以解決分布式架構中資料一致性問題。但再仔細想想,還是會有問題,下面進行改進。

在上面的**中,如果程式在執行期間,部署了微服務jar包的機器突然掛了,**層面根本就沒有走到finally**塊,也就是說在宕機前,鎖並沒有被刪除掉,這樣的話,就沒辦法保證解鎖

所以,這裡需要對這個key加乙個過期時間,redis中設定過期時間有兩種方法:

第一種方法需要單獨的一行**,且並沒有與加鎖放在同一步操作,所以不具備原子性,也會出問題

第二種方法在加鎖的同時就進行了設定過期時間,所有沒有問題,這裡採用這種方式

調整下**,在加鎖的同時,設定過期時間:

// 為key加乙個過期時間,其餘**不變

boolean flag = template.opsforvalue().setifabsent(redis_lock,value,10l,timeunit.seconds);

這種方式解決了因服務突然宕機而無法釋放鎖的問題。但再仔細想想,還是會有問題,下面進行改進。

方veddg式二設定了key的過期時間,解決了key無法刪除的問題,但問題又來了

上面設定了key的過期時間為10秒,如果業務邏輯比較複雜,需要呼叫其他微服務,處理時間需要15秒(模擬場景,別較真),而當10秒鐘過去之後,這個key就過期了,其他請求就又可以設定這個key,此時如果耗時15秒的請求處理完了,回來繼續執行程式,就會把別人設定的key給刪除了,這是個很嚴重的問題!

所以,誰上的鎖,誰才能刪除

@restcontroller

public class indexcontroller6

system.outveddg.println( value+ " 搶鎖成功");

string result = template.opsforvalue().get("goods:001");

int total = result == null ? 0 : integer.parseint(result);

if (total > 0) else

return "購買商品失敗,服務埠為8001";

}finally }}

}這種方式解決了因服務處理時間太長而釋放了別人鎖的問題。這樣就沒問題了嗎?

在上面方式三下,規定了誰上的鎖,誰才能刪除,但finally快的判斷和del刪除操作不是原子操作,併發的時候也會出問題,併發嘛,就是要保證資料的一致性,保證資料的一致性,最好要保證對資料的操作具有原子性。

在redis的set命令介紹中,最後推薦lua指令碼進行鎖的刪除,位址:

@restcontroller

public class indexcontroller7

system.out.println( value+ " 搶鎖成功");

string result = template.opsforvalue().get("goods:001");

int total = result == null ? 0 : integer.parseint(result);

if (total > 0) else

return "購買商品失敗,服務埠為8001";

}finally else

}catch (exception e)finally }}

}}

在方式四下,規定了誰上的鎖,誰才能刪除,並且解決了刪除操作沒有原子性問題。但還沒有考慮快取續命,以及redis集群部署下,非同步複製造成的鎖丟失:主節點沒來得及把剛剛set進來這條資料給從節點,就掛了。所以直接上redlock的redisson落地實現。

@restcontroller

public class indexcontroller8 else

return "購買商品失敗,服務埠為8001";

}finally }}

}分析問題的過程,也是解決問題的過程,也能鍛鍊自己編寫**時思考問題的方式和角度。

上述測試**位址:

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

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

redis實現分布式鎖

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

redis實現分布式鎖

分布式鎖可以基於很多種方式實現,比如zookeeper redis.不管哪種方式,他的 基本原理是不變的 用乙個狀態值表示鎖,對鎖的占用和釋放通過狀態值來標識。1 使用redis的setnx命令實現分布式鎖 1 實現的原理 redis為單程序單執行緒模式,採用佇列模式將併發訪問變成序列訪問,且多客戶...