秒殺專題-系統(tǒng)的設(shè)計(jì)(一)

秒殺專題-系統(tǒng)的設(shè)計(jì)(一)

觀察從客戶端請(qǐng)求訪問到服務(wù)器择诈,整個(gè)過程經(jīng)歷了 從服務(wù)器網(wǎng)關(guān)->代碼(Service層)->數(shù)據(jù)庫(kù)

根據(jù)木桶理論病曾,整個(gè)訪問的速度取決于系統(tǒng)中響應(yīng)速度最慢的地方挖腰。而訪問數(shù)據(jù)庫(kù)是內(nèi)存對(duì)磁盤進(jìn)行IO,是系統(tǒng)中效率速度最低的地方,同時(shí)數(shù)據(jù)庫(kù)所支持的QPS也是最小的物赶,所有系統(tǒng)在大并發(fā)時(shí)數(shù)據(jù)庫(kù)是最容易崩潰的橡庞。

因此所有對(duì)并發(fā)秒殺的優(yōu)化核心在于如何減少全部請(qǐng)求直接對(duì)數(shù)據(jù)庫(kù)的訪問较坛,全部思路和技術(shù)也是圍繞此展開

簡(jiǎn)單優(yōu)化思路如下:

  1. 將數(shù)據(jù)放到Redis中,也就是放到內(nèi)存中扒最,提高查詢的效率丑勤,而不去直接訪問DB,對(duì)于已經(jīng)秒殺完的產(chǎn)品可以直接全部返回吧趣。絕大部分秒殺失敗的請(qǐng)求都被Redis擋住法竞,快速做了處理。

  2. 使用MQ强挫,將Redis放進(jìn)處理程序的請(qǐng)求進(jìn)行異步處理岔霸,直接對(duì)用戶進(jìn)行返回而不等待同步處理完成。提高了對(duì)用戶的響應(yīng)速度俯渤,但并沒有減少整體的處理時(shí)間呆细,因?yàn)榈綄?shí)際處理的代碼還是同步在操作數(shù)據(jù)庫(kù)(創(chuàng)建訂單,減庫(kù)存)八匠。

  3. 前端的緩存絮爷,頁(yè)面緩存,減少對(duì)刷新頁(yè)面服務(wù)器的請(qǐng)求

總結(jié)上面的思路就得到了如下處理方式:

把所有可以緩存的東西緩存起來

  • 用戶登錄:用戶第一次登錄是攜帶賬號(hào)梨树,密碼進(jìn)行登錄的坑夯,必須要查詢一次數(shù)據(jù)庫(kù)。第一次之后就用token存到用戶cookie中進(jìn)行登錄抡四,但是這個(gè)token如果寫到DB中那么之后的登錄即使是檢查token登陸也要訪問DB柜蜈,這就是需要避免的。所以可以將用戶對(duì)象存儲(chǔ)起來床嫌,(使用JSON提供的功能跨释,對(duì)象可以被序列化也可以通過字節(jié)碼反序列化變成對(duì)象),那么之后用戶再登錄直接通過token就能在redis中找到用戶對(duì)象進(jìn)行使用

  • 秒殺商品的庫(kù)存信息:顯然每次秒殺后庫(kù)存是減少了的厌处,但不應(yīng)該立即就去MySQL中進(jìn)行修改鳖谈,那樣又會(huì)直接訪問DB,這些信息同樣也是緩存在redis中阔涉。

  • 秒殺商品的訂單信息:用戶秒殺之后生成了對(duì)應(yīng)的訂單用來組織用戶再次秒殺缆娃,那么顯然成功秒殺的訂單應(yīng)該存在于redis中捷绒。

  • 秒殺是否已經(jīng)結(jié)束:正常來說用戶每次訪問都會(huì)先去redis中查詢庫(kù)存嘗試減庫(kù)存,但是考慮到redis也是通過網(wǎng)絡(luò)提供服務(wù)贯要,所以對(duì)于秒殺是否還在進(jìn)行這種信息(不需要入庫(kù)的信息)可以直接放在本地內(nèi)存中(使用內(nèi)存標(biāo)記)其實(shí)就是使用一個(gè)數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)特定商品的秒殺狀態(tài)暖侨。

單機(jī)優(yōu)化思路

  1. 增加redis緩存,在Redis中減庫(kù)存崇渗。所有請(qǐng)求都會(huì)過redis字逗,只有成功減庫(kù)存的才會(huì)進(jìn)行MySQL。減少了 除秒殺成功之外的請(qǐng)求宅广,增加了全部請(qǐng)求對(duì)Redis的訪問葫掉。

  2. 使用內(nèi)存標(biāo)記,在庫(kù)存已經(jīng)減完的情況下不再去訪問Redis跟狱,請(qǐng)求redis也算網(wǎng)絡(luò)開銷了俭厚,內(nèi)存中就是JVM可以直接訪問到,速度最快驶臊。只有在內(nèi)存標(biāo)記被置為售完之前的請(qǐng)求挪挤,(瞬間并發(fā)沖進(jìn)來的那一部分請(qǐng)求)會(huì)訪問redis,修改完內(nèi)存標(biāo)記后关翎,剩下的請(qǐng)求不會(huì)訪問redis了扛门。

  3. 使用MQ提升用戶體驗(yàn)。首先MQ將創(chuàng)建訂單和MySQL中真實(shí)減庫(kù)存的操作去異步處理纵寝,但是這一步是沒有提升效率的尖飞,因?yàn)樵炯词共l(fā)去執(zhí)行操作MySQL,也是線程安全的(而且因?yàn)镽edis保證了進(jìn)來的線程均是秒殺成功的線程)而且是串行執(zhí)行的店雅,放到MQ中仍然是串行執(zhí)行(執(zhí)行的線程數(shù)也一樣)。但是區(qū)別在于整個(gè)串行執(zhí)行過程中贞铣,所有秒殺到商品的線程是在阻塞等待去操作MySQL(操作同一行的會(huì)阻塞闹啦,也就是減庫(kù)存),客戶端的請(qǐng)求也就阻塞了辕坝,而異步可以馬上給用戶一個(gè)反饋窍奋,并讓客戶端再進(jìn)行定時(shí)來請(qǐng)求結(jié)果(結(jié)果是存在Redis中的)。那么這樣酱畅,原本阻塞到減庫(kù)存和創(chuàng)建訂單全部成功的長(zhǎng)請(qǐng)求琳袄,被分割成了兩段,第一次請(qǐng)求可以快速響應(yīng)纺酸,第二段是連續(xù)多段的緩存訪問窖逗,阻塞DB->快速響應(yīng)查詢結(jié)果(redis)餐蔬,(DB服務(wù)被MQ去執(zhí)行了).

我們可以得到目前的系統(tǒng)鏈路圖大致如下:


秒殺系統(tǒng).png

可以看到碎紊,目前為止整個(gè)系統(tǒng)對(duì)于DB的沖擊已經(jīng)十分小了佑附。單機(jī)的QPS就差不多這樣了。但是這個(gè)系統(tǒng)仍然還有其他非常多值得討論的細(xì)節(jié)仗考。

但是后端還有一些需要進(jìn)行處理的問題音同,比如超賣&重復(fù)秒殺

超賣&重復(fù)秒殺

這個(gè)算是最好解決的問題了。超賣問題出現(xiàn)在程序直接去檢查MySQL中的庫(kù)存來作為庫(kù)存是否充足的判斷標(biāo)準(zhǔn)秃嗜,但實(shí)際上一個(gè)服務(wù)線程的運(yùn)行流程是:檢查庫(kù)存->減少庫(kù)存权均。這中間至少包含了兩步,一定不是原子性的操作锅锨,而導(dǎo)致了多個(gè)服務(wù)線程可以減少同一份庫(kù)存叽赊。

只需要直接操作Redis中的緩存就可以了,無論多少個(gè)線程都是被Redis單線程執(zhí)行的橡类,每個(gè)線程的操作結(jié)果一定正確蛇尚。而之后只需要判斷操作結(jié)果是否大于等于0即可,線程就安全了顾画,代碼如下:

 @RequestMapping("/seckill/{id}")
    public Result SecKill(@PathVariable("id") String secId, HttpServletRequest request){
        //獲取登錄用戶
        User user = secKillService.getLoginUser(request);
        if(user==null){
            return ResultUtil.error(CodeMsgUtil.USER_NOT_LOGIN);
        }
        //加內(nèi)存標(biāo)記
        if(proMap.containsKey(secId)&&proMap.get(secId)){
            return ResultUtil.error(CodeMsgUtil.SEC_SOLD_OUT);
        }
        //預(yù)減庫(kù)存
        Long remain = redisService.decr(secId);     //redis是安全的
        if(remain < 0){
            proMap.put(secId, true);
            //不能秒殺的歸還庫(kù)存
            redisService.incr(secId);
            return ResultUtil.error(CodeMsgUtil.SEC_SOLD_OUT);
        }
        //到這里多少并發(fā)的線程都是線程安全的了

如果一定要檢查MySQL去看庫(kù)存數(shù)量取劫,那么在sql語(yǔ)句中加上對(duì)庫(kù)存數(shù)量≥0的限制就可以了,這樣會(huì)有很多的線程嘗試去減庫(kù)存研侣,但只有等于秒殺商品數(shù)量的線程可以成功減庫(kù)存谱邪。因?yàn)?code>MySQL中對(duì)同一行數(shù)據(jù)的操作(同一件商品的庫(kù)存信息)是加了行鎖的,所以在這里也變成了線程安全的操作庶诡。

<update id="SecKillGoods">
        update seckill_goods_list
        set stock_count = stock_count - 1
        where good_id = #{secId} and stock_count > 0
    </update>

而對(duì)于重復(fù)秒殺而已惦银,訂單在MQ中處理完成之后會(huì)寫入到Redis中,用于之后用戶再次秒殺時(shí)阻止末誓。但MQ處理訂單扯俱,到寫入Redis中間有相當(dāng)長(zhǎng)的一段時(shí)間,可能此時(shí)用戶已經(jīng)再次進(jìn)來秒殺喇澡。所以這里存在的情況是:任務(wù)剛進(jìn)MQ隊(duì)列迅栅,還沒有寫MySQL也沒有寫Redis,所以此時(shí)去RedisMySQL中檢查是都無法得到訂單信息的晴玖。

這里考慮一種在訂單表中通過用戶id商品id建立唯一索引(用戶和秒殺的商品聯(lián)系起來)的方法读存,MySQL自身的特性會(huì)阻止第一個(gè)訂單之后的訂單寫入,那么這樣MQ最終在創(chuàng)建重復(fù)訂單時(shí)就會(huì)失敗呕屎,重復(fù)秒殺就不可能了让簿。

當(dāng)然還可以利用Redis在緩存中多記錄一些信息來實(shí)現(xiàn),充分利用Redis的單線程特性秀睛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尔当,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蹂安,更是在濱河造成了極大的恐慌居凶,老刑警劉巖虫给,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異侠碧,居然都是意外死亡抹估,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門弄兜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來药蜻,“玉大人,你說我怎么就攤上這事替饿∮镌螅” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵视卢,是天一觀的道長(zhǎng)踱卵。 經(jīng)常有香客問我,道長(zhǎng)据过,這世上最難降的妖魔是什么惋砂? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮绳锅,結(jié)果婚禮上西饵,老公的妹妹穿的比我還像新娘。我一直安慰自己鳞芙,他們只是感情好眷柔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著原朝,像睡著了一般驯嘱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喳坠,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天宙拉,我揣著相機(jī)與錄音,去河邊找鬼丙笋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛煌贴,可吹牛的內(nèi)容都是我干的御板。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼牛郑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼怠肋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淹朋,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤笙各,失蹤者是張志新(化名)和其女友劉穎钉答,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杈抢,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡数尿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惶楼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片右蹦。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歼捐,靈堂內(nèi)的尸體忽然破棺而出何陆,到底是詐尸還是另有隱情,我是刑警寧澤豹储,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布贷盲,位于F島的核電站,受9級(jí)特大地震影響剥扣,放射性物質(zhì)發(fā)生泄漏巩剖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一朦乏、第九天 我趴在偏房一處隱蔽的房頂上張望球及。 院中可真熱鬧,春花似錦呻疹、人聲如沸吃引。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)镊尺。三九已至,卻和暖如春并思,著一層夾襖步出監(jiān)牢的瞬間庐氮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工宋彼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弄砍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓输涕,卻偏偏與公主長(zhǎng)得像音婶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子莱坎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348