秒殺系統架構解密與防刷設計 - 高可用架構系列

轉載:http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=209083286&idx=1&sn=51287666d25c9aff4afa1aec142f7f8a&;scene=5&;srcid=09135YB9o4LVtVHvAkXAGtLf#rd前端

wKioL1X2VLPRsTk5AANnfgyUkrs113.jpgspacer.gif

此文是根據呂毅在【QCON高可用架構羣】中的分享內容整理而成,轉發請註明出處。redis

呂毅,百度資深研發工程師,LAMP人。
2012年重新浪加入百度移動服務事業羣。在百度期間,隨着產品線發展和業務上QPS增加,架構設計方面略有所獲,對移動端業務、優化有獨特的理解和方法。sql

搶購業務介紹


搶購、閃購,從國外風靡後,國內各種網站都開始作類似的業務,咱們耳熟能詳的惟品會、淘寶、京東都有這類業務。搶購,更多出如今電商網站。那麼,今天和你們一塊兒學習下搶購業務形態的業務架構設計。數據庫

咱們常見的搶購業務分兩種: 限時搶購、限量搶購,我簡單分了下這些case,以下圖:
wKioL1X2VNPxScrnAAJirpYUXYA005.jpg後端

想必小米的搶購運營的最火爆了,每發一款新品,都限量發售,每次搞的你們內心癢癢的。記得以前還由於搶購太火爆,站點打不開,崩潰了。那麼問題來了:爲何搶購老是引起RD、OP恐慌?我理解是,爆品太火爆,瞬時請求太大,致使業務機器、存儲機器都在搶購高峯時扛了太多壓力。那麼,咱們今天以一個搶購業務場景爲例,看看如何扛住壓力,作好搶購業務!假設,這時候咱們接到了產品層面的需求,以下圖:
wKiom1X2UrbwWZLIAAGxq1S3rVM061.jpg緩存

PM也挺呵呵的,又要有時段的要求、又要有限量的要求,大而全吶!
不過,對於我們RD同窗,也不是問題,咱們一塊兒來看看,如何設計業務架構,把需求知足的棒棒噠!
首先,咱們冷靜的看看需求。架構

需求說:商品數據來自資源方。(哦,咱們沒有商品數據。)
需求說:天天要有好幾場搶購,每場搶購都有商品限制(哦,有點商場促銷還限量甩的feel)
需求說:商品要基於用戶位置排序(哦,移動端業務嘛,這種需求老是有的)
需求說:balabala……併發

具體搶購項目中的設計


經過咱們先行的搶購需求分析,咱們畫一個粗略的流程圖,以下:
wKiom1X2UsqiqbDUAAJURdqIcGQ528.jpg咱們將自身簡單劃爲兩部分:業務層、數據層,而且旁路設計一個「運營控制」環節。
固然,數據源自第三方嘛,咱們的數據層基於第三方資源數據構建。
這時,咱們來看看這個草圖裏幾個庫和幾個數據流,是怎樣的。異步

首先,看看庫。數據層的「商品庫」,顯而易見,用於存儲第三方商品數據,經過第三方推、咱們拉的方式來構建這個數據庫信息。數據庫層的「搶購計劃」庫,主要由旁路的「運營控制」環節產生的數據,由運營同窗來維護搶購場次、商品數量。nosql

業務層的「搶購庫」,實際上是商品庫的子集,由運營同窗勾選商品並配好該商品放出多少用於搶購,發佈到業務層面的搶購庫中。

業務層的Transaction Data,一會咱們講到與第三方對帳時候,咱們再說它。

如何解耦先後端壓力


咱們此時回顧下目錄,目錄中咱們講,如何隔離先後端壓力呢?作法是:

  1. 讓咱們業務的壓力,不會傳遞到資源方,避免形成資源方接口壓力同比增加。因此,咱們本身建了商品庫,此時,第三方笑了。

  2. 業務層與數據層解耦,咱們讓搶購庫位於業務層,讓商品庫位於數據層。由於咱們能夠想象到,搶購高峯來臨時,查詢「商品還有沒有?」的請求是最多的,若「有沒有」這種高頻請求每次都去數據層,那咱們其實就將業務、數據耦合在一塊兒了,那麼,就有了搶購庫這個子庫,在業務層抗壓力。(這裏能夠明確的是,數據層的商品庫爲關係型存儲,業務層的搶購庫爲nosql的)

有了業務層的nosql(咱們就用redis吧)抗高頻壓力,數據層的商品庫笑了。
這裏就能夠拋一個思想了:咱們的架構設計中,須要分解壓力,在互聯網項目中,來自於用戶的大流量很多見,這些流量最終都會落到一個地方,就看咱們的設計如何分解這個壓力了,如何避免它層層傳遞。拋個case,咱們的水平分佈業務機器,也是考慮經過水平擴展實例的方式,來分解大流量壓力。
不扯概念的東西了,咱們迴歸咱們的搶購業務。
有了簡單的分層設計,解決的你們都擔憂的壓力問題,咱們就看看搶購業務的時序是怎樣的。
咱們的時序圖分兩個視角來講明:

  1. 商品的角度;

  2. 用戶的角度;

商品角度的時序圖,從左到右:資源方、數據層、旁路-運營控制層、業務層。以下圖:
wKioL1X2VRmzW6XIAAET2WmcNFo147.jpg錄入商品 即商品從資源方發佈到咱們的數據層,形式能夠是經過API、能夠是經過文件傳輸、能夠是咱們去拉去。經過咱們的代碼邏輯,記錄到咱們數據層的「商品庫DB」。
有了自建的商品庫的數據,咱們的運營同窗就能夠基於商品庫設計天天的搶購場次(此事就有Web界面的事情,這裏咱們就不展開這塊了),運營同窗建立好一批搶購場次,記錄在數據層的「搶購計劃」這一關係型數據庫中。
運營同窗建立完搶購場次後,沒完事,還得應產品需求,基於商品庫,配置每場搶購場次中覆蓋的商品,及商品的數量。這些搶購場次內的商品配置,會簡單的記錄在業務層的「搶購庫」中。(搶購庫記錄的信息較爲簡單,例如商品庫中ID爲123123的商品有100件,業務層的搶購庫中只存ID 123123商品運營配了在第X場搶購中有5件)
此時,數據層的 商品庫有了資源方數據、數據層的 搶購計劃庫中有運營配的搶購計劃,業務層的搶購庫中每場搶購活動中商品的狀況。
那麼,業務層此時就能夠基於時間,來展現運營配的搶購場次了。業務層,如何展現,這塊就是拼裝數據、前端效果了,這裏也不展開了。
假設此事某場場次的搶購活動已經開始,咱們再看看用戶角度的時序圖:

wKiom1X2UvfyN6hDAAEG8U1NGzA517.jpg

用戶點擊某個商品的搶購按鈕,業務層代碼首先去看看搶購計劃庫此時是否開始(此步可緩存、也可cache在前端頁面或Client,如有cache的話,此步可忽略)。若搶購在進行中,此時業務代碼須要查詢商品在本次搶購中的庫存還有否(高頻請求,即圖中「爭取名額」階段)。
「爭搶名額」這塊,一會咱們細講,先把時序圖說完。
若用戶搶到了名額,就容許用戶跳轉到第三方的支付頁面產生消費。(此時第三方笑了),產生消費後,第三方本身的庫存-1,而且能夠實時、異步、完事對帳的方式通知咱們。

如何保證商品庫的庫存可靠


此時,咱們回顧下目錄,「如何保證商品庫的庫存可靠」。
咱們實際上是將商品庫的子庫前置在業務層抗壓力。那麼,如何保證你們的庫存狀況穩定,不會由於搶購業務,致使庫存波動影響用戶體驗。這裏就須要提一個業務RD須要關注的問題,須要作好取捨。要麼,咱們保證你們看到的庫存規律一致,要麼,咱們保證單個用戶看到的庫存規律一致。若保證你們看到的庫存減小的規律一致,且同一時刻庫存你們看到的庫存都同樣。這就對系統有數據強一致性要求,須要很大成本,還只能逐漸逼近此要求要求的效果。而咱們若選擇後者,僅保證單個用戶看到的庫存減小規律一致,雖放棄了數據強一致,但以更少的時間儘量實現了最好的效果。因此,咱們用到了用戶來排隊,若搶到名額了,在搶購庫中的庫存 —(減減),這樣單用戶操做期間,能看到規律的減小,不會出現此事看剩10個,一會看還有11個的狀況。這時咱們說如何內部排隊,如何來控制「查詢商品在本次搶購中的庫存還有否(高頻請求)」這個高頻請求。
wKioL1X2VT-Sxyu-AAH_6AyZzeo923.jpg

咱們構建商品維度的cache,上圖中雖說是「隊列」,咱們能夠用redis的list來真正實現個隊列,也能夠經過 /—來實現。

假設商品A,運營配了20件,此事來了N多用戶的請求,業務代碼都會來查詢cache_prefix_a_id這個隊列的長度,若隊列長度≤0,則有權去—(減減)搶購庫的商品庫存。若隊列長度在20件內,則經過業務代碼內的等待來等待隊頭的位置,而後得到搶購權限。若隊列長度太長,則能夠直接返回,認爲商品已被搶空。

這時插入一個運營配庫的時序,便於你們理解。該時序圖有詳細的說明和標註,就不展開了,以下圖:

wKioL1X2VVDQQW-OAAFGh0lIh9w195.jpg

此時,咱們能夠想象,若上游用戶的請求壓力是N,這個N會壓在業務層的搶購庫,俗話說「責任止於此」

如何和第三方多方對帳


那麼,咱們回顧目錄「如何和第三方多方對帳」?
這裏就要提到「Transaction Data」這個庫了。
Transaction ID爲用戶維度的Session記錄,用戶從進入搶購業務開始,產生一個Transaction ID,該Transaction ID生命週期截止到用戶跳轉去第三方支付爲止。期間在生活服務中產生的瀏覽、搶購行爲均會掛靠到該Transaction ID之下,並會在跳轉去第三方支付頁時攜帶該Transaction ID憑證。最主要的是須要記錄下:用戶得到商品名額後,跳轉去第三方時,這一行爲。
考慮到Transaction ID爲搶購業務中,用戶操做行爲的關鍵字段,值須要保證惟一。故此處能夠採用發號器之類的能力。
咱們構建的Transaction Data記錄,就能夠按照DailyRun的方式,與第三方對帳,來fix兩方數據庫庫存不一致等問題。
爲何會產生咱們和資源方的庫存不一致,多是由於用戶在第三方消費後,第三方callback咱們時候失敗形成,也多是由於用戶跳去第三方後並無真正支付,但咱們的商品庫、搶購庫的庫存都已經減小形成的。緣由可能有不少,對帳機制是必要的。

項目總結


最後,咱們回顧回顧設計,壓力問題在業務層解決了,庫存不一致問題咱們經過對帳機制解決了,產品的需求咱們也經過旁路可配解決了,嗯,能夠喝杯茶,發起評審,評審經過後開始寫代碼了。 :)

感謝你們。分享中的數據強一致那塊,以及如何作取捨,都是頗有意思的點,均可以展開聊好久,這裏沒展開,你們能夠過後查查資料。

Q & A


Q1:防刷是怎麼作的?通常搶購都有很大優惠。若是有人惡意刷,那正常的用戶就失去了購買的機會。好比,搶購的商品數爲1000,有人惡意刷了900,那只有 100 被正經常使用戶搶到。等惡意搶到的 900 通過後面的支付環節驗證後,可能已通過了搶購時間了。就算惡意搶到的 900 都支付成功,那對正經常使用戶也是不公平的。

在這個業務場景中,咱們作的是商品展現、商品的購買權的發放,真正產生消費是在第三方。那麼,用戶刷的問題,須要咱們和第三方支付頁面一塊兒來控制。在用戶經過排隊機制,得到了購買名額後,跳轉去第三方時候,咱們按照和第三方約定的加密方式傳遞加密信息,第三方按照約定的解密方式解密成功後才容許用戶支付,加密解密的過程當中能夠帶具備生命週期的內容。這樣,用戶在高頻請求支付頁面獲取商品時候,實際只有:1)加密對;2)第一次,纔可能得到。不過,第三方都是爲了銷售出商品,因此這類合做的成功概率不大。惡意刷,的確會在咱們的業務層面展現商品沒量了。致使想買的用戶沒了機會,但能夠保證第三方不受損。這種刷的狀況,若想在咱們業務層規避,我想這就是一個通用的防SPAM的問題了。這塊本身真懂得很少。

Q2:要想準確的放刷,判斷的維度就多,邏輯就複雜;與之矛盾的,搶購要求的是響應迅速。

對的,搶購業務由於請求壓力大、熱門商品搶購併發高,切忌增長過多邏輯,切忌過多後端依賴,越簡單效果越好。咱們在設計系統時候,不少事不是我們一個系統能cover的,多少須要一些前置模塊、能力的構建ready後,咱們的系統才能run的不錯。建議構建賬號體系、用戶消費記錄這兩部分。

Q3:對帳只是和第三方去對比商品的庫存量嗎,金額是否去對比?

對帳,實際上是對比的消費數據。避免出現咱們統計今日產生了X件商品共價值Y的消費,第三方給出的是消費了N件共M價值的消費。避免金額不一致,形成結算、分紅等問題的出現。我想你問題中的庫存量的diff問題,還得靠第三方按期的經過咱們數據層的接口來update他們提供的商品。其實在咱們的商品庫中,商品不必定只容許第三方提供,也能夠容許第三方經過接口減小商品嘛,好比和一個賣水果的第三方合做,第三方上週發佈說有100件,但這周線下熱銷,只剩20件了,咱們也應該容許第三方來update到一個低值。但這樣,咱們的系統中就會複雜挺多。

Q4:防刷,避免第三方的推廣效果達不到問題。

對的,用戶ID維度、IP維度,都是有效辦法。看具體場景。有賬號體系的業務,用用戶ID維度效果最好,藉助存儲記錄下每一個用戶的購買記錄,來控制就好。市面上的電商網站,基本是搶購業務都須要登陸,而且限制每件商品單人購買數量,其實就是經過存儲記錄用戶的消費,而且再次產生消費前查詢並增長代碼邏輯來控制。

Q5:每次搶購活動的時候用一套新的驗證碼?

驗證碼這個東東,屬於圖靈測試嘛,只要測試方法好,而且儘量保證每次產生的驗證信息從未出現過且無規律,就是好的驗證碼啦。

感謝劉世傑的記錄與整理,國忠的校對與發佈,其餘多位編輯組志願者對本文亦有貢獻。讀者能夠經過搜索「ArchNotes」或長按下面圖片,關注「高可用架構」公衆號,查看更多架構方面內容,獲取通往架構師之路的寶貴經驗。轉載請註明來自「高可用架構(ArchNotes)」公衆號。

wKiom1X2UzGiFAqMAAGlGQq78gI455.jpg

相關文章
相關標籤/搜索