本篇博文是「Java秒殺系統實戰系列文章」的第十三篇,從本篇文章開始咱們將進入「秒殺代碼優化」環節,本文將首先從數據庫級別Sql的優化入手,結合調整秒殺相關的部分核心代碼,實現初步的優化!mysql
上篇文章咱們暴露出了「秒殺接口」在面對高併發請求的場景下所出現的「超賣」、「重複秒殺」等問題,並對相應的問題進行了分析,而後就沒有而後了……(事了拂衣去!)git
問題既然落在咱們的手裏,那麼身爲一名程序猿,那是沒有理由迴避的。經過分析該「秒殺接口」的核心代碼,能夠發如今數據庫層面,其涉及的Sql咱們仍是能夠動一動手腳的!其調整後的「秒殺核心業務邏輯」的完整源代碼以下所示:算法
//商品秒殺核心業務邏輯的處理-mysql的優化
@Override
public Boolean killItemV2(Integer killId, Integer userId) throws Exception {
Boolean result=false;
//TODO:判斷當前用戶是否已經搶購過當前商品
if (itemKillSuccessMapper.countByKillUserId(killId,userId) <= 0){
//A 查詢待秒殺商品詳情
ItemKill itemKill=itemKillMapper.selectByIdV2(killId);
//TODO:判斷是否能夠被秒殺canKill=1?
if (itemKill!=null && 1==itemKill.getCanKill() && itemKill.getTotal()>0){
//B 扣減庫存-減一
int res=itemKillMapper.updateKillItemV2(killId);
//TODO:扣減是否成功?是-生成秒殺成功的訂單,同時通知用戶秒殺成功的消息
if (res>0){
commonRecordKillSuccessInfo(itemKill,userId);
result=true;
}
}
}else{
throw new Exception("您已經搶購過該商品了!");
}
return result;
}複製代碼
首先是對於 註釋A 那裏的調整,即在獲取「秒殺商品詳情」時,咱們限定了「可秒殺商品的數量total須要大於0」,其對應的代碼爲:itemKillMapper.updateKillItemV2(killId);完整的動態Sql以下所示:
sql
<!--獲取秒殺詳情V2-->
<select id="selectByIdV2" resultType="com.debug.kill.model.entity.ItemKill">
SELECT
a.*,
b.name AS itemName,
(CASE WHEN (now() BETWEEN a.start_time AND a.end_time)
THEN 1
ELSE 0
END) AS canKill
FROM item_kill AS a LEFT JOIN item AS b ON b.id = a.item_id
WHERE a.is_active = 1 AND a.id =#{id} AND a.total>0
</select>複製代碼
而後是 註釋B 對應的優化調整,即在扣減庫存時,咱們除了能夠保證正常減1的操做以外,還須要保證扣減完以後的數量大於0,即只有在保證扣減完以後的數量大於0之下,該Sql操做後受影響的行數爲1,對應的代碼爲:itemKillMapper.updateKillItemV2(killId); 其對應的動態Sql以下所示:
數據庫
<!--搶購商品,剩餘數量減一-->
<update id="updateKillItemV2">
UPDATE item_kill
SET total = total - 1
WHERE id = #{killId} AND total>0
</update>複製代碼
至此,咱們在秒殺核心業務邏輯的優化層面~數據庫級別Sql的優化 已經搞完了!除此以外,咱們還在代碼層面進行優化,以下所示:
bash
private void commonRecordKillSuccessInfo(ItemKill kill, Integer userId) throws Exception{
//TODO:記錄搶購成功後生成的秒殺訂單記錄
ItemKillSuccess entity=new ItemKillSuccess();
String orderNo=String.valueOf(snowFlake.nextId());
//entity.setCode(RandomUtil.generateOrderCode()); //傳統時間戳+N位隨機數
entity.setCode(orderNo); //雪花算法
entity.setItemId(kill.getItemId());
entity.setKillId(kill.getId());
entity.setUserId(userId.toString());
entity.setStatus(SysConstant.OrderStatus.SuccessNotPayed.getCode().byteValue());
entity.setCreateTime(DateTime.now().toDate());
//TODO:學以至用,觸類旁通 -> 仿照單例模式的雙重檢驗鎖寫法
if (itemKillSuccessMapper.countByKillUserId(kill.getId(),userId) <= 0){
int res=itemKillSuccessMapper.insertSelective(entity);
if (res>0){
//TODO:進行異步郵件消息的通知=rabbitmq+mail
rabbitSenderService.sendKillSuccessEmailMsg(orderNo);
//TODO:入死信隊列,用於 「失效」 超過指定的TTL時間時仍然未支付的訂單
rabbitSenderService.sendKillSuccessOrderExpireMsg(orderNo);
}
}
}複製代碼
即咱們在「用戶秒殺成功生成訂單記錄」的代碼加入了相似於「單例模式」中的「雙重檢驗鎖」,即在生成訂單記錄,再次判斷一下「當前用戶是否已經真的沒有搶購過該商品」!微信
在後面的篇章中,咱們將開始搬上「中間件」這一利器,並結合本文所介紹Sql的優化和調整後的代碼,完全解決高併發壓力測試的場景下出現的「庫存超賣」、「重複秒殺」等亂七八糟的問題!併發
一、目前,這一秒殺系統的總體構建與代碼實戰已經所有完成了,完整的源代碼數據庫地址能夠來這裏下載:gitee.com/steadyjack/… 記得Fork跟Star啊!!app
二、最後,不要忘記了關注一下Debug的技術微信公衆號:dom