高併發秒殺系統分析

本人屬於個人總結: 

原文轉載: https://blog.csdn.net/jeffleo/article/details/56015710

名詞解釋:

    QPS 每秒查詢率QPS

 

本文是學習了immoc網視頻之後的個人理解和知識彙總 
項目源碼:https://github.com/jeff-leo/SpikeSystem,希望大家能starfork

   

1. 秒殺優化四個方面

(1) 詳情頁面

使用CDN加速,將cssjshtml 不變的數據存放到cdn服務器;

 

用戶請求較近的cdn緩存服務器,獲取請求數據,如果cdn沒有,則cdn會自動向上直至找到網站根源;

使用CDN服務的網站,只需將其域名解析權交給CDNGSLB設備,將需要分發的內容注入CDN,就可以實現內容加速了。

(2) 獲取系統時間操作的優化

Java每秒訪問內存10億次,服務器獲取系統時間不用優化;

3. 秒殺地址接口獲取的優化

 

先操作redis,每個用戶請求在redis中進行記錄,如果當前請求量增加,修改redis記錄,暫時關閉秒殺接口;

 

 

(3)  Redis是否可以管理庫存?

庫存使用數據庫維護比較好,秒殺數量則可以利用redis的原子性進行控制;

凡是需要進行寫操作的數據都不適合做緩存。 庫存不能在redis中維護;

(4) 秒殺操作高併發的問題(重點)

不能在redis中作庫存管理,因爲會導致數據一致性問題,(凡是需要進行寫操作的數據都不適合做緩存。)

① 秒殺流程:

 

用戶請求-> 已提交請耐心等待

② 引發問題:

熱點商品同時秒殺,會引起數據庫行級鎖,這是其他用戶等待;

③ 解決辦法:

1) 方案一介紹:

利用redis原子計數器,秒殺商品計數器-1,0則隱藏接口,不再秒殺;

 

然後記錄用戶,將用戶ID與商品ID,購買數量發送MQ中,進行訂單處理,此時返回用戶“正在秒殺,請等待。。。”

接着消息隊列的消費者進程異步獲取數據,生成訂單信息,此時訂單狀態爲“未付款”

頁面顯示“正在秒殺,請等待。。。” ,但頁面每2秒進行一次異步查詢秒殺結果,根據結果進行頁面跳轉處理;

秒殺成功後,30分鐘內進行付款;過期則將商品數量返還給秒殺原子計數器;

用戶付款成功,真正修改數據庫庫存狀態;

 

2) 方案一問題:

需要強大的運維團隊,NoSql不如MySql穩定

重發秒殺的問題。一個用戶重發秒殺請求,避免這種情況需要創建另一個NoSql存儲請求者信息,如果發現存在該用戶秒殺請求,下一次不進行處理;

 

問題:mysql性能上不去,是因爲存在網絡延遲

把服務器的執行邏輯放在mysql服務端,避免網絡延遲和gc延遲

 

 

 

3) 方案二介紹:

使用存儲過程完成MySql秒殺的事物操作;

 

<!-- mybatis 調用存儲過程-->
    <select id="seckillByProduce" statementType="CALLABLE">
        CALL excuteSeckill(
          #{ seckillId , jdbcType = BIGINT , mode= IN },
          #{ phone ,jdbcType = BIGINT , mode= IN },
          #{ killTime , jdbcType = TIMESTAMP , mode= IN },
          #{ result , jdbcType = BIGINT , mode= OUT }
        )
    </select>

 

 public SeckillExecution executeSeckillByProducure(long seckillId, long userPhone, String md5) {
        if(md5 == null || !md5.equals(getMd5(seckillId))){//數據篡改
            return new SeckillExecution(seckillId, SeckillStatEnum.DATE_REWRITE);
        }
        Date now = new Date();
        Map params = new HashMap();
        params.put("seckillId", seckillId);
        params.put("phone", userPhone);
        params.put("killTime", now);
        params.put("result", null);
        try {
            seckillMapper.seckillByProduce(params);
            Integer result = MapUtils.getInteger(params, "result", -3);
            if(result == 1){//成功
                SuccessKilled successKilled = successKilledMapper.queryByIdWithSeckill(seckillId, userPhone);
                return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);
            }else{//如果失敗,把錯誤狀態-1或者-2等返回
                return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));
            }
        } catch (Exception e){//出現異常也要返回
            logger.error(e.getMessage(), e);
            return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
        }
    }

 

參考: 
CDN的基本工作過程 
imooc

更多關於系統架構和分佈式的瞭解: 
分佈式架構的演進

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章