Java秒殺系統(tǒng)(十五):基于Redisson的分布式鎖優(yōu)化秒殺邏輯

作者: 修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。


摘要:本篇博文是“Java秒殺系統(tǒng)實戰(zhàn)系列文章”的第十五篇,本文我們將借助綜合中間件Redisson優(yōu)化“秒殺系統(tǒng)中秒殺的核心業(yè)務(wù)邏輯”,解決Redis的原子操作在優(yōu)化秒殺邏輯過程中出現(xiàn)的部分瑕疵。

內(nèi)容:Redisson,字如其名,是搭建在緩存中間件Redis的基礎(chǔ)之上的一款綜合中間件,除了擁有Redis本身提供的強大功能之外,還提供了諸如分布式鎖、分布式服務(wù)、延遲隊列、遠(yuǎn)程調(diào)用等強大的功能(從名字就可以看出來了:Redis + son,猶如Redis的兒子,兒子不僅繼承了老爸強大的血脈,而且還自己修煉、發(fā)展出了屬于自己的一套本領(lǐng))


在本篇文章中,我們將使用Redisson中間件其中一個強大的功能組件“分布式鎖”,用以解決秒殺系統(tǒng)中高并發(fā)產(chǎn)生的多線程對于共享資源/代碼塊的訪問所導(dǎo)致的“并發(fā)安全”問題!

而之所以需要Redisson這一組件,是因為在上一篇文章中,我們在采用Redis解決秒殺系統(tǒng)中出現(xiàn)的“庫存超賣”、“重復(fù)秒殺”等問題時所對應(yīng)的代碼存在著瑕疵,即在使用Redis的SetNX操作之前、而還沒來得及執(zhí)行Expire操作的時候,Redis的節(jié)點如果恰好出現(xiàn)宕機或者服務(wù)不能用的情況,那將會導(dǎo)致相應(yīng)的Key永遠(yuǎn)存在于緩存中,而處于“被鎖死”的狀態(tài)!

Redisson分布式鎖的出現(xiàn)可以很好地解決這種問題,其底層的實現(xiàn)機制在于“Redisson內(nèi)部提供了一個監(jiān)控鎖的看門狗,它的作用是在Redisson實例被關(guān)閉前,不斷的延長鎖的有效期”,除此之外,Redisson還通過加鎖的方法提供了leaseTime的參數(shù)來指定加鎖的時間,即超過這個時間后鎖便自動解開了。

接下來,我們將基于SpringBoot搭建的秒殺系統(tǒng)整合Redisson,加入其相關(guān)的依賴以及配置,并使用其“分布式鎖”組件徹底解決秒殺過程中出現(xiàn)的“庫存超賣”以及“重復(fù)秒殺”等問題。

(1)首先,需要加入Redisson的依賴,版本號為3.8.2,如下所示:

<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>

然后需要在配置文件application.properties中加入Redis服務(wù)所在的Host、Port等信息,如下所示:  

#spring.redis.password=
redis.config.host=redis://127.0.0.1:6379

(2)緊接著,是基于Spring Boot自定義注入Redisson相關(guān)操作的Bean組件,其中,主要是RedissonClient 操作組件的自定義注入,其完整源代碼如下所示:  

/**
* 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)前期工作已經(jīng)準(zhǔn)備完畢,接下來我們需要將其應(yīng)用到秒殺系統(tǒng)中 秒殺的核心操作邏輯,在KillService服務(wù)類中我們開辟了一個新的處理方法,即killItemV4,其完整的源代碼如下所示:  

@Autowired
private RedissonClient redissonClient;

//商品秒殺核心業(yè)務(wù)邏輯的處理-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:第一個參數(shù)30s=表示嘗試獲取分布式鎖,并且最大的等待獲取鎖的時間為30s
//TODO:第二個參數(shù)10s=表示上鎖之后,10s內(nèi)操作完畢將自動釋放鎖
Boolean cacheRes=lock.tryLock(30,10,TimeUnit.SECONDS);
if (cacheRes){
//TODO:核心業(yè)務(wù)邏輯的處理
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-您已經(jīng)搶購過該商品了!");
}
}
}finally {
//TODO:釋放鎖
lock.unlock();
}
return result;
}

從該源代碼中,我們主要是使用了Redisson分布式鎖中的“可重入鎖”組件,其使用需要經(jīng)過如下幾個步驟:

A.需要嘗試去獲取鎖,其對應(yīng)的代碼以及注釋如下所示:

//TODO:第一個參數(shù)30s=表示嘗試獲取分布式鎖,并且最大的等待獲取鎖的時間為30s
//TODO:第二個參數(shù)10s=表示上鎖之后,10s內(nèi)操作完畢將自動釋放鎖
Boolean cacheRes=lock.tryLock(30,10,TimeUnit.SECONDS);

B.在獲取到鎖之后,即cacheRes=true,即可進(jìn)入秒殺核心業(yè)務(wù)邏輯的處理;同時在處理完成之后,需要釋放鎖,如下所示:  

//TODO:釋放鎖
lock.unlock();

(4)至此,基于Redisson的分布式鎖解決高并發(fā)業(yè)務(wù)場景下,并發(fā)多線程對于共享資源/共享代碼塊的并發(fā)訪問所出現(xiàn)的并發(fā)安全的問題的代碼實戰(zhàn)已經(jīng)完畢了!

我們接下來進(jìn)入壓測環(huán)節(jié),仍然以之前的測試用例為例,即killId=3的待秒殺商品的可秒殺數(shù)量total=6,可以隨機選取的用戶Id列表的總數(shù)為10個,其取值為10040~10049,則理論上最好的結(jié)果是:total最終變?yōu)?0,同時item_kill_success有6條用戶秒殺成功后生成的訂單記錄。

這個時候,我們嘗試將線程組中并發(fā)的線程數(shù)調(diào)整為10w,點擊啟動按鈕,稍等片刻,觀察控制臺的輸出信息以及item_kill和item_kill_success的數(shù)據(jù)庫表,查看其最終的記錄結(jié)果,如下圖所示:


對于這一結(jié)果,其實可以說是預(yù)料之中了!

Redisson的分布式鎖確實可以在高并發(fā)業(yè)務(wù)場景/多線程高并發(fā) 場景下起到舉足輕重的作用。而在現(xiàn)實生活中,其實Debug也是建議各位小伙伴可以去研究這一綜合中間件,它完全可以替代Redis在項目中的使用,而且其提供的數(shù)據(jù)結(jié)構(gòu)以及使用方式跟JavaSE中的數(shù)據(jù)結(jié)構(gòu)很類似,比如List、Set、Map、Queue等等都可以在Java中找到相應(yīng)的蹤影(而事實上Redisson的許多分布式組件跟數(shù)據(jù)結(jié)構(gòu)正是基于Java中相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)的)! 

補充:

1、目前,這一秒殺系統(tǒng)的整體構(gòu)建與代碼實戰(zhàn)已經(jīng)全部完成了,該秒殺系統(tǒng)對應(yīng)的視頻教程的鏈接地址為:https://www.fightjava.com/web/index/course/detail/6,可以點擊鏈接進(jìn)行試看以及學(xué)習(xí),實戰(zhàn)期間有任何問題都可以留言或者與Debug聯(lián)系、交流!

2、另外,Debug也開源了該秒殺系統(tǒng)對應(yīng)的完整的源代碼以及數(shù)據(jù)庫,其地址可以來這里下載:https://gitee.com/steadyjack/SpringBoot-SecondKill 記得Fork跟Star啊?。?!

3、最后,不要忘記了關(guān)注一下Debug的技術(shù)微信公眾號: