在本篇文章中,咱們將使用Redisson中間件其中一個強大的功能組件「分佈式鎖」,用以解決秒殺系統中高併發產生的多線程對於共享資源/代碼塊的訪問所致使的「併發安全」問題!git
而之因此須要Redisson這一組件,是由於在上一篇文章中,咱們在採用Redis解決秒殺系統中出現的「庫存超賣」、「重複秒殺」等問題時所對應的代碼存在着瑕疵,即在使用Redis的SetNX操做以前、而還沒來得及執行Expire操做的時候,Redis的節點若是剛好出現宕機或者服務不能用的狀況,那將會致使相應的Key永遠存在於緩存中,而處於「被鎖死」的狀態!redis
Redisson分佈式鎖的出現能夠很好地解決這種問題,其底層的實現機制在於「Redisson內部提供了一個監控鎖的看門狗,它的做用是在Redisson實例被關閉前,不斷的延長鎖的有效期」,除此以外,Redisson還經過加鎖的方法提供了leaseTime的參數來指定加鎖的時間,即超過這個時間後鎖便自動解開了。spring
接下來,咱們將基於SpringBoot搭建的秒殺系統整合Redisson,加入其相關的依賴以及配置,並使用其「分佈式鎖」組件完全解決秒殺過程當中出現的「庫存超賣」以及「重複秒殺」等問題。數據庫
(1)首先,須要加入Redisson的依賴,版本號爲3.8.2,以下所示:緩存
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>複製代碼
而後須要在配置文件application.properties中加入Redis服務所在的Host、Port等信息,以下所示:
安全
#spring.redis.password=
redis.config.host=redis://127.0.0.1:6379複製代碼
(2)緊接着,是基於Spring Boot自定義注入Redisson相關操做的Bean組件,其中,主要是RedissonClient 操做組件的自定義注入,其完整源代碼以下所示:
bash
/**
* redisson通用化配置
* @Author:debug (SteadyJack)
* @Date: 2019/7/2 10:57
**/
@Configuration
public class RedissonConfig {
@Autowired
private Environment env;
@Bean
public RedissonClient redissonClient(){
Config config=new Config();
config.useSingleServer()
.setAddress(env.getProperty("redis.config.host"))
.setPassword(env.getProperty("spring.redis.password"));
RedissonClient client=Redisson.create(config);
return client;
}
}複製代碼
(3)前期工做已經準備完畢,接下來咱們須要將其應用到秒殺系統中 秒殺的核心操做邏輯,在KillService服務類中咱們開闢了一個新的處理方法,即killItemV4,其完整的源代碼以下所示:
微信
@Autowired
private RedissonClient redissonClient;
//商品秒殺核心業務邏輯的處理-redisson的分佈式鎖
@Override
public Boolean killItemV4(Integer killId, Integer userId) throws Exception {
Boolean result=false;
final String lockKey=new StringBuffer().append(killId).append(userId).append("-RedissonLock").toString();
RLock lock=redissonClient.getLock(lockKey);
try {
//TODO:第一個參數30s=表示嘗試獲取分佈式鎖,而且最大的等待獲取鎖的時間爲30s
//TODO:第二個參數10s=表示上鎖以後,10s內操做完畢將自動釋放鎖
Boolean cacheRes=lock.tryLock(30,10,TimeUnit.SECONDS);
if (cacheRes){
//TODO:核心業務邏輯的處理
if (itemKillSuccessMapper.countByKillUserId(killId,userId) <= 0){
ItemKill itemKill=itemKillMapper.selectByIdV2(killId);
if (itemKill!=null && 1==itemKill.getCanKill() && itemKill.getTotal()>0){
int res=itemKillMapper.updateKillItemV2(killId);
if (res>0){
commonRecordKillSuccessInfo(itemKill,userId);
result=true;
}
}
}else{
throw new Exception("redisson-您已經搶購過該商品了!");
}
}
}finally {
//TODO:釋放鎖
lock.unlock();
}
return result;
}複製代碼
從該源代碼中,咱們主要是使用了Redisson分佈式鎖中的「可重入鎖」組件,其使用須要通過以下幾個步驟:數據結構
A.須要嘗試去獲取鎖,其對應的代碼以及註釋以下所示:多線程
//TODO:第一個參數30s=表示嘗試獲取分佈式鎖,而且最大的等待獲取鎖的時間爲30s
//TODO:第二個參數10s=表示上鎖以後,10s內操做完畢將自動釋放鎖
Boolean cacheRes=lock.tryLock(30,10,TimeUnit.SECONDS);複製代碼
B.在獲取到鎖以後,即cacheRes=true,便可進入秒殺核心業務邏輯的處理;同時在處理完成以後,須要釋放鎖,以下所示:
//TODO:釋放鎖
lock.unlock();複製代碼
(4)至此,基於Redisson的分佈式鎖解決高併發業務場景下,併發多線程對於共享資源/共享代碼塊的併發訪問所出現的併發安全的問題的代碼實戰已經完畢了!
咱們接下來進入壓測環節,仍然以以前的測試用例爲例,即killId=3的待秒殺商品的可秒殺數量total=6,能夠隨機選取的用戶Id列表的總數爲10個,其取值爲10040~10049,則理論上最好的結果是:total最終變爲=0,同時item_kill_success有6條用戶秒殺成功後生成的訂單記錄。
這個時候,咱們嘗試將線程組中併發的線程數調整爲10w,點擊啓動按鈕,稍等片刻,觀察控制檯的輸出信息以及item_kill和item_kill_success的數據庫表,查看其最終的記錄結果,以下圖所示:
對於這一結果,其實能夠說是預料之中了!
Redisson的分佈式鎖確實能夠在高併發業務場景/多線程高併發 場景下起到舉足輕重的做用。而在現實生活中,其實Debug也是建議各位小夥伴能夠去研究這一綜合中間件,它徹底能夠替代Redis在項目中的使用,並且其提供的數據結構以及使用方式跟JavaSE中的數據結構很相似,好比List、Set、Map、Queue等等均可以在Java中找到相應的蹤跡(而事實上Redisson的許多分佈式組件跟數據結構正是基於Java中相應的數據結構來實現的)!
一、目前,這一秒殺系統的總體構建與代碼實戰已經所有完成了,完整的源代碼數據庫地址能夠來這裏下載:gitee.com/steadyjack/… 記得Fork跟Star啊!!
二、最後,不要忘記了關注一下Debug的技術微信公衆號: