java學習

          java學習

           

          秒殺系統架構優化思路

          一、秒殺業務為什么難做

          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是一個典型(當然還有會緩存)

          這個圖雖然簡單,但能形象的說明大流量高并發的秒殺業務架構,大家要記得這一張圖。

          后面細細解析各個層級怎么優化。

           

          四、各層次優化細節

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

          問大家一個問題,大家都玩過微信的搖一搖搶紅包對吧,每次搖一搖,就會往后端發送請求么?回顧我們下單搶票的場景,點擊了“查詢”按鈕之后,系統那個卡呀,進度條漲的慢呀,作為用戶,我會不自覺的再去點擊“查詢”,對么?繼續點,繼續點,點點點。。。有用么?平白無故的增加了系統負載,一個用戶點5次,80%的請求是這么多出來的,怎么整?

          a產品層面,用戶點擊“查詢”或者“購票”后,按鈕置灰,禁止用戶重復提交請求;

          bJS層面,限制用戶在x秒之內只能提交一次請求

          APP層面,可以做類似的事情,雖然你瘋狂的在搖微信,其實x秒才向后端發起一次請求。這就是所謂的“將請求盡量攔截在系統上游”,越上游越好,瀏覽器層,APP層就給攔住,這樣就能擋住80%+的請求,這種辦法只能攔住普通用戶(但99%的用戶是普通用戶)對于群內的高端程序員是攔不住的firebug一抓包,http長啥樣都知道,js是萬萬攔不住程序員寫for循環,調用http接口的,這部分請求怎么處理?

           

          第二層,站點層面的請求攔截

          怎么攔截?怎么防止程序員寫for循環調用,有去重依據么?ipcookie-id?…想復雜了,這類業務都需要登錄,用uid即可。在站點層面,uid進行請求計數和去重,甚至不需要統一存儲計數,直接站點層內存存儲(這樣計數會不準,但最簡單)。一個uid5秒只準透過1個請求,這樣又能攔住99%for循環請求。

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

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

           

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

           

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

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

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

          1w部手機,只透1w個下單請求去db

          3k張火車票,只透3k個下單請求去db

          如果均成功再放下一批,如果庫存不夠則隊列里的寫請求全部返回“已售完”。

           

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

           

          當然,還有業務規則上的一些優化?;叵?/span>12306所做的,分時分段售票,原來統一10點賣票,現在8點,8點半,9點,...每隔半個小時放出一批:將流量攤勻。

          其次,數據粒度的優化:你去購票,對于余票查詢這個業務,票剩了58張,還是26張,你真的關注么,其實我們只關心有票和無票?流量大的時候,做一個粗粒度的“有票”“無票”緩存即可。

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

           

          好了,最后是數據庫層

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

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

           

          五、總結

          上文應該描述的非常清楚了,沒什么總結了,對于秒殺系統,再次重復下我個人經驗的兩個架構優化思路:

          1盡量將請求攔截在系統上游(越上游越好);

          2讀多寫少的常用多使用緩存(緩存抗讀壓力);

          瀏覽器和APP:做限速

          站點層:按照uid做限速,做頁面緩存

          服務層:按照業務做寫請求隊列控制流量,做數據緩存

          數據層:閑庭信步

          并且:結合業務做優化

           

          六、Q&A

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

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

          1)站點層是可以通過加機器擴容的,最不濟1k臺機器來唄。

          2)如果機器不夠,拋棄請求,拋棄50%50%直接返回稍后再試),原則是要保護系統,不能讓所有用戶都失敗。

           

          問題2、“控制了10w個肉雞,手里有10wuid,同時發請求” 這個問題怎么解決哈?

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

           

          問題3限制訪問頻次的緩存,是否也可以用于搜索?例如A用戶搜索了“手機”,B用戶搜索“手機”,優先使用A搜索后生成的緩存頁面?

          答:這個是可以的,這個方法也經常用在“動態”運營活動頁,例如短時間推送4kw用戶app-push運營活動,做頁面緩存。

           

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

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

           

          問題5:站點層過濾的話,是把uid請求數單獨保存到各個站點的內存中么?如果是這樣的話,怎么處理多臺服務器集群經過負載均衡器將相同用戶的響應分布到不同服務器的情況呢?還是說將站點層的過濾放到負載均衡前?

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

          1)加大限制(這是建議的方案,最簡單)

          2)在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、秒殺群提問:減庫存是在那個階段減呢?如果是下單鎖庫存的話,大量惡意用戶下單鎖庫存而不支付如何處理呢?

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

           ==【完】==

          posted on 2017-11-06 17:35 楊軍威 閱讀(184) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          導航

          統計

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 庆阳市| 呼玛县| 准格尔旗| 万宁市| 汶上县| 八宿县| 余姚市| 安庆市| 临沂市| 广东省| 团风县| 宁都县| 东平县| 五原县| 芜湖县| 乾安县| 高雄市| 靖宇县| 尖扎县| 甘孜| 绵阳市| 诏安县| 桃园县| 牟定县| 卢湾区| 来宾市| 西青区| 华池县| 营口市| 乐昌市| 汽车| 广德县| 新乡市| 昭通市| 巴林右旗| 江山市| 德阳市| 漯河市| 永州市| 赣州市| 顺昌县|