相信不少人會被這個問題困擾,分享大家一篇這樣的文章,希望能夠幫到你!
一、秒殺業務為什麼難做?
1)im系統,例如qq或者微博,每個人都讀自己的資料(好友列表、群列表、個人資訊);
2)微博系統,每個人讀你關注的人的資料,乙個人讀多個人的資料;
3)秒殺系統,庫存只有乙份,所有人會在集中的時間讀和寫這些資料,多個人讀乙個資料。
例如:小公尺手機每週二的秒殺,可能手機只有1萬部,但瞬時進入的流量可能是幾百幾千萬。
又例如:12306搶票,票是有限的,庫存乙份,瞬時流量非常多,都讀相同的庫存。讀寫衝突,鎖非常嚴重,這是秒殺業務難的地方。那我們怎麼優化秒殺業務的架構呢?
二、優化方向
優化方向有兩個(今天就講這兩個點):
(1)將請求盡量攔截在系統上游(不要讓鎖衝突落到資料庫上去)。傳統秒殺系統之所以掛,請求都壓倒了後端資料層,資料讀寫鎖衝突嚴重,併發高響應慢,幾乎所有請求都超時,流量雖大,下單成功的有效流量甚小。以12306為例,一趟火車其實只有2000張票,200w個人來買,基本沒有人能買成功,請求有效率為0。
(2)充分利用快取,秒殺買票,這是乙個典型的讀多寫少的應用場景,大部分請求是車次查詢,票查詢,下單和支付才是寫請求。一趟火車其實只有2000張票,200w個人來買,最多2000個人下單成功,其他人都是查詢庫存,寫比例只有0.1%,讀比例佔99.9%,非常適合使用快取來優化。好,後續講講怎麼個「將請求盡量攔截在系統上游」法,以及怎麼個「快取」法,講講細節。
三、常見秒殺架構
常見的站點架構基本是這樣的(絕對不畫忽悠類的架構圖)
(1)瀏覽器端,最上層,會執行到一些js**
(2)站點層,這一層會訪問後端資料,拼html頁面返回給瀏覽器
(3)服務層,向上游遮蔽底層資料細節,提供資料訪問
(4)資料層,最終的庫存是存在這裡的,mysql是乙個典型(當然還有會快取)
這個圖雖然簡單,但能形象的說明大流量高併發的秒殺業務架構,大家要記得這一張圖。後面細細解析各個層級怎麼優化。
(a)產品層面,使用者點選「查詢」或者「購票」後,按鈕置灰,禁止使用者重複提交請求;
(b)js層面,限制使用者在x秒之內只能提交一次請求;
第二層,站點層面的請求攔截
怎麼攔截?怎麼防止程式設計師寫for迴圈呼叫,有去重依據麼?ip?cookie-id?…想複雜了,這類業務都需要登入,用uid即可。在站點層面,對uid進行請求計數和去重,甚至不需要統一儲存計數,直接站點層記憶體儲存(這樣計數會不准,但最簡單)。乙個uid,5秒只准透過1個請求,這樣又能攔住99%的for迴圈請求。
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)讀多寫少的常用多使用快取(快取抗讀壓力);
站點層:按照uid做限速,做頁面快取
服務層:按照業務做寫請求佇列控制流量,做資料快取
資料層:閑庭信步
並且:結合業務做優化
六、q&a
問題1、按你的架構,其實壓力最大的反而是站點層,假設真實有效的請求數有1000萬,不太可能限制請求連線數吧,那麼這部分的壓力怎麼處理?
答:每秒鐘的併發可能沒有1kw,假設有1kw,解決方案2個:
(1)站點層是可以通過加機器擴容的,最不濟1k臺機器來唄。
(2)如果機器不夠,拋棄請求,拋棄50%(50%直接返回稍後再試),原則是要保護系統,不能讓所有使用者都失敗。
問題2、「控制了10w個肉雞,手裡有10w個uid,同時發請求」 這個問題怎麼解決哈?
答:上面說了,服務層寫請求佇列控制
問題3:限制訪問頻次的快取,是否也可以用於搜尋?例如a使用者搜尋了「手機」,b使用者搜尋「手機」,優先使用a搜尋後生成的快取頁面?
問題4:如果佇列處理失敗,如何處理?肉雞把佇列被撐爆了怎麼辦?
答:處理失敗返回下單失敗,讓使用者再試。佇列成本很低,爆了很難吧。最壞的情況下,快取了若干請求之後,後續請求都直接返回「無票」(佇列裡已經有100w請求了,都等著,再接受請求也沒有意義了)
問題5:站點層過濾的話,是把uid請求數單獨儲存到各個站點的記憶體中麼?如果是這樣的話,怎麼處理多台伺服器集群經過負載均衡器將相同使用者的響應分布到不同伺服器的情況呢?還是說將站點層的過濾放到負載均衡前?
答:可以放在記憶體,這樣的話看似一台伺服器限制了5s乙個請求,全域性來說(假設有10臺機器),其實是限制了5s 10個請求,解決辦法:
1)加大限制(這是建議的方案,最簡單)
2)在nginx層做7層均衡,讓乙個uid的請求盡量落到同乙個機器上
問題6:服務層過濾的話,佇列是服務層統一的乙個佇列?還是每個提供服務的伺服器各乙個佇列?如果是統一的乙個佇列的話,需不需要在各個伺服器提交的請求入佇列前進行鎖控制?
答:可以不用統一乙個佇列,這樣的話每個服務透過更少量的請求(總票數/服務個數),這樣簡單。統一乙個佇列又複雜了。
問題7:秒殺之後的支付完成,以及未支付取消佔位,如何對剩餘庫存做及時的控制更新?
問題8:不同的使用者瀏覽同乙個商品 落在不同的快取例項顯示的庫存完全不一樣 請問老師怎麼做快取資料一致或者是允許髒讀?
答:目前的架構設計,請求落到不同的站點上,資料可能不一致(頁面快取不一樣),這個業務場景能接受。但資料庫層面真實資料是沒問題的。
問題9:就算處於業務把優化考慮「3k張火車票,只透3k個下單請求去db」那這3k個訂單就不會發生擁堵了嗎?
答:(1)資料庫抗3k個寫請求還是ok的;(2)可以資料拆分;(3)如果3k扛不住,服務層可以控制透過去的併發數量,根據壓測情況來吧,3k只是舉例;
問題10;如果在站點層或者服務層處理後台失敗的話,需不需要考慮對這批處理失敗的請求做重放?還是就直接丟棄?
答:別重放了,返回使用者查詢失敗或者下單失敗吧,架構設計原則之一是「fail fast」。
問題11.對於大型系統的秒殺,比如12306,同時進行的秒殺活動很多,如何分流?
答:垂直拆分
問題12、額外又想到乙個問題。這套流程做成同步還是非同步的?如果是同步的話,應該還存在會有響應反饋慢的情況。但如果是非同步的話,如何控制能夠將響應結果返回正確的請求方?
答:使用者層面肯定是同步的(使用者的http請求是夯住的),服務層面可以同步可以非同步。
問題13、秒殺群提問:減庫存是在那個階段減呢?如果是下單鎖庫存的話,大量惡意使用者下單鎖庫存而不支付如何處理呢?
答:資料庫層面寫請求量很低,還好,下單不支付,等時間過完再「回倉」,之前提過了。
**:
如何解決高併發
如何解決高併發 快取靜態頁面 伺服器分離 優化資料庫結構,多做索引 資料庫集群和庫表雜湊 不要頻繁得使用new物件,能使用單例模式就使用,對於utility型別的類通過靜態方法來訪問。使用執行緒安全的集合物件vector hashtable 使用執行緒池 盡量使用快取,包括使用者快取,資訊快取等,多...
如何解決秒殺的高併發和超賣的問題?
秒殺或搶購活動一般會經過 預約 搶訂單 支付 這3個大環節,而其中 搶訂單 這個環節是最考驗業務提供方的抗壓能力的。搶訂單環節一般會帶來2個問題 1 高併發 2 超賣 任何商品都會有數量上限,如何避免成功下訂單買到商品的人數不超過商品數量的上限,這是每個搶購活動都要面臨的難題。擴容 加機器,這是最簡...
2 7 如何解決高併發?
1 cdn加速 把靜態資源放到別人伺服器上 2 後台資料庫使用mysql redis mysql是持久化儲存,存放在磁碟裡面,檢索的話,會涉及到一定的io,為了解決這個瓶頸,於是出現了快取,比如現在常用的 redis。首先,使用者訪問快取,如果未命中,就去訪問mysql,之後將mysql中的資料複製...