1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        基于 SpringBoot 實(shí)現(xiàn)商城限時(shí)秒殺系統(tǒng)項(xiàng)目

        共 5569字,需瀏覽 12分鐘

         ·

        2022-01-16 23:06

        這兩天,拼多多上了頭條。因?yàn)?,拼多多的砍價(jià)細(xì)則被公開了,后面居然有6位小數(shù)。

        基于熱點(diǎn),我想到了一個(gè)秒殺系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn),今天分享給大家!

        「在高并發(fā)情況下的秒殺優(yōu)化,我們知道當(dāng)并發(fā)數(shù)達(dá)到一定量的時(shí)候,會(huì)對(duì)數(shù)據(jù)庫服務(wù)器帶來很大的壓力,那么如何緩解這些壓力以及提高并發(fā)的 QPS 就是整個(gè)項(xiàng)目的解決重點(diǎn),也是我們優(yōu)化系統(tǒng)的目標(biāo)?!?/strong>

        高并發(fā)也經(jīng)常是我們面試中比問的知識(shí)點(diǎn),最近我基于所學(xué)的知識(shí),實(shí)現(xiàn)了一個(gè)秒殺系統(tǒng)。目前該系統(tǒng)擁有以下功能:

        • 接口限流防刷
        • 數(shù)學(xué)圖形驗(yàn)證碼
        • 秒殺接口地址隱藏
        • RabbitMQ異步下單
        • 秒殺靜態(tài)化 + 訂單詳情靜態(tài)化
        • 商品詳情頁面靜態(tài)化(前后端分離)
        • 頁面緩存 + URL緩存 + 對(duì)象緩存
        • 使用JMeter壓測秒殺系統(tǒng)(秒殺接口的壓測及結(jié)果)
        • 秒殺商品詳情頁 + 秒殺倒計(jì)時(shí)功能實(shí)現(xiàn)
        • 分布式Session
        • JSR303參數(shù)校驗(yàn) + 全局異常處理
        • 多次 MD5 加密設(shè)計(jì)與登錄實(shí)現(xiàn)
        • 通用緩存 Key 的設(shè)計(jì)與封裝
        • 緩存擊穿和緩存雪崩以及緩存一致性等
        • 。。。

        項(xiàng)目的亮點(diǎn):

        1. 使用分布式 Seesion,可以實(shí)現(xiàn)讓多臺(tái)服務(wù)器同時(shí)可以響應(yīng)。
        2. 使用 redis 做緩存提高訪問速度和并發(fā)量,減少數(shù)據(jù)庫壓力,利用內(nèi)存標(biāo)記減少 redis 的訪問。
        3. 使用頁面靜態(tài)化,加快用戶訪問速度,提高 QPS,緩存頁面至瀏覽器,前后端分離降低服務(wù)器壓力。
        4. 使用消息隊(duì)列完成異步下單,提升用戶體驗(yàn),削峰和降流。
        5. 安全性優(yōu)化:雙重 md5 密碼校驗(yàn),秒殺接口地址的隱藏,接口限流防刷,數(shù)學(xué)公式驗(yàn)證碼。

        「主要知識(shí)點(diǎn):」

        • 「分布式Seesion」

          我們的秒殺服務(wù),實(shí)際的應(yīng)用可能不止部署在一個(gè)服務(wù)器上,而是分布式的多臺(tái)服務(wù)器,這時(shí)候假如用戶登錄是在第一個(gè)服務(wù)器,第一個(gè)請(qǐng)求到了第一臺(tái)服務(wù)器,但是第二個(gè)請(qǐng)求到了第二個(gè)服務(wù)器,那么用戶的session信息就丟失了。

          解決:session同步,無論訪問那一臺(tái)服務(wù)器,session都可以取得到,利用「redis緩存的方法,另外使用一個(gè)redis服務(wù)器專門用于存放用戶的session信息。這樣就不會(huì)出現(xiàn)用戶session丟失的情況」。(每次需要session,從緩存中取即可)

        • 「redis緩解數(shù)據(jù)庫壓力」

          本項(xiàng)目大量的利用了緩存技術(shù),包括用戶信息緩存(分布式session),商品信息的緩存,商品庫存緩存,訂單的緩存,頁面緩存,對(duì)象緩存減少了對(duì)數(shù)據(jù)庫服務(wù)器的訪問。

        • 「通用緩存key封裝」

          大量的緩存引用也出現(xiàn)了一個(gè)問題,如何識(shí)別不同模塊中的緩存(key值重復(fù),如何辨別是不同模塊的key)

          解決:利用一個(gè)抽象類,定義BaseKey(前綴),在里面定義緩存key的前綴以及緩存的過期時(shí)間從而實(shí)現(xiàn)將緩存的key進(jìn)行封裝。讓不同模塊繼承它,這樣每次存入一個(gè)模塊的緩存的時(shí)候,加上這個(gè)緩存特定的前綴,以及可以統(tǒng)一制定不同的過期時(shí)間。

        • 「頁面靜態(tài)化(前后端分離)」
          頁面靜態(tài)化的主要目的是為了加快頁面的加載速度,將商品的詳情和訂單詳情頁面做成靜態(tài)HTML(純的HTML),數(shù)據(jù)的加載只需要通過ajax來請(qǐng)求服務(wù)器,并且做了靜態(tài)化HTML頁面可以緩存在客戶端的瀏覽器。

        • 「消息隊(duì)列完成異步下單」

          使用消息隊(duì)列完成異步下單,提升用戶體驗(yàn),削峰和降流

          「思路:」

          1. 系統(tǒng)初始化,把商品庫存數(shù)量stock加載到Redis上面來。
          2. 后端收到秒殺請(qǐng)求,Redis預(yù)減庫存,如果庫存已經(jīng)到達(dá)臨界值的時(shí)候,就不需要繼續(xù)請(qǐng)求下去,直接返回失敗,即后面的大量請(qǐng)求無需給系統(tǒng)帶來壓力。
          3. 判斷這個(gè)秒殺訂單形成沒有,判斷是否已經(jīng)秒殺到了,避免一個(gè)賬戶秒殺多個(gè)商品,判斷是否重復(fù)秒殺。
          4. 庫存充足,且無重復(fù)秒殺,將秒殺請(qǐng)求封裝后消息入隊(duì),同時(shí)給前端返回一個(gè)code (0),即代表返回排隊(duì)中。(返回的并不是失敗或者成功,此時(shí)還不能判斷)
          5. 前端接收到數(shù)據(jù)后,顯示排隊(duì)中,并根據(jù)商品id輪詢請(qǐng)求服務(wù)器(考慮200ms輪詢一次)。
          6. 后端RabbitMQ監(jiān)聽秒殺MIAOSHA_QUEUE的這名字的通道,如果有消息過來,獲取到傳入的信息,執(zhí)行真正的秒殺之前,要判斷數(shù)據(jù)庫的庫存,判斷是否重復(fù)秒殺,然后執(zhí)行秒殺事務(wù)(秒殺事務(wù)是一個(gè)原子操作:庫存減1,下訂單,寫入秒殺訂單)。
          7. 此時(shí),前端根據(jù)商品id輪詢請(qǐng)求接口MiaoshaResult,查看是否生成了商品訂單,如果請(qǐng)求返回-1代表秒殺失敗,返回0代表排隊(duì)中,返回>0代表商品id說明秒殺成功。
        • 「安全性優(yōu)化」

          雙重md5密碼校驗(yàn),秒殺接口地址的隱藏,接口限流防刷,數(shù)學(xué)公式驗(yàn)證碼。

        • 「優(yōu)雅的代碼編寫」

          接口的輸出結(jié)果做了一個(gè)Result封裝

          對(duì)錯(cuò)誤的代碼做了一個(gè)CodeMsg的封裝

          訪問緩存做了一個(gè)key的封裝

        項(xiàng)目難點(diǎn)及問題解決

        壓測

        使用JMeter做壓測的時(shí)候開啟5000個(gè)線程,系統(tǒng)跑不起來,出現(xiàn)異常。

        原因:修改配置文件中 redis 的配置項(xiàng) poolMaxTotal 將其設(shè)置成 1000。

        #redis配置項(xiàng)
        redis.poolMaxTotal=1000
        redis.poolMaxldle=500
        redis.poolMaxWait=500

        緩存一致性

        使用了大量緩存,那么就存在緩存擊穿和緩存雪崩以及緩存一致性等問題?

        • 「緩存穿透指的是對(duì)某個(gè)一定不存在的數(shù)據(jù)進(jìn)行請(qǐng)求,該請(qǐng)求將會(huì)穿透緩存到達(dá)數(shù)據(jù)庫?!?/strong>
          解決方案:對(duì)這些不存在的數(shù)據(jù)緩存一個(gè)空數(shù)據(jù),對(duì)這類請(qǐng)求進(jìn)行過濾。

        • 「緩存雪崩指的是由于數(shù)據(jù)沒有被加載到緩存中,或者緩存數(shù)據(jù)在同一時(shí)間大面積失效(過期),又或者緩存服務(wù)器宕機(jī),導(dǎo)致大量的請(qǐng)求都到達(dá)數(shù)據(jù)庫?!?/strong>

          解決方案:

          為了防止緩存在同一時(shí)間大面積過期導(dǎo)致的緩存雪崩,可以通過觀察用戶行為,合理設(shè)置緩存過期時(shí)間來實(shí)現(xiàn);

          為了防止緩存服務(wù)器宕機(jī)出現(xiàn)的緩存雪崩,可以使用分布式緩存,分布式緩存中每一個(gè)節(jié)點(diǎn)只緩存部分的數(shù)據(jù),當(dāng)某個(gè)節(jié)點(diǎn)宕機(jī)時(shí)可以保證其它節(jié)點(diǎn)的緩存仍然可用。

          也可以進(jìn)行緩存預(yù)熱,避免在系統(tǒng)剛啟動(dòng)不久由于還未將大量數(shù)據(jù)進(jìn)行緩存而導(dǎo)致緩存雪崩。

          例如:首先針對(duì)不同的緩存設(shè)置不同的過期時(shí)間,比如session緩存,在userKey這個(gè)前綴中,設(shè)置是30分鐘過期,并且每次用戶響應(yīng)的話更新緩存時(shí)間。這樣每次取session,都會(huì)延長30分鐘,相對(duì)來說,就減少了緩存過期的幾率

        • 「緩存一致性要求數(shù)據(jù)更新的同時(shí)緩存數(shù)據(jù)也能夠?qū)崟r(shí)更新?!?/strong>

          解決方案:

          在數(shù)據(jù)更新的同時(shí)立即去更新緩存,「首先嘗試從緩存讀取,讀到數(shù)據(jù)則直接返回;如果讀不到,就讀數(shù)據(jù)庫,并將數(shù)據(jù)會(huì)寫到緩存,并返回?!?/strong>

          在讀緩存之前先判斷緩存是否是最新的,如果不是最新的先進(jìn)行更新,「需要更新數(shù)據(jù)時(shí),先更新數(shù)據(jù)庫,然后把緩存里對(duì)應(yīng)的數(shù)據(jù)失效掉(刪掉)?!?/strong>

        減少redis的訪問

        大量的使用緩存,對(duì)于緩存服務(wù)器,也有很大的壓力,思考如何減少redis的訪問?

        在redis預(yù)減庫存的時(shí)候,內(nèi)存中維護(hù)一個(gè)isOvermap作為一個(gè)內(nèi)存標(biāo)記,當(dāng)沒有庫存的時(shí)候,將其置為true。每次秒殺業(yè)務(wù)訪問redis之前,查一下map標(biāo)記,如果true說明沒有庫存,就直接返回失敗,無需再去請(qǐng)求redis服務(wù)器。

        請(qǐng)求堆積

        在高并發(fā)請(qǐng)求的業(yè)務(wù)場景,大量請(qǐng)求來不及處理,甚至出現(xiàn)請(qǐng)求堆積時(shí)候?

        消息隊(duì)列,用來異步處理請(qǐng)求。每次請(qǐng)求過來,先不去處理請(qǐng)求,而是放入消息隊(duì)列,然后在后臺(tái)布置一個(gè)監(jiān)聽器,分別監(jiān)聽不同業(yè)務(wù)的消息隊(duì)列,有消息來的時(shí)候,才進(jìn)行秒殺業(yè)務(wù)邏輯。這樣防止多個(gè)請(qǐng)求同時(shí)操作的時(shí)候,數(shù)據(jù)庫連接過多的異常。

        重復(fù)下單

        怎么保證一個(gè)用戶不能重復(fù)下單?

        解決:秒殺訂單表中建立一個(gè)唯一索引(所引是用戶Id與商品goodsId),使得第一個(gè)記錄可以插入,第二個(gè)則出錯(cuò),然后通過事務(wù)回滾,防止一個(gè)用戶同時(shí)發(fā)出多個(gè)請(qǐng)求的處理,秒殺到多個(gè)商品。

        唯一索引,即是唯一的意思,在數(shù)據(jù)庫表結(jié)構(gòu)中對(duì)字段添加唯一索引后進(jìn)行數(shù)據(jù)庫進(jìn)行存儲(chǔ)操作時(shí)數(shù)據(jù)庫會(huì)判斷庫中是否已經(jīng)存在此數(shù)據(jù),不存在此數(shù)據(jù)時(shí)才能進(jìn)行插入操作。

        這雖然是個(gè)小技能,但實(shí)際上在業(yè)務(wù)開發(fā)中是個(gè)很實(shí)用的技能,比如在高并發(fā)業(yè)務(wù)中,數(shù)據(jù)庫「如何杜絕數(shù)據(jù)并發(fā)插入兩條相同的訂單號(hào)呢?添加一個(gè)唯一索引」當(dāng)然是最快捷的方法之一,當(dāng)然是添加索引還是通過業(yè)務(wù)代碼去解決因公司業(yè)務(wù)而定

        超賣

        怎么解決超賣現(xiàn)象?

        超賣場景:不同用戶在讀請(qǐng)求的時(shí)候,發(fā)現(xiàn)商品庫存足夠,然后同時(shí)發(fā)起請(qǐng)求,進(jìn)行秒殺操作,減庫存,導(dǎo)致庫存減為負(fù)數(shù)。

        最簡單的方法,更新數(shù)據(jù)庫減庫存的時(shí)候,進(jìn)行庫存限制條件,在reduceStock(GoodsVo goodsvo)這個(gè)方法里,sql要多加一個(gè)stock_count > 0 ,使用數(shù)據(jù)庫特性來保證超賣的問題,只有stock_count還大于0的時(shí)候才去讀stock_count然后減1操作

        @Update("update?miaosha_goods?set?stock_count=stock_count-1?where?goods_id=#{goodsId}?and?stock_count>0")
        ?public?void?reduceStock(MiaoshaGoods?goods);??

        頁面靜態(tài)化

        頁面靜態(tài)化的過程及什么是瀏覽器緩存?

        將HTML靜態(tài)頁面緩存在客戶端瀏覽器,只有數(shù)據(jù)通過ajax異步調(diào)用接口來獲取,僅僅交互的是部分?jǐn)?shù)據(jù),減少了帶寬,也加快用戶訪問的速度。

        瀏覽器緩存就是把一個(gè)已經(jīng)請(qǐng)求過的Web資源(如html頁面,圖片,js,數(shù)據(jù)等)拷貝一份副本儲(chǔ)存在瀏覽器中。緩存會(huì)根據(jù)進(jìn)來的請(qǐng)求保存輸出內(nèi)容的副本。當(dāng)下一個(gè)請(qǐng)求來到的時(shí)候,如果是相同的URL,緩存會(huì)根據(jù)緩存機(jī)制決定是直接使用副本響應(yīng)訪問請(qǐng)求,還是向源服務(wù)器再次發(fā)送請(qǐng)求。比較常見的就是瀏覽器會(huì)緩存訪問過網(wǎng)站的網(wǎng)頁,當(dāng)再次訪問這個(gè)URL地址的時(shí)候,如果網(wǎng)頁沒有更新,就不會(huì)再次下載網(wǎng)頁,而是直接使用本地緩存的網(wǎng)頁。只有當(dāng)網(wǎng)站明確標(biāo)識(shí)資源已經(jīng)更新,瀏覽器才會(huì)再次下載網(wǎng)頁。

        秒殺架構(gòu)設(shè)計(jì)理念?

        • 限流:鑒于只有少部分用戶能夠秒殺成功,所以要限制大部分流量,只允許少部分流量進(jìn)入服務(wù)后端。

        • 削峰:對(duì)于秒殺系統(tǒng)瞬時(shí)會(huì)有大量用戶涌入,所以在搶購一開始會(huì)有很高的瞬間峰值。高峰值流量是壓垮系統(tǒng)很重要的原因,所以如何把瞬間的高流量變成一段時(shí)間平穩(wěn)的流量也是設(shè)計(jì)秒殺系統(tǒng)很重要的思路。實(shí)現(xiàn)削峰的常用的方法有利用緩存和消息中間件等技術(shù)。

        • 異步處理:秒殺系統(tǒng)是一個(gè)高并發(fā)系統(tǒng),采用異步處理模式可以極大地提高系統(tǒng)并發(fā)量,其實(shí)異步處理就是削峰的一種實(shí)現(xiàn)方式。

        • 內(nèi)存緩存:秒殺系統(tǒng)最大的瓶頸一般都是數(shù)據(jù)庫讀寫,由于數(shù)據(jù)庫讀寫屬于磁盤IO,性能很低,如果能夠把部分?jǐn)?shù)據(jù)或業(yè)務(wù)邏輯轉(zhuǎn)移到內(nèi)存緩存,效率會(huì)有極大地提升。

        • 可拓展:當(dāng)然如果我們想支持更多用戶,更大的并發(fā),最好就將系統(tǒng)設(shè)計(jì)成彈性可拓展的,如果流量來了,拓展機(jī)器就好了。像淘寶、京東等雙十一活動(dòng)時(shí)會(huì)增加大量機(jī)器應(yīng)對(duì)交易高峰。

        秒殺系統(tǒng)是怎么設(shè)計(jì)的?

        • 「將請(qǐng)求攔截在系統(tǒng)上游,降低下游壓力:秒殺系統(tǒng)特點(diǎn)是并發(fā)量極大,但實(shí)際秒殺成功的請(qǐng)求數(shù)量卻很少,所以如果不在前端攔截很可能造成數(shù)據(jù)庫讀寫鎖沖突,最終請(qǐng)求超時(shí)?!?/strong>
        • 利用緩存:利用緩存可極大提高系統(tǒng)讀寫速度。
        • 消息隊(duì)列:消息隊(duì)列可以削峰,將攔截大量并發(fā)請(qǐng)求,這也是一個(gè)異步處理過程,后臺(tái)業(yè)務(wù)根據(jù)自己的處理能力,從消息隊(duì)列中主動(dòng)的拉取請(qǐng)求消息進(jìn)行業(yè)務(wù)處理。
        秒殺系統(tǒng)是怎么設(shè)計(jì)

        超時(shí)未支付

        假如減了庫存用戶沒有支付,庫存怎么還原繼續(xù)參加搶購?

        設(shè)定一個(gè)最長付款時(shí)間,比如30分鐘,后臺(tái)有個(gè)定時(shí)任務(wù)(使用定時(shí)器Timer),輪訓(xùn)超過30分鐘的待付款訂單(數(shù)據(jù)庫里面判定訂單狀態(tài)),然后關(guān)閉訂單,恢復(fù)庫存。

        開發(fā)工具

        IntelliJ IDEA 2017.3.1 x64。

        開發(fā)環(huán)境

        JDKMavenMysqlSpringBootredisRabbitMQ
        1.83.2.25.51.5.9.RELEASE3.23.7.14

        使用說明

        1. 下載百度網(wǎng)盤源碼(鏈接: https://pan.baidu.com/s/1pYQs5RYvgqdfVM6q-8JExg?pwd=3xhp若連接過期,加我微信:xttblog2獲取補(bǔ)充鏈接),將項(xiàng)目下載到 IDEA 里面
        2. 運(yùn)行 sql 文件夾下的sql文件
        3. 到src/main/resources下的application.properties下修改你的數(shù)據(jù)庫鏈接用戶名與密碼
        4. 安裝redis、mysql、rabbitmq、maven等環(huán)境
        5. 啟動(dòng)前,檢查配置 application.properties 中相關(guān)redis、mysql、rabbitmq地址
        6. 在數(shù)據(jù)庫秒殺商品表里面設(shè)置合理的秒殺開始時(shí)間與結(jié)束時(shí)間
        7. 登錄地址:http://localhost:8080/login/to_login
        8. 商品秒殺列表地址:http://localhost:8080/goods/to_list

        其它說明

        1. 數(shù)據(jù)庫共有一千個(gè)用戶左右(手機(jī)號(hào):15200000000~15200000997 密碼為:123456),為壓測準(zhǔn)備的。

        2. 郵箱只實(shí)現(xiàn)了前端格式驗(yàn)證,只需輸入一個(gè)正確的郵箱格式即可(例如:[email protected]

        圖片演示

        登錄頁面

        登錄頁面

        商品列表頁面

        商品列表頁面

        商品詳情頁面

        商品詳情頁面

        商品秒殺倒計(jì)時(shí)

        商品秒殺倒計(jì)時(shí)

        成功秒殺頁面

        成功秒殺頁面

        限于篇幅與時(shí)間,本文就不在貼具體的實(shí)現(xiàn)代碼了。大家通過鏈接: https://pan.baidu.com/s/1pYQs5RYvgqdfVM6q-8JExg?pwd=3xhp下載源碼,自行研究。如若連接過期,加我微信:xttblog2獲取補(bǔ)充鏈接。

        瀏覽 63
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            手机av网站 | 在线成人av | 欧美性受XXXX黑人XYX | 国内久久成人网站地址 | 韩国精品免费一区二区三区 | 天天操夜夜操夜夜 | www.热久久 | 男女扒开双腿猛进入免费直播 | 亚洲小说欧美激情另类A片小说 | 亚洲自拍自偷 |