使用SpringCloud搭建「秒殺」架構(github源碼)
- 2020 年 2 月 26 日
- 筆記
分析,在做秒殺系統的設計之初,一直在思考如何去設計這個秒殺系統,使之在現有的技術基礎和認知範圍內,能夠做到最好;同時也能充分的利用公司現有的中間件來完成系統的實現。
我們都知道,正常去實現一個WEB端的秒殺系統,前端的處理和後端的處理一樣重要;前端一般會做CDN,後端一般會做分散式部署,限流,性能優化等等一系列的操作,並完成一些網路的優化,比如IDC多線路(電信、聯通、移動)的接入,頻寬的升級等等。而由於目前系統前端是基於微信小程式,所以關於前端部分的優化就儘可能都是在程式碼中完成,CDN這一步就可以免了;
關於秒殺的更多思考在原有的秒殺架構的基礎上新增了新的實現方案
原有方案:
通過分散式鎖的方式控制最終庫存不超賣,並控制最終能夠進入到下單環節的訂單,入到隊列中慢慢去消費下單
新增方案
請求進來之後,通過活動開始判斷和重複秒殺判斷之後,即進入到消息隊列,然後在消息的消費端去做庫存判斷等操作,通過消息隊列達到削峰的操作
其實,我覺得兩種方案都是可以的,只是具體用在什麼樣的場景;原有方案更適合流量相對較小的平台,而且整個流程也會更加簡單;而新增方案則是許多超大型平台採用的方案,通過消息隊列達到削峰的目的;而這兩種方案都加了真實能進入的請求限制,通過redis的原子自增來記錄請求數,當請求量達到庫存的n倍時,後面再進入的請求,則直接返回活動太火爆的提示。整編:微信公眾號,搜雲庫技術團隊,ID:souyunku
架構介紹
架構介紹 後端項目是基於SpringCloud+SpringBoot搭建的微服務框架架構
前端在微信小程式商城上
核心支撐組件
- 服務網關 Zuul
- 服務註冊發現 Eureka+Ribbon
- 認證授權中心 Spring Security OAuth2、JWTToken
- 服務框架 Spring MVC/Boot
- 服務容錯 Hystrix
- 分散式鎖 Redis
- 服務調用 Feign
- 消息隊列 Kafka
- 文件服務 私有雲盤
- 富文本組件 UEditor
- 定時任務 xxl-job
- 配置中心 apollo
關於秒殺的場景特點分析
秒殺系統的場景特點
1、秒殺時大量用戶會在同一時間同時進行搶購,網站瞬時訪問流量激增;
2、秒殺一般是訪問請求量遠遠大於庫存數量,只有少部分用戶能夠秒殺成功;
3、秒殺業務流程比較簡單,一般就是下訂單操作;
秒殺架構的設計理念
限流:鑒於只有少部分用戶能夠秒殺成功,所以要限制大部分流量,只允許少部分流量進入服務後端(暫未處理);
削峰:對於秒殺系統瞬時的大量用戶湧入,所以在搶購開始會有很高的瞬時峰值。實現削峰的常用方法有利用快取或者消息中間件等技術;
非同步處理:對於高並發系統,採用非同步處理模式可以極大地提高系統並發量,非同步處理就是削峰的一種實現方式;
記憶體快取:秒殺系統最大的瓶頸最終都可能會是資料庫的讀寫,主要體現在的磁碟的I/O,性能會很低,如果能把大部分的業務邏輯都搬到快取來處理,效率會有極大的提升;
可拓展:如果需要支援更多的用戶或者更大的並發,將系統設計為彈性可拓展的,如果流量來了,拓展機器就好;

秒殺設計思路
由於前端是屬於小程式端,所以不存在前端部分的訪問壓力,所以前端的訪問壓力就無從談起;
1、秒殺相關的活動頁面相關的介面,所有查詢能加快取的,全部添加redis的快取;
2、活動相關真實庫存、鎖定庫存、限購、下單處理狀態等全放redis;
3、當有請求進來時,首先通過redis原子自增的方式記錄當前請求數,當請求超過一定量,比如說庫存的10倍之後,後面進入的請求則直接返回活動太火爆的響應;而能進入搶購的請求,則首先進入活動ID為粒度的分散式鎖,第一步進行用戶購買的重複性校驗,滿足條件進入下一步,否則返回已下單的提示;整編:微信公眾號,搜雲庫技術團隊,ID:souyunku4、第二步,判斷當前可鎖定的庫存是否大於購買的數量,滿足條件進入下一步,否則返回已售罄的提示;
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的層面有很好的限流策略,可以防止同一用戶的惡意請求行為
1 zuul: 2 ratelimit: 3 key-prefix: your-prefix #對應用來標識請求的key的前綴 4 enabled: true 5 repository: REDIS #對應存儲類型(用來存儲統計資訊) 6 behind-proxy: true #代理之後 7 default-policy: #可選 - 針對所有的路由配置的策略,除非特別配置了policies 8 limit: 10 #可選 - 每個刷新時間窗口對應的請求數量限制 9 quota: 1000 #可選- 每個刷新時間窗口對應的請求時間限制(秒) 10 refresh-interval: 60 # 刷新時間窗口的時間,默認值 (秒) 11 type: #可選 限流方式 12 - user 13 - origin 14 - url 15 policies: 16 myServiceId: #特定的路由 17 limit: 10 #可選- 每個刷新時間窗口對應的請求數量限制 18 quota: 1000 #可選- 每個刷新時間窗口對應的請求時間限制(秒) 19 refresh-interval: 60 # 刷新時間窗口的時間,默認值 (秒) 20 type: #可選 限流方式 21 - user 22 - origin 23 - url
關於負載與分流
當一個活動的訪問量級特別大的時候,可能從域名分發進來的nginx就算是做了高可用,但實際上最終還是單機在線,始終敵不過超大流量的壓力時,我們可以考慮域名的多IP映射。也就是說同一個域名下面映射多個外網的IP,再映射到DMZ的多組高可用的nginx服務上,nginx再配置可用的應用服務集群來減緩壓力。整編:微信公眾號,搜雲庫技術團隊,ID:souyunku
這裡也順帶介紹redis可以採用redis cluster的分散式實現方案,同時springcloud hystrix 也能有服務容錯的效果;
而關於nxinx、springboot的tomcat、zuul等一系列參數優化操作對於性能的訪問提升也是至關重要;
補充說明一點,即使前端是基於小程式實現,但是活動相關的圖片資源都放在自己的雲盤服務上,所以活動前活動相關的圖片資源上傳CDN也是至關重要,否則哪怕是你IDC有1G的流量頻寬,也會分分鐘被吃完;
主要程式碼實現
周末抽空整理了一個小demo,把主要的業務邏輯抽出來了,由於為了方便處理,暫時是弄成了單體應用,上文中提到的很多的組件並沒有全部集成進來,只保留了核心的業務處理邏輯;如有需要,再將一整套的框架開源出來了(包含了微服務後端和分離了的VUE+elementUI+AdminLTE的後台管理框架);
swagger地址:
http://localhost:8080/swagger-ui.html
git clone 項目後啟動即可訪問

設置庫存參數
{ "stockNum":100, "stallActivityId":1 }

設置去秒殺參數
{ "stallActivityId": 1, "purchaseNum": 1, "openId": "this is a test openId", "formId": "this is a test formId", "addressId": 100 }

設置輪詢請求的參數
{ "stallActivityId": 1, "openId": "this is a test openId" }

git地址:
https://github.com/coderliguoqing/distributed-seckill.git
如有不妥之處,歡迎來交流和分享,接受批評和指正。
(完)