本人屬於個人總結:
原文轉載: https://blog.csdn.net/jeffleo/article/details/56015710
名詞解釋:
QPS :每秒查詢率QPS
本文是學習了immoc網視頻之後的個人理解和知識彙總
項目源碼:https://github.com/jeff-leo/SpikeSystem,希望大家能star和fork
1. 秒殺優化四個方面
(1) 詳情頁面
使用CDN加速,將css,js,html 不變的數據存放到cdn服務器;
用戶請求較近的cdn緩存服務器,獲取請求數據,如果cdn沒有,則cdn會自動向上直至找到網站根源;
使用CDN服務的網站,只需將其域名解析權交給CDN的GSLB設備,將需要分發的內容注入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
更多關於系統架構和分佈式的瞭解:
分佈式架構的演進