【高併發】高併發秒殺系統架構解密,不是全部的秒殺都是秒殺!

前言

不少小夥伴反饋說,高併發專題學了那麼久,可是,在真正作項目時,仍然不知道如何下手處理高併發業務場景!甚至不少小夥伴仍然停留在只是簡單的提供接口(CRUD)階段,不知道學習的併發知識如何運用到實際項目中,就更別提如何構建高併發系統了!前端

究竟什麼樣的系統算是高併發系統?今天,咱們就一塊兒解密高併發業務場景下典型的秒殺系統的架構,結合高併發專題下的其餘文章,學以至用。數據庫

電商系統架構

在電商領域,存在着典型的秒殺業務場景,那何謂秒殺場景呢。簡單的來講就是一件商品的購買人數遠遠大於這件商品的庫存,並且這件商品在很短的時間內就會被搶購一空。 好比每一年的61八、雙11大促,小米新品促銷等業務場景,就是典型的秒殺業務場景。緩存

咱們能夠將電商系統的架構簡化成下圖所示。服務器

由圖所示,咱們能夠簡單的將電商系統的核心層分爲:負載均衡層、應用層和持久層。接下來,咱們就預估下每一層的併發量。微信

  • 假如負載均衡層使用的是高性能的Nginx,則咱們能夠預估Nginx最大的併發度爲:10W+,這裏是以萬爲單位。網絡

  • 假設應用層咱們使用的是Tomcat,而Tomcat的最大併發度能夠預估爲800左右,這裏是以百爲單位。架構

  • 假設持久層的緩存使用的是Redis,數據庫使用的是MySQL,MySQL的最大併發度能夠預估爲1000左右,以千爲單位。Redis的最大併發度能夠預估爲5W左右,以萬爲單位。併發

因此,負載均衡層、應用層和持久層各自的併發度是不一樣的,那麼,爲了提高系統的整體併發度和緩存,咱們一般能夠採起哪些方案呢?負載均衡

(1)系統擴容異步

系統擴容包括垂直擴容和水平擴容,增長設備和機器配置,絕大多數的場景有效。

(2)緩存

本地緩存或者集中式緩存,減小網絡IO,基於內存讀取數據。大部分場景有效。

(3)讀寫分離

採用讀寫分離,分而治之,增長機器的並行處理能力。

秒殺系統的特色

對於秒殺系統來講,咱們能夠從業務和技術兩個角度來闡述其自身存在的一些特色。

秒殺系統的業務特色

這裏,咱們可使用12306網站來舉例,每一年春運時,12306網站的訪問量是很是大的,可是網站平時的訪問量倒是比較平緩的,也就是說,每一年春運時節,12306網站的訪問量會出現瞬時突增的現象。

再好比,小米秒殺系統,在上午10點開售商品,10點前的訪問量比較平緩,10點時一樣會出現併發量瞬時突增的現象。

因此,秒殺系統的流量和併發量咱們可使用下圖來表示。

由圖能夠看出,秒殺系統的併發量存在瞬時凸峯的特色,也叫作流量突刺現象。

咱們能夠將秒殺系統的業務特色總結以下。

(1)限時、限量、限價

在規定的時間內進行;秒殺活動中商品的數量有限;商品的價格會遠遠低於原來的價格,也就是說,在秒殺活動中,商品會以遠遠低於原來的價格出售。

例如,秒殺活動的時間僅限於某天上午10點到10點半,商品數量只有10萬件,售完爲止,並且商品的價格很是低,例如:1元購等業務場景。

限時、限量和限價能夠單獨存在,也能夠組合存在。

(2)活動預熱

須要提早配置活動;活動還未開始時,用戶能夠查看活動的相關信息;秒殺活動開始前,對活動進行大力宣傳。

(3)持續時間短

購買的人數數量龐大;商品會迅速售完。

在系統流量呈現上,就會出現一個突刺現象,此時的併發訪問量是很是高的,大部分秒殺場景下,商品會在極短的時間內售完。

秒殺系統的技術特色

咱們能夠將秒殺系統的技術特色總結以下。

(1)瞬時併發量很是高

大量用戶會在同一時間搶購商品;瞬間併發峯值很是高。

(2)讀多寫少

系統中商品頁的訪問量巨大;商品的可購買數量很是少;庫存的查詢訪問數量遠遠大於商品的購買數量。

在商品頁中每每會加入一些限流措施,例如早期的秒殺系統商品頁會加入驗證碼來平滑前端對系統的訪問流量,近期的秒殺系統商品詳情頁會在用戶打開頁面時,提示用戶登陸系統。這都是對系統的訪問進行限流的一些措施。

(3)流程簡單

秒殺系統的業務流程通常比較簡單;整體上來講,秒殺系統的業務流程能夠歸納爲:下單減庫存。

針對這種短期內大流量的系統來講,就不太適合使用系統擴容了,由於即便系統擴容了,也就是在很短的時間內會使用到擴容後的系統,大部分時間內,系統無需擴容便可正常訪問。 那麼,咱們能夠採起哪些方案來提高系統的秒殺性能呢?

秒殺系統方案

針對秒殺系統的特色,咱們能夠採起以下的措施來提高系統的性能。

(1)異步解耦

將總體流程進行拆解,核心流程經過隊列方式進行控制。

(2)限流防刷

控制網站總體流量,提升請求的門檻,避免系統資源耗盡。

(3)資源控制

將總體流程中的資源調度進行控制,揚長避短。

因爲應用層可以承載的併發量比緩存的併發量少不少。因此,在高併發系統中,咱們能夠直接使用OpenResty由負載均衡層訪問緩存,避免了調用應用層的性能損耗。你們能夠到openresty.org/cn/來了解有關OpenResty更多的知識。同時,因爲秒殺系統中,商品數量比較少,咱們也可使用動態渲染技術,CDN技術來加速網站的訪問性能。

若是在秒殺活動開始時,併發量過高時,咱們能夠將用戶的請求放入隊列中進行處理,併爲用戶彈出排隊頁面。

圖片來自魅族

秒殺系統時序圖

網上不少的秒殺系統和對秒殺系統的解決方案,並非真正的秒殺系統,他們採用的只是同步處理請求的方案,一旦併發量真的上來了,他們所謂的秒殺系統的性能會急劇降低。咱們先來看一下秒殺系統在同步下單時的時序圖。

同步下單流程

1.用戶發起秒殺請求

在同步下單流程中,首先,用戶發起秒殺請求。商城服務須要依次執行以下流程來處理秒殺請求的業務。

(1)識別驗證碼是否正確

商城服務判斷用戶發起秒殺請求時提交的驗證碼是否正確。

(2)判斷活動是否已經結束

驗證當前秒殺活動是否已經結束。

(3)驗證訪問請求是否處於黑名單

在電商領域中,存在着不少的惡意競爭,也就是說,其餘商家可能會經過不正當手段來惡意請求秒殺系統,佔用系統大量的帶寬和其餘系統資源。此時,就須要使用風控系統等實現黑名單機制。爲了簡單,也可使用攔截器統計訪問頻次實現黑名單機制。

(4)驗證真實庫存是否足夠

系統須要驗證商品的真實庫存是否足夠,是否可以支持本次秒殺活動的商品庫存量。

(5)扣減緩存中的庫存

在秒殺業務中,每每會將商品庫存等信息存放在緩存中,此時,還須要驗證秒殺活動使用的商品庫存是否足夠,而且須要扣減秒殺活動的商品庫存數量。

(6)計算秒殺的價格

因爲在秒殺活動中,商品的秒殺價格和商品的真實價格存在差別,因此,須要計算商品的秒殺價格。

注意:若是在秒殺場景中,系統涉及的業務更加複雜的話,會涉及更多的業務操做,這裏,我只是列舉出一些常見的業務操做。

2.提交訂單

(1)訂單入口

將用戶提交的訂單信息保存到數據庫中。

(2)扣減真實庫存

訂單入庫後,須要在商品的真實庫存中將本次成功下單的商品數量扣除。

若是咱們使用上述流程開發了一個秒殺系統,當用戶發起秒殺請求時,因爲系統每一個業務流程都是串行執行的,總體上系統的性能不會過高,當併發量過高時,咱們會爲用戶彈出下面的排隊頁面,來提示用戶進行等待。

圖片來自魅族

此時的排隊時間多是15秒,也多是30秒,甚至是更長時間。這就存在一個問題:在用戶發起秒殺請求到服務器返回結果的這段時間內,客戶端和服務器之間的鏈接不會被釋放,這就會佔大量佔用服務器的資源。

網上不少介紹如何實現秒殺系統的文章都是採用的這種方式,那麼,這種方式能作秒殺系統嗎?答案是能夠作,可是這種方式支撐的併發量並非過高。此時,有些網友可能會問:咱們公司就是這樣作的秒殺系統啊!上線後一直在用,沒啥問題啊!我想說的是:使用同步下單方式確實能夠作秒殺系統,可是同步下單的性能不會過高。之因此大家公司採用同步下單的方式作秒殺系統沒出現大的問題,那是由於大家的秒殺系統的併發量沒達到必定的量級,也就是說,大家的秒殺系統的併發量其實並不高。

因此,不少所謂的秒殺系統,存在着秒殺的業務,可是稱不上真正的秒殺系統,緣由就在於他們使用的是同步的下單流程,限制了系統的併發流量。之因此上線後沒出現太大的問題,是由於系統的併發量不高,不足以壓死整個系統。

若是1230六、淘寶、天貓、京東、小米等大型商城的秒殺系統是這麼玩的話,那麼,他們的系統早晚會被玩死,他們的系統工程師不被開除纔怪!因此,在秒殺系統中,這種同步處理下單的業務流程的方案是不可取的。

以上就是同步下單的整個流程操做,若是下單流程更加複雜的話,就會涉及到更多的業務操做。

異步下單流程

既然同步下單流程的秒殺系統稱不上真正的秒殺系統,那咱們就須要採用異步的下單流程了。異步的下單流程不會限制系統的高併發流量。

1.用戶發起秒殺請求

用戶發起秒殺請求後,商城服務會通過以下業務流程。

(1)檢測驗證碼是否正確

用戶發起秒殺請求時,會將驗證碼一同發送過來,系統會檢驗驗證碼是否有效,而且是否正確。

(2)是否限流

系統會對用戶的請求進行是否限流的判斷,這裏,咱們能夠經過判斷消息隊列的長度來進行判斷。由於咱們將用戶的請求放在了消息隊列中,消息隊列中堆積的是用戶的請求,咱們能夠根據當前消息隊列中存在的待處理的請求數量來判斷是否須要對用戶的請求進行限流處理。

例如,在秒殺活動中,咱們出售1000件商品,此時在消息隊列中存在1000個請求,若是後續仍然有用戶發起秒殺請求,則後續的請求咱們能夠再也不處理,直接向用戶返回商品已售完的提示。

因此,使用限流後,咱們能夠更快的處理用戶的請求和釋放鏈接的資源。

(3)發送MQ

用戶的秒殺請求經過前面的驗證後,咱們就能夠將用戶的請求參數等信息發送到MQ中進行異步處理,同時,向用戶響應結果信息。在商城服務中,會有專門的異步任務處理模塊來消費消息隊列中的請求,並處理後續的異步流程。

在用戶發起秒殺請求時,異步下單流程比同步下單流程處理的業務操做更少,它將後續的操做經過MQ發送給異步處理模塊進行處理,並迅速向用戶返回響應結果,釋放請求鏈接。

2.異步處理

咱們能夠將下單流程的以下操做進行異步處理。

(1)判斷活動是否已經結束

(2)判斷本次請求是否處於系統黑名單,爲了防止電商領域同行的惡意競爭能夠爲系統增長黑名單機制,將惡意的請求放入系統的黑名單中。可使用攔截器統計訪問頻次來實現。

(3)扣減緩存中的秒殺商品的庫存數量。

(4)生成秒殺Token,這個Token是綁定當前用戶和當前秒殺活動的,只有生成了秒殺Token的請求才有資格進行秒殺活動。

這裏咱們引入了異步處理機制,在異步處理中,系統使用多少資源,分配多少線程來處理相應的任務,是能夠進行控制的。

3.短輪詢查詢秒殺結果

這裏,能夠採起客戶端短輪詢查詢是否得到秒殺資格的方案。例如,客戶端能夠每隔3秒鐘輪詢請求服務器,查詢是否得到秒殺資格,這裏,咱們在服務器的處理就是判斷當前用戶是否存在秒殺Token,若是服務器爲當前用戶生成了秒殺Token,則當前用戶存在秒殺資格。不然繼續輪詢查詢,直到超時或者服務器返回商品已售完或者無秒殺資格等信息爲止。

採用短輪詢查詢秒殺結果時,在頁面上咱們一樣能夠提示用戶排隊處理中,可是此時客戶端會每隔幾秒輪詢服務器查詢秒殺資格的狀態,相比於同步下單流程來講,無需長時間佔用請求鏈接。

此時,可能會有網友會問:採用短輪詢查詢的方式,會不會存在直到超時也查詢不到是否具備秒殺資格的狀態呢?答案是:有可能! 這裏咱們試想一下秒殺的真實場景,商家參加秒殺活動本質上不是爲了賺錢,而是提高商品的銷量和商家的知名度,吸引更多的用戶來買本身的商品。因此,咱們沒必要保證用戶可以100%的查詢到是否具備秒殺資格的狀態。

4.秒殺結算

(1)驗證下單Token

客戶端提交秒殺結算時,會將秒殺Token一同提交到服務器,商城服務會驗證當前的秒殺Token是否有效。

(2)加入秒殺購物車

商城服務在驗證秒殺Token合法並有效後,會將用戶秒殺的商品添加到秒殺購物車。

5.提交訂單

(1)訂單入庫

將用戶提交的訂單信息保存到數據庫中。

(2)刪除Token

秒殺商品訂單入庫成功後,刪除秒殺Token。

這裏你們能夠思考一個問題:咱們爲何只在異步下單流程的粉色部分採用異步處理,而沒有在其餘部分採起異步削峯和填谷的措施呢?

這是由於在異步下單流程的設計中,不管是在產品設計上仍是在接口設計上,咱們在用戶發起秒殺請求階段對用戶的請求進行了限流操做,能夠說,系統的限流操做是很是前置的。在用戶發起秒殺請求時進行了限流,系統的高峯流量已經被平滑解決了,再日後走,其實系統的併發量和系統流量並非很是高了。

因此,網上不少的文章和帖子中在介紹秒殺系統時,說是在下單時使用異步削峯來進行一些限流操做,那都是在扯淡! 由於下單操做在整個秒殺系統的流程中屬於比較靠後的操做了,限流操做必定要前置處理,在秒殺業務後面的流程中作限流操做是沒啥卵用的。

高併發「黑科技」與致勝奇招

假設,在秒殺系統中咱們使用Redis實現緩存,假設Redis的讀寫併發量在5萬左右。咱們的商城秒殺業務須要支持的併發量在100萬左右。若是這100萬的併發所有打入Redis中,Redis極可能就會掛掉,那麼,咱們如何解決這個問題呢?接下來,咱們就一塊兒來探討這個問題。

在高併發的秒殺系統中,若是採用Redis緩存數據,則Redis緩存的併發處理能力是關鍵,由於不少的前綴操做都須要訪問Redis。而異步削峯只是基本的操做,關鍵仍是要保證Redis的併發處理能力。

解決這個問題的關鍵思想就是:分而治之,將商品庫存分開放。

暗度陳倉

咱們在Redis中存儲秒殺商品的庫存數量時,能夠將秒殺商品的庫存進行「分割」存儲來提高Redis的讀寫併發量。

例如,原來的秒殺商品的id爲10001,庫存爲1000件,在Redis中的存儲爲(10001, 1000),咱們將原有的庫存分割爲5份,則每份的庫存爲200件,此時,咱們在Redia中存儲的信息爲(10001_0, 200),(10001_1, 200),(10001_2, 200),(10001_3, 200),(10001_4, 200)。

此時,咱們將庫存進行分割後,每一個分割後的庫存使用商品id加上一個數字標識來存儲,這樣,在對存儲商品庫存的每一個Key進行Hash運算時,得出的Hash結果是不一樣的,這就說明,存儲商品庫存的Key有很大機率不在Redis的同一個槽位中,這就可以提高Redis處理請求的性能和併發量。

分割庫存後,咱們還須要在Redis中存儲一份商品id和分割庫存後的Key的映射關係,此時映射關係的Key爲商品的id,也就是10001,Value爲分割庫存後存儲庫存信息的Key,也就是10001_0,10001_1,10001_2,10001_3,10001_4。在Redis中咱們可使用List來存儲這些值。

在真正處理庫存信息時,咱們能夠先從Redis中查詢出秒殺商品對應的分割庫存後的全部Key,同時使用AtomicLong來記錄當前的請求數量,使用請求數量對從Redia中查詢出的秒殺商品對應的分割庫存後的全部Key的長度進行求模運算,得出的結果爲0,1,2,3,4。再在前面拼接上商品id就能夠得出真正的庫存緩存的Key。此時,就能夠根據這個Key直接到Redis中獲取相應的庫存信息。

移花接木

在高併發業務場景中,咱們能夠直接使用Lua腳本庫(OpenResty)從負載均衡層直接訪問緩存。

這裏,咱們思考一個場景:若是在秒殺業務場景中,秒殺的商品被瞬間搶購一空。此時,用戶再發起秒殺請求時,若是系統由負載均衡層請求應用層的各個服務,再由應用層的各個服務訪問緩存和數據庫,其實,本質上已經沒有任何意義了,由於商品已經賣完了,再經過系統的應用層進行層層校驗已經沒有太多意義了!!而應用層的併發訪問量是以百爲單位的,這又在必定程度上會下降系統的併發度。

爲了解決這個問題,此時,咱們能夠在系統的負載均衡層取出用戶發送請求時攜帶的用戶id,商品id和秒殺活動id等信息,直接經過Lua腳本等技術來訪問緩存中的庫存信息。若是秒殺商品的庫存小於或者等於0,則直接返回用戶商品已售完的提示信息,而不用再通過應用層的層層校驗了。 針對這個架構,咱們能夠參見本文中的電商系統的架構圖(正文開始的第一張圖)。

好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一塊兒學習,一塊兒進步!!

寫在最後

若是你以爲冰河寫的還不錯,請微信搜索並關注「 冰河技術 」微信公衆號,跟冰河學習高併發、分佈式、微服務、大數據、互聯網和雲原生技術,「 冰河技術 」微信公衆號更新了大量技術專題,每一篇技術文章乾貨滿滿!很多讀者已經經過閱讀「 冰河技術 」微信公衆號文章,成功跳槽到大廠;也有很多讀者實現了技術上的飛躍,成爲公司的技術骨幹!若是你也想像他們同樣提高本身的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公衆號吧,天天更新超硬核技術乾貨,讓你對如何提高技術能力再也不迷茫!

相關文章
相關標籤/搜索