- 今日推薦
- 特別關(guān)注
-
- 企業(yè)想要資本運(yùn)作這篇文章把融資這事講明白了什么「如何進(jìn)行資本運(yùn)作」
- 銀河應(yīng)用商店電視直播「第二銀河軍團(tuán)礦船默認(rèn)裝貨時(shí)間」
- 淘寶客服kpi是什么意思怎么設(shè)置「電話客服kpi」
- 直播平臺(tái)流水造假「退款理由材質(zhì)造假有影響嗎」
- 什么是融資租賃融資租賃的業(yè)務(wù)模式及并購(gòu)方式的特點(diǎn)「融資租賃交易模式」
- b端產(chǎn)品的核心價(jià)值「b端設(shè)計(jì)師」
- 企業(yè)跨國(guó)經(jīng)營(yíng)過程中如何防范匯率風(fēng)險(xiǎn)「人民幣匯率對(duì)出口企業(yè)影響」
- 電子商務(wù)出口企業(yè)出口貨物退免稅政策「跨境貿(mào)易便利化措施」
- 電商客服激勵(lì)考核制度「電商客服主管kpi考核表」
- 合伙做跨境電商「合伙做電商」
- 熱門點(diǎn)擊
-
- 客服外包前景「客服外包服務(wù)商」
- 電商毀了實(shí)體店「電商搞垮實(shí)體店」
- 電商在什么情況下選擇客服外包才是正確的時(shí)機(jī)「電商外包客服如何開展業(yè)務(wù)」
- 電商管理層如何分配合理「團(tuán)隊(duì)組建方案」
- 淘寶客服管理制度「淘寶客服基本工作流程包括」
- 熱搜第一淘寶崩了「成為淘寶熱搜詞」
- 電商外包客服好不好「網(wǎng)店客服外包靠譜嗎」
- KPI考核什么意思「職場(chǎng)必須懂的社會(huì)常識(shí)」
- 客服外包公司什么意思「電話客服kpi」
- 客服外包:電商行業(yè)發(fā)展下的必然趨勢(shì)「現(xiàn)在的客服都是外包的嗎」
如何使用redis來實(shí)現(xiàn)秒殺「redis秒殺」
redis實(shí)現(xiàn)秒殺
背景:
某電商網(wǎng)站實(shí)現(xiàn)秒殺功能,用戶在某個(gè)時(shí)間段內(nèi)能夠搶購(gòu)到特價(jià)商品,且某一商品最多只能被同一用戶搶購(gòu)一次。
基本思路:
秒殺商品由商家后臺(tái)添加,秒殺商品數(shù)據(jù)保存在tb_seckilll_goods表中,關(guān)鍵字段包括:id,status(審核狀態(tài)),start_time(開始時(shí)間),end_time(結(jié)束時(shí)間),stock_count(庫存量);寫一個(gè)定時(shí)器,定時(shí)從秒殺商品表中掃描數(shù)據(jù),將符合條件的商品加載到緩存中;條件:審核狀態(tài)="1",start_time < 當(dāng)前時(shí)間 < end_time,庫存量大于0;前端展示,此處略點(diǎn)擊搶購(gòu),拿著秒殺商品的id去緩存中查詢,如果緩存中商品不存在或者為空,提示“已售罄”,否則生成訂單,保存到緩存中,訂單表tb_seckill_order庫存-1,判斷減完之后緩存中商品的庫存是否大于0,大于0則更新緩存,否則刪除該秒殺商品的緩存,并更新到數(shù)據(jù)庫技術(shù)選型:緩存redis,定時(shí)器:spring整合quartz
如下完成了一個(gè)基本的秒殺下單的業(yè)務(wù):
掃描秒殺商品加載到redis:
@Scheduled(cron = "0 */1 * * * ?")//cron表達(dá)式:每分鐘執(zhí)行一次,周期可任意定義
public void importToRedis(){
//1.查詢合法秒殺商品數(shù)據(jù)
TbSeckillGoodsExample example = new TbSeckillGoodsExample();
Date date = new Date();
example.createCriteria().andStatusEqualTo("1").andStockCountGreaterThan(0)
.andStartTimeLessThan(date).andEndTimeGreaterThan(date);
List<TbSeckillGoods> tbSeckillGoods = seckillGoodsMapper.selectByExample(example);
for (TbSeckillGoods seckillGood : tbSeckillGoods) {//將秒殺商品依次存入redis
//注意如果redis中已經(jīng)有的商品,則不更新,只添加之前未加入過的秒殺商品
if(redisTemplate.boundHashOps("TbSeckillGoods").get(seckillGood.getId()) == null){
redisTemplate.boundHashOps("TbSeckillGoods").put(seckillGood.getId(), seckillGood);
}
}
}
對(duì)所有的秒殺商品都使用同一個(gè)key:“TbSeckillGoods”,值的存儲(chǔ)類型為hash
下單的service代碼:
public Result saveOrder(Long id, String userId) {
//根據(jù)商品id從redis中查出商品
TbSeckillGoods seckillGood = (TbSeckillGoods) redisTemplate.boundHashOps(TbSeckillGoods.class.getSimpleName()).get(id);
//如果緩存中秒殺商品不存在或者庫存為空,則提示已售罄
if(seckillGood == null || seckillGood.getStockCount() <= 0){
return new Result(false, "已售罄");
}
//如果時(shí)間已截止,提示秒殺時(shí)間已結(jié)束
if(seckillGood.getEndTime().getTime() < System.currentTimeMillis()){
return new Result(false, "活動(dòng)已結(jié)束");
}
//生成訂單保存到緩存中
TbSeckillOrder seckillOrder = new TbSeckillOrder();
seckillOrder.setUserId(userId);
seckillOrder.setSeckillId(idWorker.nextId());
seckillOrder.setSellerId(seckillGood.getSellerId());
seckillOrder.setMoney(seckillGood.getCostPrice());
seckillOrder.setStatus("0");//未支付
seckillOrder.setCreateTime(new Date());
redisTemplate.boundHashOps(TbSeckillOrder.class.getSimpleName()).put(userId, seckillOrder);
//秒殺商品庫存量減1
seckillGood.setStockCount(seckillGood.getStockCount() - 1);
//判斷減完之后redis中商品的庫存是否大于0,大于0則更新緩存,否則刪除該秒殺商品的緩存,并更新到數(shù)據(jù)庫
if(seckillGood.getStockCount() > 0){
redisTemplate.boundHashOps(TbSeckillGoods.class.getSimpleName()).put(seckillGood.getGoodsId(), seckillGood);
}else {
redisTemplate.boundHashOps(TbSeckillGoods.class.getSimpleName()).delete(seckillGood.getGoodsId());
seckillGoodsMapper.updateByPrimaryKey(seckillGood);
}
return new Result(true, "恭喜您搶購(gòu)到商品,請(qǐng)盡快支付");
}
以上是關(guān)鍵代碼,其他業(yè)務(wù)代碼可不關(guān)注,完整代碼可在我的github中查看
分析上述代碼:
上述代碼在多線程環(huán)境下存在三個(gè)問題:
1.超賣:
if(seckillGood == null || seckillGood.getStockCount() <= 0){
return new Result(false, "已售罄");
}
業(yè)務(wù)邏輯是如果seckillGood不為null,且?guī)齑?gt;0,即可進(jìn)行下單,但是在實(shí)際環(huán)境中,可能會(huì)有很多的用戶同時(shí)獲取到redis中的商品信息,每個(gè)用戶讀取到的庫存量一樣且均大于0,假如庫存只有2,但是有三個(gè)用戶都符合下單條件,就出現(xiàn)了超賣情況
2.沒有對(duì)用戶多次搶購(gòu)做限制
3.下單和生成訂單串行,影響并發(fā)效率。完全可以在用戶搶購(gòu)之后立即能夠下單成功,后續(xù)的訂單處理可以利用多線程來異步操作
解決方案:
1.對(duì)于超賣問題,很容易想到是就是對(duì)下單操作加鎖,一次只能有一個(gè)用戶進(jìn)行下單并減庫存。這種方法可以避免超賣問題,但是卻會(huì)導(dǎo)致效率下降。
redis中有一種存儲(chǔ)結(jié)構(gòu)list,它的元素在彈出時(shí)能夠保證一次只有一個(gè)線程進(jìn)行操作,并且效率比較高。例如,我們?cè)阡浫朊霘⑸唐返耐瑫r(shí),對(duì)每一種商品都創(chuàng)建一個(gè)list,該商品的庫存有多少,list中的元素就有多少個(gè),每次下單就從list中彈出一個(gè)元素,防止超賣。
如圖:以“SECKILLGOODS_ID_PREFIX_秒殺商品ID”的格式字符串作為list的key,商品庫存有n,則該list就有n個(gè)元素,元素的壓入在錄入商品時(shí)完成,每下單一次,就彈出一個(gè)元素。
2.對(duì)于同一用戶多次搶購(gòu)的問題,我們同樣可以使用redis來記錄每種商品已搶購(gòu)成功的用戶id,我們使用set來記錄用戶id,防止用戶id重復(fù)
如圖:以“USER_ID_PREFIX_秒殺商品ID”的格式字符串作為set的key,一旦有一個(gè)用戶搶購(gòu)了該商品,則在先判斷Set集合中是否存在用戶id,不存在則添加
3.多線程處理訂單,在redis中創(chuàng)建一個(gè)隊(duì)列,每當(dāng)一個(gè)用戶成功搶購(gòu)一個(gè)商品,就往隊(duì)列中壓入一個(gè)下單數(shù)據(jù),包含商品id和用戶id即可。線程從隊(duì)列中彈出一個(gè)包含下單數(shù)據(jù)的元素,進(jìn)行訂單的生成
如圖:OrederRecorder作為key,集合中記錄了搶購(gòu)成功的商品id和用戶id,等待多線程去從集合中彈出元素進(jìn)行處理
整個(gè)秒殺業(yè)務(wù)的大致流程如下:
完整代碼可參考https://github.com/ithushuai/seckill-demo
秒殺活動(dòng)是絕大部分電商選擇的低價(jià)促銷、推廣品牌的方式。不僅可以給平臺(tái)帶來用戶量,還可以提高平臺(tái)知名度。一個(gè)好的秒殺系統(tǒng),可以提高平臺(tái)系統(tǒng)的穩(wěn)定性和公平性,獲得更好的用戶體驗(yàn),提升平臺(tái)的口碑,從而提升秒殺活動(dòng)的最大價(jià)值。本文討論云數(shù)據(jù)庫Redis版緩存設(shè)計(jì)高并發(fā)的秒殺系統(tǒng)。
秒殺的特征
秒殺活動(dòng)對(duì)稀缺或者特價(jià)的商品進(jìn)行定時(shí)定量售賣,吸引成大量的消費(fèi)者進(jìn)行搶購(gòu),但又只有少部分消費(fèi)者可以下單成功。因此,秒殺活動(dòng)將在較短時(shí)間內(nèi)產(chǎn)生比平時(shí)大數(shù)十倍,上百倍的頁面訪問流量和下單請(qǐng)求流量。
秒殺活動(dòng)可以分為3個(gè)階段:
秒殺前:用戶不斷刷新商品詳情頁,頁面請(qǐng)求達(dá)到瞬時(shí)峰值。秒殺開始:用戶點(diǎn)擊秒殺按鈕,下單請(qǐng)求達(dá)到瞬時(shí)峰值。秒殺后:一部分成功下單的用戶不斷刷新訂單或者產(chǎn)生退單操作,大部分用戶繼續(xù)刷新商品詳情頁等待退單機(jī)會(huì)。消費(fèi)者提交訂單,一般做法是利用數(shù)據(jù)庫的行級(jí)鎖,只有搶到鎖的請(qǐng)求可以進(jìn)行庫存查詢和下單操作。但是在高并發(fā)的情況下,數(shù)據(jù)庫無法承擔(dān)如此大的請(qǐng)求,往往會(huì)使整個(gè)服務(wù)blocked,在消費(fèi)者看來就是服務(wù)器宕機(jī)。
秒殺系統(tǒng)
秒殺系統(tǒng)的流量雖然很高,但是實(shí)際有效流量是十分有限的。利用系統(tǒng)的層次結(jié)構(gòu),在每個(gè)階段提前校驗(yàn),攔截?zé)o效流量,可以減少大量無效的流量涌入數(shù)據(jù)庫。
利用瀏覽器緩存和CDN抗壓靜態(tài)頁面流量
秒殺前,用戶不斷刷新商品詳情頁,造成大量的頁面請(qǐng)求。所以,我們需要把秒殺商品詳情頁與普通的商品詳情頁分開。對(duì)于秒殺商品詳情頁盡量將能靜態(tài)化的元素靜態(tài)化處理,除了秒殺按鈕需要服務(wù)端進(jìn)行動(dòng)態(tài)判斷,其他的靜態(tài)數(shù)據(jù)可以緩存在瀏覽器和CDN上。這樣,秒殺前刷新頁面導(dǎo)致的流量進(jìn)入服務(wù)端的流量只有很小的一部分。
利用讀寫分離Redis緩存攔截流量
CDN是第一級(jí)流量攔截,第二級(jí)流量攔截我們使用支持讀寫分離的Redis。在這一階段我們主要讀取數(shù)據(jù),讀寫分離Redis能支持高達(dá)60萬以上qps,完全可以支持需求。
首先通過數(shù)據(jù)控制模塊,提前將秒殺商品緩存到讀寫分離Redis,并設(shè)置秒殺開始標(biāo)記如下:
"goodsId_count": 100 //總數(shù)
"goodsId_start": 0 //開始標(biāo)記
"goodsId_access": 0 //接受下單數(shù)
秒殺開始前,服務(wù)集群讀取goodsId_Start為0,直接返回未開始。數(shù)據(jù)控制模塊將goodsId_start改為1,標(biāo)志秒殺開始。服務(wù)集群緩存開始標(biāo)記位并開始接受請(qǐng)求,并記錄到Redis中g(shù)oodsId_access,商品剩余數(shù)量為(goodsId_count - goodsId_access)。當(dāng)接受下單數(shù)達(dá)到goodsId_count后,繼續(xù)攔截所有請(qǐng)求,商品剩余數(shù)量為0。可以看出,最后成功參與下單的請(qǐng)求只有少部分可以被接受。在高并發(fā)的情況下,允許稍微多的流量進(jìn)入。因此可以控制接受下單數(shù)的比例。
利用主從版Redis緩存加速庫存扣量
成功參與下單后,進(jìn)入下層服務(wù),開始進(jìn)行訂單信息校驗(yàn),庫存扣量。為了避免直接訪問數(shù)據(jù)庫,我們使用主從版Redis來進(jìn)行庫存扣量,主從版Redis提供10萬級(jí)別的QPS。使用Redis來優(yōu)化庫存查詢,提前攔截秒殺失敗的請(qǐng)求,將大大提高系統(tǒng)的整體吞吐量。
通過數(shù)據(jù)控制模塊提前將庫存存入Redis,將每個(gè)秒殺商品在Redis中用一個(gè)hash結(jié)構(gòu)表示。
"goodsId" : {
"Total": 100
"Booked": 100
}
扣量時(shí),服務(wù)器通過請(qǐng)求Redis獲取下單資格,通過以下lua腳本實(shí)現(xiàn),由于Redis是單線程模型,lua可以保證多個(gè)命令的原子性。
local n = tonumber(ARGV[1])
if not n or n == 0 then
return 0
end
local vals = redis.call("HMGET", KEYS[1], "Total", "Booked");
local total = tonumber(vals[1])
local blocked = tonumber(vals[2])
if not total or not blocked then
return 0
end
if blocked n <= total then
redis.call("HINCRBY", KEYS[1], "Booked", n)
return n;
end
return 0
先使用SCRIPT LOAD將lua腳本提前緩存在Redis,然后調(diào)用EVALSHA調(diào)用腳本,比直接調(diào)用EVAL節(jié)省網(wǎng)絡(luò)帶寬:
redis 127.0.0.1:6379>SCRIPT LOAD "lua code"
"438dd755f3fe0d32771753eb57f075b18fed7716"
redis 127.0.0.1:6379>EVALSHA 438dd755f3fe0d32771753eb57f075b18fed7716 1 goodsId 1
秒殺服務(wù)通過判斷Redis是否返回?fù)屬?gòu)個(gè)數(shù)n,即可知道此次請(qǐng)求是否扣量成功。
使用主從版Redis實(shí)現(xiàn)簡(jiǎn)單的消息隊(duì)列異步下單入庫
扣量完成后,需要進(jìn)行訂單入庫。如果商品數(shù)量較少的時(shí)候,直接操作數(shù)據(jù)庫即可。如果秒殺的商品是1萬,甚至10萬級(jí)別,那數(shù)據(jù)庫鎖沖突將帶來很大的性能瓶頸。因此,利用消息隊(duì)列組件,當(dāng)秒殺服務(wù)將訂單信息寫入消息隊(duì)列后,即可認(rèn)為下單完成,避免直接操作數(shù)據(jù)庫。
消息隊(duì)列組件依然可以使用Redis實(shí)現(xiàn),在R2中用list數(shù)據(jù)結(jié)構(gòu)表示。orderList {[0] = {訂單內(nèi)容}[1] = {訂單內(nèi)容}[2] = {訂單內(nèi)容}...}將訂單內(nèi)容寫入Redis:LPUSH orderList {訂單內(nèi)容}異步下單模塊從Redis中順序獲取訂單信息,并將訂單寫入數(shù)據(jù)庫。BRPOP orderList 0通過使用Redis作為消息隊(duì)列,異步處理訂單入庫,有效的提高了用戶的下單完成速度。
數(shù)據(jù)控制模塊管理秒殺數(shù)據(jù)同步
最開始,利用讀寫分離Redis進(jìn)行流量限制,只讓部分流量進(jìn)入下單。對(duì)于下單檢驗(yàn)失敗和退單等情況,需要讓更多的流量進(jìn)來。因此,數(shù)據(jù)控制模塊需要定時(shí)將數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行一定的計(jì)算,同步到主從版Redis,同時(shí)再同步到讀寫分離的Redis,讓更多的流量進(jìn)來
相關(guān)文章
- 跨境電商ERP系統(tǒng)的功能「跨境獨(dú)立建站」
- 爭(zhēng)流品牌策劃有限公司「如何品牌化」
- vergamot品牌「英特爾T5750」
- 網(wǎng)店代運(yùn)營(yíng)服務(wù)內(nèi)容都有哪些服務(wù)全面很省心「運(yùn)營(yíng)客戶所有服務(wù)內(nèi)容」
- 農(nóng)村電商多渠道發(fā)展這個(gè)渠道一定要重視嗎「鄉(xiāng)村電商如何開展」
- 新零售生態(tài)圈的構(gòu)建「跨境電商生態(tài)圈」
- 你感興趣的跨境發(fā)展史是什么「跨境電商的發(fā)展現(xiàn)狀」
- 網(wǎng)店如何快速進(jìn)行推廣有哪些運(yùn)營(yíng)的技巧需要把握「開網(wǎng)店怎樣推廣」
- 帶獨(dú)顯的顯示器「組裝電腦和品牌電腦的區(qū)別」
- 線上買白酒「電商達(dá)人榜」
- 電商防止訂單重復(fù)提交「下單頻繁怎么破解」
- 洋河酒活動(dòng)「洋河南吧」
- \\「你的事業(yè)」
- 電商平臺(tái)的白酒為什么這么便宜「淘寶網(wǎng)上的白酒怎么那么便宜」
- oem定制酒「酒的水貨和公司貨」
- 為什么很多商家喜歡小程序商城的分銷模式「小程序商城的分銷模式」
- 酒類市場(chǎng)份額「2017年gdp」
- 為什么所有平臺(tái)都想帶貨「為什么要直播帶貨」