<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 5569字,需瀏覽 12分鐘

           ·

          2022-01-16 23:06

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

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

          「在高并發(fā)情況下的秒殺優(yōu)化,我們知道當并發(fā)數(shù)達到一定量的時候,會對數(shù)據(jù)庫服務器帶來很大的壓力,那么如何緩解這些壓力以及提高并發(fā)的 QPS 就是整個項目的解決重點,也是我們優(yōu)化系統(tǒng)的目標。」

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

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

          項目的亮點:

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

          「主要知識點:」

          • 「分布式Seesion」

            我們的秒殺服務,實際的應用可能不止部署在一個服務器上,而是分布式的多臺服務器,這時候假如用戶登錄是在第一個服務器,第一個請求到了第一臺服務器,但是第二個請求到了第二個服務器,那么用戶的session信息就丟失了。

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

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

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

          • 「通用緩存key封裝」

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

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

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

          • 「消息隊列完成異步下單」

            使用消息隊列完成異步下單,提升用戶體驗,削峰和降流

            「思路:」

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

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

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

            接口的輸出結果做了一個Result封裝

            對錯誤的代碼做了一個CodeMsg的封裝

            訪問緩存做了一個key的封裝

          項目難點及問題解決

          壓測

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

          原因:修改配置文件中 redis 的配置項 poolMaxTotal 將其設置成 1000。

          #redis配置項
          redis.poolMaxTotal=1000
          redis.poolMaxldle=500
          redis.poolMaxWait=500

          緩存一致性

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

          • 「緩存穿透指的是對某個一定不存在的數(shù)據(jù)進行請求,該請求將會穿透緩存到達數(shù)據(jù)庫。」
            解決方案:對這些不存在的數(shù)據(jù)緩存一個空數(shù)據(jù),對這類請求進行過濾。

          • 「緩存雪崩指的是由于數(shù)據(jù)沒有被加載到緩存中,或者緩存數(shù)據(jù)在同一時間大面積失效(過期),又或者緩存服務器宕機,導致大量的請求都到達數(shù)據(jù)庫。」

            解決方案:

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

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

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

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

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

            解決方案:

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

            在讀緩存之前先判斷緩存是否是最新的,如果不是最新的先進行更新,「需要更新數(shù)據(jù)時,先更新數(shù)據(jù)庫,然后把緩存里對應的數(shù)據(jù)失效掉(刪掉)。」

          減少redis的訪問

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

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

          請求堆積

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

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

          重復下單

          怎么保證一個用戶不能重復下單?

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

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

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

          超賣

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

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

          最簡單的方法,更新數(shù)據(jù)庫減庫存的時候,進行庫存限制條件,在reduceStock(GoodsVo goodsvo)這個方法里,sql要多加一個stock_count > 0 ,使用數(shù)據(jù)庫特性來保證超賣的問題,只有stock_count還大于0的時候才去讀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)用接口來獲取,僅僅交互的是部分數(shù)據(jù),減少了帶寬,也加快用戶訪問的速度。

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

          秒殺架構設計理念?

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

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

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

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

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

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

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

          超時未支付

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

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

          開發(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獲取補充鏈接),將項目下載到 IDEA 里面
          2. 運行 sql 文件夾下的sql文件
          3. 到src/main/resources下的application.properties下修改你的數(shù)據(jù)庫鏈接用戶名與密碼
          4. 安裝redis、mysql、rabbitmq、maven等環(huán)境
          5. 啟動前,檢查配置 application.properties 中相關redis、mysql、rabbitmq地址
          6. 在數(shù)據(jù)庫秒殺商品表里面設置合理的秒殺開始時間與結束時間
          7. 登錄地址:http://localhost:8080/login/to_login
          8. 商品秒殺列表地址:http://localhost:8080/goods/to_list

          其它說明

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

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

          圖片演示

          登錄頁面

          登錄頁面

          商品列表頁面

          商品列表頁面

          商品詳情頁面

          商品詳情頁面

          商品秒殺倒計時

          商品秒殺倒計時

          成功秒殺頁面

          成功秒殺頁面

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

          瀏覽 63
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费无码毛片一区二区本码视频 | 在线看片操 | 正在播放:欧美系列黑人老哥干大奶少妇 | 操逼黄视频 | 国产美女被艹 |