【商城秒殺項目】-- 項目總結

轉自:https://blog.csdn.net/weixin_42687829/article/details/104535027?utm_source=app

原作者項目代碼已上傳至gitHub:https://github.com/java-LJ/miaosha.git

最近對商城秒殺項目所用到的技術進行了剖析,技術點還是挺多的,本篇博客就來對商城秒殺項目進行一個總結與整理

項目相關博客彙總

1、【商城秒殺項目】-- 概況

2、【商城秒殺項目】-- 對返回 json 結果的封裝、通用緩存 Key 的設計與封裝

3、【商城秒殺項目】-- 登錄時使用兩次 MD5 加密

4、【商城秒殺項目】-- 使用 JSR303 進行參數校驗、全局異常處理

5、【商城秒殺項目】-- 分佈式 Session 的實現、使用 Aop 校驗 Token 令牌

6、【商城秒殺項目】-- 流量削峯應該怎麼做

7、【商城秒殺項目】-- 頁面緩存、URL 緩存、對象緩存

8、【商城秒殺項目】-- 頁面僞靜態化

9、【商城秒殺項目】-- 秒殺的業務邏輯、接口的優化

10、【商城秒殺項目】-- 使用 rabbitmq 異步下單

11、【商城秒殺項目】-- 秒殺接口地址隱藏

12、【商城秒殺項目】-- 使用數學圖形驗證碼來進行限流

13、【商城秒殺項目】-- 接口限流防刷

14、關於 springboot 項目使用 @Transactional 註解事務不回滾的問題

項目的亮點

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

主要知識點:

  • 分佈式 Seesion

通常秒殺服務實際的應用可能不止部署在一個服務器上,而是分佈式的部署在多臺服務器,這時候假如用戶登錄是在第一個服務器,第一個請求到了第一臺服務器,這時是沒問題的,但是第二個請求到了第二個服務器,那麼用戶的 session 信息就丟失了,於是需要使用分佈式 session 來進行處理

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

  • 使用 redis 緩解數據庫壓力

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

  • 通用緩存 key 封裝

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

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

  • 頁面靜態化(前後端分離)

頁面靜態化的主要目的是爲了加快頁面的加載速度,將商品的詳情和訂單詳情頁面做成靜態 HTML(純的 HTML),數據的加載只需要通過 ajax 來請求服務器,並且做了靜態化 HTML 頁面可以緩存在客戶端的瀏覽器

  • 消息隊列完成異步下單

使用消息隊列完成異步下單,可以提升用戶體驗、削峯和限流

使用思路:

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

雙重 md5 密碼校驗、秒殺接口地址的隱藏、接口限流防刷、數學公式驗證碼

  • 優雅的代碼編寫

對接口的輸出結果做了一個 Result 封裝、對錯誤的代碼做了一個 CodeMsg 封裝、對訪問緩存做了一個 key 的封裝

項目難點及問題解決

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

  • 緩存穿透指的是對某個一定不存在的數據進行請求,該請求將會穿透緩存到達數據庫

解決方案:對這些不存在的數據緩存一個空數據,對這類請求進行過濾

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

解決方案:爲了防止緩存在同一時間大面積過期導致的緩存雪崩,可以通過觀察用戶行爲,合理設置緩存過期時間來實現;爲了防止緩存服務器宕機出現的緩存雪崩,可以使用分佈式緩存,分佈式緩存中每一個節點只緩存部分的數據,當某個節點宕機時可以保證其它節點的緩存仍然可用;也可以進行緩存預熱,避免在系統剛啓動不久由於還未將大量數據進行緩存而導致緩存雪崩

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

  • 緩存一致性要求數據更新的同時緩存數據也能夠實時更新

解決方案:在數據更新的同時立即去更新緩存,首先嚐試從緩存讀取,讀到數據則直接返回;如果讀不到,就讀數據庫,並將數據會寫到緩存,並返回;在讀緩存之前先判斷緩存是否是最新的,如果不是最新的先進行更新,需要更新數據時,先更新數據庫,然後把緩存裏對應的數據失效掉(刪掉)

2、大量的使用緩存,對於緩存服務器也有很大的壓力,如何減少 redis 的訪問

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

3、在高併發請求的業務場景,大量請求來不及處理,甚至出現請求堆積的情況

使用消息隊列來異步處理請求,每次請求過來先不去處理請求,而是放入消息隊列,然後在後臺設置一個監聽器,分別監聽不同業務的消息隊列,有消息來的時候才進行秒殺業務邏輯,這樣防止多個請求同時操作的時候,出現數據庫連接過多的異常

4、怎麼保證一個用戶不能重複下單

在秒殺訂單表中建立一個唯一索引(由用戶 Id 與商品 Id 組成),使得同一條記錄只能入庫一次,防止一個用戶同時發出多個請求秒殺到多個商品

唯一索引是在數據庫表結構中對字段添加唯一索引後數據庫進行存儲操作時數據庫會判斷庫中是否已經存在此數據,不存在此數據時才能進行插入操作。這雖然是個小技能,但實際上在業務開發中是個很實用的技能,比如在高併發業務中,數據庫如何杜絕數據併發插入兩條相同的訂單號?添加一個唯一索引是最快捷的方法之一,當然是添加索引還是通過業務代碼去解決因業務而定

5、怎麼解決超賣現象

超賣場景:不同用戶在讀請求的時候,發現商品庫存足夠,然後同時發起請求,進行秒殺操作減庫存,導致庫存減爲負數

最簡單的方法就是在更新數據庫減庫存的時候,進行庫存限制條件,在 reduceStock (GoodsVo goodsvo) 這個方法裏,sql 要多加一個 stock_count>0 的條件,使用數據庫特性來保證超賣的問題,只有 stock_count 還大於 0 的時候纔去 update 將 stock_count 進行減 1 的操作

6、頁面靜態化的過程及什麼是瀏覽器緩存

將 HTML 靜態頁面緩存在客戶端瀏覽器,只有數據通過 ajax 異步調用接口來獲取,僅僅交互的是部分數據,可以減少帶寬,也加快用戶訪問的速度

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

7、秒殺架構設計理念

  • 限流: 鑑於只有少部分用戶能夠秒殺成功,所以要限制大部分流量,只允許少部分流量進入服務後端
  • 削峯:對於秒殺系統瞬時會有大量用戶湧入,所以在搶購一開始會有很高的瞬間峯值,而高峯值流量是壓垮系統很重要的原因,所以如何把瞬間的高流量變成一段時間平穩的流量也是設計秒殺系統很重要的思路。實現削峯的常用的方法有利用緩存和消息中間件等技術
  • 異步處理:秒殺系統是一個高併發系統,採用異步處理模式可以極大地提高系統併發量,其實異步處理就是削峯的一種實現方式
  • 內存緩存:秒殺系統最大的瓶頸一般都是數據庫讀寫,由於數據庫讀寫屬於磁盤 IO,性能很低,如果能夠把部分數據或業務邏輯轉移到內存緩存,效率會有極大地提升
  • 可拓展:當然如果我們想支持更多用戶,更大的併發,最好就是將系統設計成彈性可拓展的,如果流量來了,拓展機器就好了

8、秒殺系統架構設計思路

  • 將請求攔截在系統上游,降低下游壓力:秒殺系統特點是併發量極大,但實際秒殺成功的請求數量卻很少,所以如果不在前端攔截很可能造成數據庫讀寫鎖衝突,最終請求超時
  • 使用緩存:使用緩存可極大提高系統讀寫速度
  • 使用消息隊列:消息隊列可以削峯、攔截大量併發請求,這也是一個異步處理過程,後臺業務根據自己的處理能力,從消息隊列中主動的拉取請求消息進行業務處理

9、假如減了庫存但用戶沒有支付,怎麼將庫存還原繼續進行搶購

設定一個最長付款時間,比如 30 分鐘,後臺有個定時任務(使用定時器 Timer),輪詢超過 30 分鐘的待付款訂單(從數據庫裏面判定訂單狀態),然後關閉訂單,恢復庫存

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章