Java秒殺業務架構設計之路

来源:https://www.cnblogs.com/jurendage/archive/2018/02/06/8420714.html
-Advertisement-
Play Games

一、秒殺業務為什麼難做 IM系統,例如QQ或者微博,每個人都讀自己的數據(好友列表、群列表、個人信息)。 微博系統,每個人讀你關註的人的數據,一個人讀多個人的數據。 秒殺系統,庫存只有一份,所有人會在集中的時間讀和寫這些數據,多個人讀一個數據。 例如小米手機每周二的秒殺,可能手機只有1萬部,但瞬時進 ...


一、秒殺業務為什麼難做

IM系統,例如QQ或者微博,每個人都讀自己的數據(好友列表、群列表、個人信息)。

微博系統,每個人讀你關註的人的數據,一個人讀多個人的數據。

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

例如小米手機每周二的秒殺,可能手機只有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%的請求是這麼多出來的,怎麼整?

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

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

FireBug一抓包,HTTP長啥樣都知道,JS是萬萬攔不住程式員寫for迴圈,調用HTTP介面的,這部分請求怎麼處理?

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

怎麼攔截?怎麼防止程式員寫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. 讀多寫少的常用多使用緩存(緩存抗讀壓力);

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

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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、window.screen.height window.screen.height:設備顯示屏的高度 (1)解析度為1080px的顯示屏 (2)手機屏 2、window.screen.availHeight 屏幕的可用高度 (1)解析度為1080px的顯示屏 (2)手機屏 3、document. ...
  • 容器的屬性 項目的屬性 ...
  • target:指定框架集中的哪個框架來裝在另一個資源,該屬性可以是_self、_blank、_top、_parent四個值,分別代表使用自身、新視窗、頂層框架、父框架來裝載新資源。 alt只是在圖片無法載入的還是才會顯示出提示文字,如果想讓圖片無論怎樣都顯示,用title 如果希望獲得最佳表單性能, ...
  • 對於剛接觸ubuntu的同學來說,一切都是新的,一切都是那麼熟悉而又不熟悉的.不管是作為一個前端工程師還是一個後端工程師,我相信大家知道nodejs,但是如果希望自己能夠在ubuntu上面使用nodejs,是需要給點功夫去做的. 當然對於一個ubuntuer來說 這個命令就再熟悉不過了,也是經常用的 ...
  • 登錄認證幾乎是任何一個系統的標配,web 系統、APP、PC 客戶端等,好多都需要註冊、登錄、授權認證。 場景說明 以一個電商系統,假設淘寶為例,如果我們想要下單,首先需要註冊一個賬號。擁有了賬號之後,我們需要輸入用戶名(比如手機號或郵箱)、密碼完成登錄過程。之後如果你在一段時間內再次進入系統,是不 ...
  • https://www.cnblogs.com/jiese/p/3164940.html 將抽象部份與它的實現部份分離,使它們都可以獨立地變化。 橋接模式號稱設計模式中最難理解的模式之一,關鍵就是這個抽象和實現的分離非常讓人奇怪,大部分人剛看到這個定義的時候都會認為實現就是繼承自抽象,那怎麼可能將他 ...
  • 該系列教程系個人原創,並完整發佈在個人官網 "劉江的博客和教程" 所有轉載本文者,需在頂部顯著位置註明原作者及www.liujiangblog.com官網地址。 Python及Django學習QQ群:453131687 很多時候,我們都不是從‘一窮二白’開始編寫模型的,有時候可以從第三方庫中繼承,有 ...
  • 通過前面三篇的分析,我們深入瞭解了AbstractQueuedSynchronizer的內部結構和一些設計理念,知道了AbstractQueuedSynchronizer內部維護了一個同步狀態和兩個排隊區,這兩個排隊區分別是同步隊列和條件隊列。我們還是拿公共廁所做比喻,同步隊列是主要的排隊區,如果公 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...