秒殺業務 架構優化之路

秒殺業務 架構優化之路

轉自infoq,原文連接nginx

秒殺業務爲何難作

IM 系統,例如QQ、微博,每一個人都讀本身的數據(好友列表、羣列表、我的信息)。微博系統,每一個人讀你關注的人的數據,一我的讀多我的的數據。程序員

秒殺系統,庫存只有一份,全部人會在集中的時間讀和寫這些數據,多我的讀一個數據。redis

例如小米手機每週二的秒殺,可能手機只有1w,可是瞬時進入的流量多是幾百幾千萬。又例如12306的搶票,票是有限的,庫存一份,瞬時流量很是多,都讀相同的庫存。讀寫衝突,鎖很是嚴重,這是秒殺業務難的地方。那咱們怎麼優化秒殺業務的架構呢?數據庫

優化方向

  1. 將請求儘可能攔截在系統上游(不要讓鎖衝突落到數據庫上去)。傳統秒殺之因此容易掛,請求都壓到了後端數據層,數據讀寫鎖衝突嚴重,併發高響應慢,幾乎全部的請求都超時,流量雖大,下單成功的流量甚小。以12306爲例,一趟火車其實只有2000張票,200w我的來買,基本沒有人能買成功,請求有效率爲0。
  2. 充分利用緩存,秒殺買票,這是一個典型的讀多寫少的應用場景,大部分請求是車次查詢,票查詢,下單和支付纔是寫請求。一趟火車其實只有2000張票,200w我的來買,最多2000我的下單成功,其餘人都是查詢庫存,寫比例只有0.1%,讀比例佔99.9%,很是適合使用緩存來優化。好,後續講講怎麼個「將請求儘可能攔截在系統上游」法,以及怎麼個「緩存」法,講講細節。

常見秒殺架構

常見的站點架構基本是這樣的(特別是流量上億的站點架構):後端

瀏覽器 -> 站點 -> 服務 -> 數據
  1. 瀏覽器端,最上層,會執行一些js代碼
  2. 站點層,這一層會訪問後端數據,拼HTML頁面返回給瀏覽器
  3. 服務層,向上遊屏蔽底層數據細節,提供數據訪問
  4. 數據層,最終的庫存是存在這裏,MYSQL是一個典型(固然還有緩存)

這圖雖然簡單,但能形象說明大流量高併發的秒殺業務架構,後面細細解析各個層級如何優化瀏覽器

各層次細節優化

第一層,客戶端怎麼優化(瀏覽器層,APP層)

問你們一個問題,你們都玩過微信的搖一搖搶紅包對吧,每次搖一搖,就會日後端發送請求麼?回顧咱們下單搶票的場景,點擊了「查詢」按鈕以後,系統那個卡呀,進度條漲的慢呀,做爲用戶,我會不自覺的再去點擊「查詢」,對麼?繼續點,繼續點,點點點……有用麼?無緣無故的增長了系統負載,一個用戶點5次,80%的請求是這麼多出來的,怎麼整?緩存

  • 產品層面,用戶點擊「查詢」或者「購票」後,按鈕置灰,禁止用戶重複提交請求;
  • JS層面,限制用戶在x秒以內只能提交一次請求;

APP層面,能夠作相似的事情,雖然你瘋狂的在搖微信,其實x秒才向後端發起一次請求。這就是所謂的「將請求儘可能攔截在系統上游」,越上游越好,瀏覽器層,APP層就給攔住,這樣就能擋住80%+的請求,這種辦法只能攔住普通用戶(但99%的用戶是普通用戶)對於羣內的高端程序員是攔不住的。服務器

FireBug一抓包,HTTP長啥樣都知道,JS是萬萬攔不住程序員寫for循環,調用HTTP接口的,這部分請求怎麼處理?微信

站點層面的請求攔截

怎麼攔截?怎麼防止程序員寫for循環調用,有去重依據麼?IP?cookie-id?…想複雜了,這類業務都須要登陸,用uid便可。在站點層面,對uid進行請求計數和去重,甚至不須要統一存儲計數,直接站點層內存存儲(這樣計數會不許,但最簡單)。一個uid,5秒只准透過1個請求,這樣又能攔住99%的for循環請求。cookie

5s只透過一個請求,其他的請求怎麼辦?緩存,頁面緩存,同一個uid,限制訪問頻度,作頁面緩存,x秒內到達站點層的請求,均返回同一頁面。同一個item的查詢,例如車次,作頁面緩存,x秒內到達站點層的請求,均返回同一頁面。如此限流,既能保證用戶有良好的用戶體驗(沒有返回404)又能保證系統的健壯性(利用頁面緩存,把請求攔截在站點層了)。

頁面緩存不必定要保證全部站點返回一致的頁面,直接放在每一個站點的內存也是能夠的。優勢是簡單,壞處是HTTP請求落到不一樣的站點,返回的車票數據可能不同,這是站點層的請求攔截與緩存優化。

好,這個方式攔住了寫for循環發HTTP請求的程序員,有些高端程序員(黑客)控制了10w個肉雞,手裏有10w個uid,同時發請求(先不考慮實名制的問題,小米搶手機不須要實名制),這下怎麼辦,站點層按照uid限流攔不住了。

服務層攔截(反正就是不要讓請求落到數據庫上去)

服務層怎麼攔截?大哥,我是服務層,我清楚的知道小米只有1萬部手機,我清楚的知道一列火車只有2000張車票,我透10w個請求去數據庫有什麼意義呢?沒錯,請求隊列!

對於寫請求,作請求隊列,每次只透有限的寫請求去數據層(下訂單,支付這樣的寫業務):

  • 1w部手機,只透1w個下單請求去db:
  • 3k張火車票,只透3k個下單請求去db。

若是均成功再放下一批,若是庫存不夠則隊列裏的寫請求所有返回「已售完」。

對於讀請求,怎麼優化?Cache抗,不論是memcached仍是redis,單機抗個每秒10w應該都是沒什麼問題的。如此限流,只有很是少的寫請求,和很是少的讀緩存mis的請求會透到數據層去,又有99.9%的請求被攔住了。

固然,還有業務規則上的一些優化。回想12306所作的,分時分段售票,原來統一10點賣票,如今8點,8點半,9點,...每隔半個小時放出一批:將流量攤勻。

其次,數據粒度的優化:你去購票,對於餘票查詢這個業務,票剩了58張,仍是26張,你真的關注麼,其實咱們只關心有票和無票?流量大的時候,作一個粗粒度的 「有票」「無票」緩存便可。

第三,一些業務邏輯的異步:例如 下單業務與 支付業務的分離。這些優化都是結合 業務 來的,我以前分享過一個觀點「一切脫離業務的架構設計都是耍流氓」架構的優化也要針對業務。

最後是數據層

瀏覽器攔截了80%,站點層攔截了99.9%並作了頁面緩存,服務層又作了寫請求隊列與數據緩存,每次透到數據庫層的請求都是可控的。db基本就沒什麼壓力了,閒庭信步,單機也能扛得住,仍是那句話,庫存是有限的,小米的產能有限,透這麼多請求來數據庫沒有意義。

所有透到數據庫,100w個下單,0個成功,請求有效率0%。透3k到數據,所有成功,請求有效率100%。

總結

上文描述的很是清楚了,沒什麼總結了,對於秒殺系統,再次重複下我我的經驗的兩個架構優化思路:

  1. 儘可能將請求攔截在系統的上游(越上游越好)
  2. 讀多寫少的場景多使用緩存(緩存抗讀壓力)

瀏覽器和APP:作限速。站點層:按照UID作限速,作頁面緩存。服務層:按照業務作寫請求隊列控制流量,作數據緩存。數據層:閒庭信步。以及結合業務作優化

6、Q&A

問題一、按你的架構,其實壓力最大的反而是站點層,假設真實有效的請求數有1000萬,不太可能限制請求鏈接數吧,那麼這部分的壓力怎麼處理?

答:每秒鐘的併發可能沒有1kw,假設有1kw,解決方案2個:

站點層是能夠經過加機器擴容的,最不濟1k臺機器來唄。
若是機器不夠,拋棄請求,拋棄50%(50%直接返回稍後再試),原則是要保護系統,不能讓全部用戶都失敗。

問題二、「控制了10w個肉雞,手裏有10w個uid,同時發請求」 這個問題怎麼解決哈?

答:上面說了,服務層寫請求隊列控制

問題3: 限制訪問頻次的緩存,是否也能夠用於搜索?例如A用戶搜索了「手機」,B用戶搜索「手機」,優先使用A搜索後生成的緩存頁面?

答:這個是能夠的,這個方法也常常用在 「動態」運營活動頁,例如短期推送4kw用戶app-push運營活動,作頁面緩存。

問題4:若是隊列處理失敗,如何處理?肉雞把隊列被撐爆了怎麼辦?

答:處理失敗返回下單失敗,讓用戶再試。隊列成本很低,爆了很難吧。最壞的狀況下,緩存了若干請求以後,後續請求都直接返回「無票」(隊列裏已經有100w請求了,都等着,再接受請求也沒有意義了)。

問題5:站點層過濾的話,是把uid請求數單獨保存到各個站點的內存中麼?若是是這樣的話,怎麼處理多臺服務器集羣通過負載均衡器將相同用戶的響應分佈到不一樣服務器的狀況呢?仍是說將站點層的過濾放到負載均衡前?

答:能夠放在內存,這樣的話看似一臺服務器限制了5s一個請求,全局來講(假設有10臺機器),實際上是限制了5s 10個請求,解決辦法:

加大限制(這是建議的方案,最簡單)
在nginx層作7層均衡,讓一個uid的請求儘可能落到同一個機器上

問題6:服務層過濾的話,隊列是服務層統一的一個隊列?仍是每一個提供服務的服務器各一個隊列?若是是統一的一個隊列的話,需不須要在各個服務器提交的請求入隊列前進行鎖控制?

答:能夠不用統一一個隊列,這樣的話每一個服務透過更少許的請求(總票數/服務個數),這樣簡單。統一一個隊列又複雜了。

問題7:秒殺以後的支付完成,以及未支付取消佔位,如何對剩餘庫存作及時的控制更新 ?

答:數據庫裏一個狀態,未支付。若是超過期間,例如45分鐘,庫存會從新會恢復(你們熟知的「回倉」),給咱們搶票的啓示是,開動秒殺後,45分鐘以後再試試看,說不定又有票喲。

問題8:不一樣的用戶 瀏覽同一個商品 落在不一樣的緩存實例 顯示的庫存徹底不同 請問老師怎麼作緩存數據一致 或者是容許髒讀?

答:目前的架構設計,請求落到不一樣的站點上,數據可能不一致(頁面緩存不同),這個業務場景能接受。但數據庫層面真實數據是沒問題的。

問題9:就算處於業務把優化考慮 「3k張火車票,只透3k個下單請求去db」那這3k個訂單就不會發生擁堵了嗎?

答:(1)數據庫抗3k個寫請求仍是ok的;(2)能夠數據拆分;(3)若是3k扛不住,服務層能夠控制透過去的併發數量,根據壓測狀況來吧,3k只是舉例;

問題10:若是在站點層或者服務層處理後臺失敗的話,需不須要考慮對這批處理失敗的請求作重放?仍是就直接丟棄?

答:別重放了,返回用戶查詢失敗或者下單失敗吧,架構設計原則之一是「fail fast」。

問題11:對於大型系統的秒殺,好比12306,同時進行的秒殺活動不少,如何分流?

答:垂直拆分

問題12:額外又想到一個問題。這套流程作成同步仍是異步的?若是是同步的話,應該還存在會有響應反饋慢的狀況。但若是是異步的話,如何控制可以將響應結果返回正確的請求方?

答:用戶層面確定是同步的(用戶的HTTP請求是夯住的),服務層面能夠同步能夠異步。

問題13:秒殺羣提問:減庫存是在那個階段減呢?若是是下單鎖庫存的話,大量惡意用戶下單鎖庫存而不支付如何處理呢?

答:數據庫層面寫請求量很低,還好,下單不支付,等時間過完再「回倉」,以前提過了。

講師介紹

沈劍,58到家技術總監,互聯網架構技術專家,「架構師之路」公衆號做者,曾屢次表明58集團做爲演講嘉賓參與業內技術會議,分享58的架構技術。

本文根據ArchSummit微信大講堂上邀請講師,聯想資深雲架構師樓煒爲你們線上分享內容整理而成,掃描下方二維碼,回覆「架構師」,獲取參與方式,加入圍觀大牛線上分享,同步大會籌備近況……

相關文章
相關標籤/搜索