互聯網秒殺業務設計
搶購、閃購,從國外風靡后,國內各類網站都開始做相似的業務,我們耳熟能詳的唯品會、淘寶、京東都有這類業務。搶購,更多出現在電商網站。那么,今天和大家一起學習下搶購業務形態的業務架構設計。
1.搶購業務介紹
我們常見的搶購業務分兩種:限時搶購、限量搶購,我簡單分了下這些case,如下圖:
想必小米的搶購運營的最火爆了,每發一款新品,都限量發售,每次搞的大家心里癢癢的。記得之前還因為搶購太火爆,站點打不開,崩潰了。那么問題來 了:為什么搶購總是引發RD、OP恐慌?我理解是,爆品太火爆,瞬時請求太大,導致業務機器、存儲機器都在搶購高峰時扛了太多壓力。那么,我們今天以一個 搶購業務場景為例,看看如何扛住壓力,做好搶購業務!假設,這時候我們接到了產品層面的需求,如下圖:
PM也挺呵呵的,又要有時段的要求、又要有限量的要求,大而全吶!
不過,對于咱們RD同學,也不是問題,我們一起來看看,如何設計業務架構,把需求滿足的棒棒噠!
首先,我們冷靜的看看需求。
需求說:商品數據來自資源方。(哦,我們沒有商品數據。)
需求說:每天要有好幾場搶購,每場搶購都有商品限制(哦,有點商場促銷還限量甩的feel)
需求說:商品要基于用戶位置排序(哦,移動端業務嘛,這種需求總是有的)
需求說:balabala……
2.具體搶購項目中的設計
通過我們先行的搶購需求分析,我們畫一個粗略的流程圖,如下:
我們將自身簡單劃為兩部分:業務層、數據層,并且旁路設計一個“運營控制”環節。
當然,數據源自第三方嘛,我們的數據層基于第三方資源數據構建。
這時,我們來看看這個草圖里幾個庫和幾個數據流,是怎樣的。
首先,看看庫。
數據層的“商品庫”,顯而易見,用于存儲第三方商品數據,通過第三方推、我們拉的方式來構建這個數據庫信息。數據庫層的“搶購計劃”庫,主要由旁路的“運營控制”環節產生的數據,由運營同學來維護搶購場次、商品數量。
業務層的“搶購庫”,其實是商品庫的子集,由運營同學勾選商品并配好該商品放出多少用于搶購,發布到業務層面的搶購庫中。
業務層的TransactionData,一會我們講到與第三方對賬時候,我們再說它。
2.1如何解耦前后端壓力
我們此時回顧下目錄,目錄中我們講,如何隔離前后端壓力呢?做法是:
1.讓我們業務的壓力,不會傳遞到資源方,避免造成資源方接口壓力同比增長。所以,我們自己建了商品庫,此時,第三方笑了。
2.業務層與數據層解耦,我們讓搶購庫位于業務層,讓商品庫位于數據層。因為我們可以想象到,搶購高峰來臨時,查詢“商品還有沒有?”的請求是最 多的,若“有沒有”這種高頻請求每次都去數據層,那我們其實就將業務、數據耦合在一起了,那么,就有了搶購庫這個子庫,在業務層抗壓力。(這里可以明確的 是,數據層的商品庫為關系型存儲,業務層的搶購庫為nosql的)
有了業務層的nosql(我們就用redis吧)抗高頻壓力,數據層的商品庫笑了。
這里就可以拋一個思想了:我們的架構設計中,需要分解壓力,在互聯網項目中,來自于用戶的大流量不少見,這些流量最終都會落到一個地方,就看我們 的設計如何分解這個壓力了,如何避免它層層傳遞。拋個case,我們的水平分布業務機器,也是考慮通過水平擴展實例的方式,來分解大流量壓力。
不扯概念的東西了,我們回歸我們的搶購業務。
有了簡單的分層設計,解決的大家都擔心的壓力問題,我們就看看搶購業務的時序是怎樣的。
我們的時序圖分兩個視角來說明:
1.商品的角度;
2.用戶的角度;
商品角度的時序圖,從左到右:資源方、數據層、旁路-運營控制層、業務層。如下圖:
錄入商品 即商品從資源方發布到我們的數據層,形式可以是通過API、可以是通過文件傳輸、可以是我們去拉去。通過我們的代碼邏輯,記錄到我們數據層的“商品庫DB”。
有了自建的商品庫的數據,我們的運營同學就可以基于商品庫設計每天的搶購場次(此事就有Web界面的事情,這里我們就不展開這塊了),運營同學創建好一批搶購場次,記錄在數據層的“搶購計劃”這一關系型數據庫中。
運營同學創建完搶購場次后,沒完事,還得應產品需求,基于商品庫,配置每場搶購場次中覆蓋的商品,及商品的數量。這些搶購場次內的商品配置,會簡 單的記錄在業務層的“搶購庫”中。(搶購庫記錄的信息較為簡單,例如商品庫中ID為123123的商品有100件,業務層的搶購庫中只存ID123123 商品運營配了在第X場搶購中有5件)
此時,數據層的商品庫有了資源方數據、數據層的搶購計劃庫中有運營配的搶購計劃,業務層的搶購庫中每場搶購活動中商品的情況。
那么,業務層此時就可以基于時間,來展示運營配的搶購場次了。業務層,如何展示,這塊就是拼裝數據、前端效果了,這里也不展開了。
假設此事某場場次的搶購活動已經開始,我們再看看用戶角度的時序圖:
用戶點擊某個商品的搶購按鈕,業務層代碼首先去看看搶購計劃庫此時是否開始(此步可緩存、也可cache在前端頁面或Client,若有 cache的話,此步可忽略)。若搶購在進行中,此時業務代碼需要查詢商品在本次搶購中的庫存還有否(高頻請求,即圖中“爭取名額”階段)。
“爭搶名額”這塊,一會我們細講,先把時序圖說完。
若用戶搶到了名額,就允許用戶跳轉到第三方的支付頁面產生消費。(此時第三方笑了),產生消費后,第三方自己的庫存-1,并且可以實時、異步、完事對賬的方式通知我們。
2.2如何保證商品庫的庫存可靠
此時,我們回顧下目錄,“如何保證商品庫的庫存可靠”。
我們其實是將商品庫的子庫前置在業務層抗壓力。那么,如何保證大家的庫存情況穩定,不會因為搶購業務,導致庫存波動影響用戶體驗。這里就需要提一 個業務RD需要關注的問題,需要做好取舍。要么,我們保證大家看到的庫存規律一致,要么,我們保證單個用戶看到的庫存規律一致。若保證大家看到的庫存減少 的規律一致,且同一時刻庫存大家看到的庫存都一樣。這就對系統有數據強一致性要求,需要很大成本,還只能逐漸逼近此要求要求的效果。而我們若選擇后者,僅 保證單個用戶看到的庫存減少規律一致,雖放棄了數據強一致,但以更少的時間盡可能實現了最好的效果。所以,我們用到了用戶來排隊,若搶到名額了,在搶購庫 中的庫存–(減減),這樣單用戶操作期間,能看到規律的減少,不會出現此事看剩10個,一會看還有11個的情況。這時我們說如何內部排隊,如何來控制“查 詢商品在本次搶購中的庫存還有否(高頻請求)”這個高頻請求。
我們構建商品維度的cache,上圖中雖然說是“隊列”,我們可以用redis的list來真正實現個隊列,也可以通過/–來實現。
假設商品A,運營配了20件,此事來了N多用戶的請求,業務代碼都會來查詢cache_prefix_a_id這個隊列的長度,若隊列長度≤0, 則有權去–(減減)搶購庫的商品庫存。若隊列長度在20件內,則通過業務代碼內的等待來等待隊頭的位置,然后獲得搶購權限。若隊列長度太長,則可以直接返 回,認為商品已被搶空。
這時插入一個運營配庫的時序,便于大家理解。該時序圖有詳細的說明和標注,就不展開了,如下圖:
此時,我們可以想象,若上游用戶的請求壓力是N,這個N會壓在業務層的搶購庫,俗話說“責任止于此”:P
2.3如何和第三方多方對賬
那么,我們回顧目錄“如何和第三方多方對賬”?
這里就要提到“TransactionData”這個庫了。
TransactionID為用戶維度的Session記錄,用戶從進入搶購業務開始,產生一個TransactionID,該 TransactionID生命周期截止到用戶跳轉去第三方支付為止。期間在生活服務中產生的瀏覽、搶購行為均會掛靠到該TransactionID之 下,并會在跳轉去第三方支付頁時攜帶該TransactionID憑證。最主要的是需要記錄下:用戶獲得商品名額后,跳轉去第三方時,這一行為。
考慮到TransactionID為搶購業務中,用戶操作行為的關鍵字段,值需要保證唯一。故此處可以采用發號器之類的能力。
我們構建的TransactionData記錄,就可以按照DailyRun的方式,與第三方對賬,來fix兩方數據庫庫存不一致等問題。
為什么會產生我們和資源方的庫存不一致,可能是因為用戶在第三方消費后,第三方callback我們時候失敗造成,也可能是因為用戶跳去第三方后并沒有真正支付,但我們的商品庫、搶購庫的庫存都已經減少造成的。原因可能有很多,對賬機制是必要的。
3.項目總結
最后,我們回顧回顧設計,壓力問題在業務層解決了,庫存不一致問題我們通過對賬機制解決了,產品的需求我們也通過旁路可配解決了,嗯,可以喝杯茶,發起評審,評審通過后開始寫代碼了。:)
感謝大家。分享中的數據強一致那塊,以及如何做取舍,都是很有意思的點,都可以展開聊很久,這里沒展開,大家可以事后查查資料。
4.Q&A
Q1.1:請問,防刷是怎么做的?一般搶購都有很大優惠。如果有人惡意刷,那正常的用戶就失去了購買的機會。比如,搶購的商品數為1000,有人 惡意刷了900,那只有100被正常用戶搶到。等惡意搶到的900經過后面的支付環節驗證后,可能已經過了搶購時間了。就算惡意搶到的900都支付成功, 那對正常用戶也是不公平的。
A1.1:在這個業務場景中,我們做的是商品展示、商品的購買權的發放,真正產生消費是在第三方。那么,用戶刷的問題,需要我們和第三方支付頁面 一起來控制。在用戶通過排隊機制,獲得了購買名額后,跳轉去第三方時候,我們按照和第三方約定的加密方式傳遞加密信息,第三方按照約定的解密方式解密成功 后才允許用戶支付,加密解密的過程中可以帶具有生命周期的內容。這樣,用戶在高頻請求支付頁面獲取商品時候,實際只有:1)加密對;2)第一次,才可能獲 得。不過,第三方都是為了銷售出商品,所以這類合作的成功幾率不大。惡意刷,的確會在我們的業務層面展示商品沒量了。導致想買的用戶沒了機會。但上面Q1 的回復,可以保證第三方不受損。對于你提到的這種刷的情況,若想在我們業務層規避,我想這就是一個通用的防SPAM的問題了。這塊自己真懂得不多。:P
Q1.2:要想準確的放刷,判斷的維度就多,邏輯就復雜;與之矛盾的,搶購要求的是響應迅速。
A1.2:對的,@裴寶慶|SinoIOV,搶購業務因為請求壓力大、熱門商品搶購并發高,切忌增加過多邏輯,切忌過多后端依賴,越簡單效果越 好。我們在設計系統時候,很多事不是咱們一個系統能cover的,多少需要一些前置模塊、能力的構建ready后,我們的系統才能run的不錯。還是建議 @裴寶慶|SinoIOV快構建帳號體系、用戶消費記錄這兩部分。
Q2:對賬這里,只是和第三方去對比商品的庫存量嗎,金額這里是否去對比?
A2:對賬,其實是對比的消費數據。避免出現我們統計今日產生了X件商品共價值Y的消費,第三方給出的是消費了N件共M價值的消費。避免金額不一 致,造成結算、分成等問題的出現。我想你問題中的庫存量的diff問題,還得靠第三方定期的通過我們數據層的接口來update他們提供的商品。其實在我 們的商品庫中,商品不一定只允許第三方提供,也可以允許第三方通過接口減少商品嘛,比如和一個賣水果的第三方合作,第三方上周發布說有100件,但這周線 下熱銷,只剩20件了,我們也應該允許第三方來update到一個低值。但這樣,我們的系統中就會復雜挺多。
Q3:防刷,避免第三方的推廣效果達不到問題。
A3:對的,用戶ID維度、IP維度,都是有效辦法。看具體場景。有帳號體系的業務,用用戶ID維度效果最好,借助存儲記錄下每個用戶的購買記 錄,來控制就好。市面上的電商網站,基本是搶購業務都需要登錄,并且限制每件商品單人購買數量,其實就是通過存儲記錄用戶的消費,并且再次產生消費前查詢 并增加代碼邏輯來控制。
Q4:每次搶購活動的時候用一套新的驗證碼?
A4:驗證碼這個東東,屬于圖靈測試嘛,只要測試方法好,并且盡可能保證每次產生的驗證信息從未出現過且無規律,就是好的驗證碼啦。:)
作者介紹:
呂毅(微博:呂毅),百度公司資深研發工程師,LAMP人。
2012年從新浪平臺架構部加入百度移動服務事業群組(MSG)手機百度產品線。在百度期間,隨著產品線發展和業務上QPS增長,架構設計方面略有所獲,對移動端業務、優化有獨特的理解和方法。