Spring Cloud 實戰-微服務架構下的“秒殺”

案情分析

分析,在做秒殺系統的設計之初,一直在思考如何去設計這個秒殺系統,使之在現有的技術基礎和認知範圍內,能夠做到最好;同時也能充分的利用公司現有的中間件來完成系統的實現。

我們都知道,正常去實現一個WEB端的秒殺系統,前端的處理和後端的處理一樣重要;前端一般會做CDN,後端一般會做分佈式部署,限流,性能優化等等一系列的操作,並完成一些網絡的優化,比如IDC多線路(電信、聯通、移動)的接入,帶寬的升級等等。而由於目前系統前端是基於微信小程序,所以關於前端部分的優化就儘可能都是在代碼中完成,CDN這一步就可以免了;

關於秒殺的更多思考,在原有的秒殺架構的基礎上新增了新的實現方案
原有方案:
通過分佈式鎖的方式控制最終庫存不超賣,並控制最終能夠進入到下單環節的訂單,入到隊列中慢慢去消費下單
新增方案“
請求進來之後,通過活動開始判斷和重複秒殺判斷之後,即進入到消息隊列,然後在消息的消費端去做庫存判斷等操作,通過消息隊列達到削峯的操作

其實,我覺得兩種方案都是可以的,只是具體用在什麼樣的場景;原有方案更適合流量相對較小的平臺,而且整個流程也會更加簡單;而新增方案則是許多超大型平臺採用的方案,通過消息隊列達到削峯的目的;而這兩種方案都加了真實能進入的請求限制,通過redis的原子自增來記錄請求數,當請求量達到庫存的n倍時,後面再進入的請求,則直接返回活動太火爆的提示;

  1. 架構介紹 後端項目是基於SpringCloud+SpringBoot搭建的微服務框架架構

前端在微信小程序商城上

核心支撐組件

  • 服務網關 Zuul
  • 服務註冊發現 Eureka+Ribbon
  • 認證授權中心 Spring SecurityOAuth2、JWTToken
  • 服務框架 Spring MVC/Boot
  • 服務容錯 Hystrix
  • 分佈式鎖 Redis
  • 服務調用 Feign
  • 消息隊列 Kafka
  • 文件服務 私有云盤
  • 富文本組件 UEditor
  • 定時任務 xxl-job
  • 配置中心 apollo
  1. 關於秒殺的場景特點分析

秒殺系統的場景特點

  • 秒殺時大量用戶會在同一時間同時進行搶購,網站瞬時訪問流量激增;

  • 秒殺一般是訪問請求量遠遠大於庫存數量,只有少部分用戶能夠秒殺成功;

  • 秒殺業務流程比較簡單,一般就是下訂單操作;

秒殺架構設計理念

  • 限流:鑑於只有少部分用戶能夠秒殺成功,所以要限制大部分流量,只允許少部分流量進入服務後端(暫未處理);
  • 削峯:對於秒殺系統瞬時的大量用戶湧入,所以在搶購開始會有很高的瞬時峯值。實現削峯的常用方法有利用緩存或者消息中間件等技術;
  • 異步處理:對於高併發系統,採用異步處理模式可以極大地提高系統併發量,異步處理就是削峯的一種實現方式;
  • 內存緩存:秒殺系統最大的瓶頸最終都可能會是數據庫的讀寫,主要體現在的磁盤的I/O,性能會很低,如果能把大部分的業務邏輯都搬到緩存來處理,效率會有極大的提升;
  • 可拓展:如果需要支持更多的用戶或者更大的併發,將系統設計爲彈性可拓展的,如果流量來了,拓展機器就好;
    在這裏插入圖片描述
    在這裏插入圖片描述

秒殺設計思路

由於前端是屬於小程序端,所以不存在前端部分的訪問壓力,所以前端的訪問壓力就無從談起;

  1. 秒殺相關的活動頁面相關的接口,所有查詢能加緩存的,全部添加redis的緩存;

  2. 活動相關真實庫存、鎖定庫存、限購、下單處理狀態等全放redis;

  3. 當有請求進來時,首先通過redis原子自增的方式記錄當前請求數,當請求超過一定量,比如說庫存的10倍之後,後面進入的請求則直接返回活動太火爆的響應;而能進入搶購的請求,則首先進入活動ID爲粒度的分佈式鎖,第一步進行用戶購買的重複性校驗,滿足條件進入下一步,否則返回已下單的提示;

  4. 第二步,判斷當前可鎖定的庫存是否大於購買的數量,滿足條件進入下一步,否則返回已售罄的提示;

  5. 第三步,鎖定當前請求的購買庫存,從鎖定庫存中減除,並將下單的請求放入kafka消息隊列;

  6. 第四步,在redis中標記一個polling的key(用於輪詢的請求接口判斷用戶是否下訂單成功),在kafka消費端消費完成創建訂單之後需要刪除該key,並且維護一個活動id+用戶id的key,防止重複購買;

  7. 第五步,消息隊列消費,創建訂單,創建訂單成功則扣減redis中的真實庫存,並且刪除polling的key。如果下單過程出現異常,則刪除限購的key,返還鎖定庫存,提示用戶下單失敗;

  8. 第六步,提供一個輪詢接口,給前端在完成搶購動作後,檢查最終下訂單操作是否成功,主要判斷依據是redis中的polling的key的狀態;

  9. 整個流程會將所有到後端的請求攔截的在redis的緩存層面,除了最終能下訂單的庫存限制訂單會與數據庫存在交互外,基本上無其他的交互,將數據庫I/O壓力降到了最低;

關於限流

SpringCloud zuul的層面有很好的限流策略,可以防止同一用戶的惡意請求行爲

 zuul:
     ratelimit:
         key-prefix: your-prefix  #對應用來標識請求的key的前綴
         enabled: true
         repository: REDIS  #對應存儲類型(用來存儲統計信息)
         behind-proxy: true  #代理之後
         default-policy: #可選 - 針對所有的路由配置的策略,除非特別配置了policies
              limit: 10 #可選 - 每個刷新時間窗口對應的請求數量限制
              quota: 1000 #可選-  每個刷新時間窗口對應的請求時間限制(秒)
               refresh-interval: 60 # 刷新時間窗口的時間,默認值 (秒)
                type: #可選 限流方式
                     - user
                     - origin
                     - url
           policies:
                 myServiceId: #特定的路由
                       limit: 10 #可選- 每個刷新時間窗口對應的請求數量限制
                       quota: 1000 #可選-  每個刷新時間窗口對應的請求時間限制(秒)
                       refresh-interval: 60 # 刷新時間窗口的時間,默認值 (秒)
                       type: #可選 限流方式
                           - user
                           - origin
                           - url

關於負載與分流

當一個活動的訪問量級特別大的時候,可能從域名分發進來的nginx就算是做了高可用,但實際上最終還是單機在線,始終敵不過超大流量的壓力時,我們可以考慮域名的多IP映射。也就是說同一個域名下面映射多個外網的IP,再映射到DMZ的多組高可用的nginx服務上,nginx再配置可用的應用服務集羣來減緩壓力;

這裏也順帶介紹redis可以採用redis cluster的分佈式實現方案,同時springcloud hystrix 也能有服務容錯的效果;

而關於nginx、springboot的tomcat、zuul等一系列參數優化操作對於性能的訪問提升也是至關重要;

補充說明一點,即使前端是基於小程序實現,但是活動相關的圖片資源都放在自己的雲盤服務上,所以活動前活動相關的圖片資源上傳CDN也是至關重要,否則哪怕是你IDC有1G的流量帶寬,也會分分鐘被喫完;

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