前言
秒殺大家都不陌生挤土。自2011年首次出現(xiàn)以來,無論是雙十一購物還是 12306 搶票误算,秒殺場景已隨處可見耕挨。簡單來說,秒殺就是在同一時刻大量請求爭搶購買同一商品并完成交易的過程尉桩。從架構(gòu)視角來看筒占,秒殺系統(tǒng)本質(zhì)是一個高性能、高一致蜘犁、高可用的三高系統(tǒng)翰苫。而打造并維護一個超大流量的秒殺系統(tǒng)需要進行哪些關(guān)注,就是本文討論的話題这橙。
整體思考
首先從高維度出發(fā)奏窑,整體思考問題。秒殺無外乎解決兩個核心問題屈扎,一是并發(fā)讀埃唯,一是并發(fā)寫,對應到架構(gòu)設(shè)計鹰晨,就是高可用墨叛、一致性和高性能的要求。關(guān)于秒殺系統(tǒng)的設(shè)計思考模蜡,本文即基于此 3 層依次推進漠趁,簡述如下——
高性能。秒殺涉及高讀和高寫的支持忍疾,如何支撐高并發(fā)闯传,如何抵抗高IOPS?核心優(yōu)化理念其實是類似的:高讀就盡量"少讀"或"讀少"卤妒,高寫就數(shù)據(jù)拆分甥绿。本文將從動靜分離、熱點優(yōu)化以及服務端性能優(yōu)化 3 個方面展開
一致性则披。秒殺的核心關(guān)注是商品庫存共缕,有限的商品在同一時間被多個請求同時扣減,而且要保證準確性收叶,顯而易見是一個難題骄呼。如何做到既不多又不少?本文將從業(yè)界通用的幾種減庫存方案切入,討論一致性設(shè)計的核心邏輯
高可用蜓萄。大型分布式系統(tǒng)在實際運行過程中面對的工況是非常復雜的隅茎,業(yè)務流量的突增、依賴服務的不穩(wěn)定嫉沽、應用自身的瓶頸苍鲜、物理資源的損壞等方方面面都會對系統(tǒng)的運行帶來大大小小的的沖擊爬橡。如何保障應用在復雜工況環(huán)境下還能高效穩(wěn)定運行,如何預防和面對突發(fā)問題,系統(tǒng)設(shè)計時應該從哪些方面著手戴涝?本文將從架構(gòu)落地的全景視角進行關(guān)注思考
高性能
1 動靜分離
大家可能會注意到洪唐,秒殺過程中你是不需要刷新整個頁面的谐鼎,只有時間在不停跳動唯咬。這是因為一般都會對大流量的秒殺系統(tǒng)做系統(tǒng)的靜態(tài)化改造,即數(shù)據(jù)意義上的動靜分離咬崔。動靜分離三步走:1税稼、數(shù)據(jù)拆分;2垮斯、靜態(tài)緩存郎仆;3、數(shù)據(jù)整合兜蠕。
1.1 數(shù)據(jù)拆分
動靜分離的首要目的是將動態(tài)頁面改造成適合緩存的靜態(tài)頁面扰肌。因此第一步就是分離出動態(tài)數(shù)據(jù),主要從以下 2 個方面進行:
用戶熊杨。用戶身份信息包括登錄狀態(tài)以及登錄畫像等曙旭,相關(guān)要素可以單獨拆分出來,通過動態(tài)請求進行獲群锇肌夷狰;與之相關(guān)的廣平推薦,如用戶偏好郊霎、地域偏好等,同樣可以通過異步方式進行加載
時間爷绘。秒殺時間是由服務端統(tǒng)一管控的书劝,可以通過動態(tài)請求進行獲取
這里你可以打開電商平臺的一個秒殺頁面,看看這個頁面里都有哪些動靜數(shù)據(jù)土至。
1.2 靜態(tài)緩存
分離出動靜態(tài)數(shù)據(jù)之后购对,第二步就是將靜態(tài)數(shù)據(jù)進行合理的緩存,由此衍生出兩個問題:1陶因、怎么緩存骡苞;2、哪里緩存
1.2.1 怎么緩存
靜態(tài)化改造的一個特點是直接緩存整個 HTTP 連接而不是僅僅緩存靜態(tài)數(shù)據(jù),如此一來解幽,Web 代理服務器根據(jù)請求 URL贴见,可以直接取出對應的響應體然后直接返回,響應過程無需重組 HTTP 協(xié)議躲株,也無需解析 HTTP 請求頭片部。而作為緩存鍵,URL唯一化是必不可少的霜定,只是對于商品系統(tǒng)档悠,URL 天然是可以基于商品 ID 來進行唯一標識的。
1.2.2 哪里緩存
靜態(tài)數(shù)據(jù)緩存到哪里呢望浩?可以有三種方式:1辖所、瀏覽器;2磨德、CDN 奴烙;3、服務端剖张。
瀏覽器當然是第一選擇切诀,但用戶的瀏覽器是不可控的,主要體現(xiàn)在如果用戶不主動刷新搔弄,系統(tǒng)很難主動地把消息推送給用戶(注意幅虑,當討論靜態(tài)數(shù)據(jù)時,潛臺詞是 “相對不變”顾犹,言外之意是 “可能會變”)倒庵,如此可能會導致用戶端在很長一段時間內(nèi)看到的信息都是錯誤的。對于秒殺系統(tǒng)炫刷,保證緩存可以在秒級時間內(nèi)失效是不可或缺的擎宝。
服務端主要進行動態(tài)邏輯計算及加載,本身并不擅長處理大量連接浑玛,每個連接消耗內(nèi)存較多绍申,同時 Servlet 容器解析 HTTP 較慢,容易侵占邏輯計算資源顾彰;另外极阅,靜態(tài)數(shù)據(jù)下沉至此也會拉長請求路徑。
因此通常將靜態(tài)數(shù)據(jù)緩存在 CDN涨享,其本身更擅長處理大并發(fā)的靜態(tài)文件請求筋搏,既可以做到主動失效,又離用戶盡可能近厕隧,同時規(guī)避 Java 語言層面的弱點奔脐。需要注意的是俄周,上 CDN 有以下幾個問題需要解決:
失效問題。任何一個緩存都應該是有時效的髓迎,尤其對于一個秒殺場景峦朗。所以,系統(tǒng)需要保證全國各地的 CDN 在秒級時間內(nèi)失效掉緩存信息竖般,這實際對 CDN 的失效系統(tǒng)要求是很高的
命中率問題甚垦。高命中是緩存系統(tǒng)最為核心的性能要求,不然緩存就失去了意義涣雕。如果將數(shù)據(jù)放到全國各地的 CDN 艰亮,勢必會導致請求命中同一個緩存的可能性降低,那么命中率就成為一個問題
因此挣郭,將數(shù)據(jù)放到全國所有的 CDN 節(jié)點是不太現(xiàn)實的迄埃,失效問題、命中率問題都會面臨比較大的挑戰(zhàn)兑障。更為可行的做法是選擇若干 CDN 節(jié)點進行靜態(tài)化改造侄非,節(jié)點的選取通常需要滿足以下幾個條件:
臨近訪問量集中的地區(qū)
距離主站較遠的地區(qū)
節(jié)點與主站間網(wǎng)絡質(zhì)量良好的地區(qū)
基于以上因素,選擇 CDN 的二級緩存比較合適流译,因為二級緩存數(shù)量偏少逞怨,容量也更大,訪問量相對集中福澡,這樣就可以較好解決緩存的失效問題以及命中率問題叠赦,是當前比較理想的一種 CDN 化方案。部署方式如下圖所示:
1.3 數(shù)據(jù)整合
分離出動靜態(tài)數(shù)據(jù)之后革砸,前端如何組織數(shù)據(jù)頁就是一個新的問題除秀,主要在于動態(tài)數(shù)據(jù)的加載處理,通常有兩種方案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案算利。
ESI 方案:Web 代理服務器上請求動態(tài)數(shù)據(jù)册踩,并將動態(tài)數(shù)據(jù)插入到靜態(tài)頁面中,用戶看到頁面時已經(jīng)是一個完整的頁面效拭。這種方式對服務端性能要求高暂吉,但用戶體驗較好
CSI 方案:Web 代理服務器上只返回靜態(tài)頁面,前端單獨發(fā)起一個異步 JS 請求動態(tài)數(shù)據(jù)允耿。這種方式對服務端性能友好借笙,但用戶體驗稍差
1.4 小結(jié)
動靜分離對于性能的提升,抽象起來只有兩點较锡,一是數(shù)據(jù)要盡量少,以便減少沒必要的請求盗痒,二是路徑要盡量短蚂蕴,以便提高單次請求的效率低散。具體方法其實就是基于這個大方向進行的。
2 熱點優(yōu)化
熱點分為熱點操作和熱點數(shù)據(jù)骡楼,以下分開進行討論熔号。
2.1 熱點操作
零點刷新、零點下單鸟整、零點添加購物車等都屬于熱點操作引镊。熱點操作是用戶的行為,不好改變篮条,但可以做一些限制保護弟头,比如用戶頻繁刷新頁面時進行提示阻斷。
2.2 熱點數(shù)據(jù)
熱點數(shù)據(jù)的處理三步走涉茧,一是熱點識別赴恨,二是熱點隔離,三是熱點優(yōu)化伴栓。
2.2.1 熱點識別
熱點數(shù)據(jù)分為靜態(tài)熱點和動態(tài)熱點伦连,具體如下:
靜態(tài)熱點:能夠提前預測的熱點數(shù)據(jù)。大促前夕钳垮,可以根據(jù)大促的行業(yè)特點惑淳、活動商家等緯度信息分析出熱點商品,或者通過賣家報名的方式提前篩選饺窿;另外歧焦,還可以通過技術(shù)手段提前預測,例如對買家每天訪問的商品進行大數(shù)據(jù)計算短荐,然后統(tǒng)計出 TOP N 的商品倚舀,即可視為熱點商品
動態(tài)熱點:無法提前預測的熱點數(shù)據(jù)。冷熱數(shù)據(jù)往往是隨實際業(yè)務場景發(fā)生交替變化的忍宋,尤其是如今直播賣貨模式的興起——帶貨商臨時做一個廣告痕貌,就有可能導致一件商品在短時間內(nèi)被大量購買。由于此類商品日常訪問較少糠排,即使在緩存系統(tǒng)中一段時間后也會被逐出或過期掉舵稠,甚至在db中也是冷數(shù)據(jù)。瞬時流量的涌入入宦,往往導致緩存被擊穿哺徊,請求直接到達DB,引發(fā)DB壓力過大
因此秒殺系統(tǒng)需要實現(xiàn)熱點數(shù)據(jù)的動態(tài)發(fā)現(xiàn)能力乾闰,一個常見的實現(xiàn)思路是:
異步采集交易鏈路各個環(huán)節(jié)的熱點 Key 信息落追,如 Nginx采集訪問URL或 Agent 采集熱點日志(一些中間件本身已具備熱點發(fā)現(xiàn)能力),提前識別潛在的熱點數(shù)據(jù)
聚合分析熱點數(shù)據(jù)涯肩,達到一定規(guī)則的熱點數(shù)據(jù)轿钠,通過訂閱分發(fā)推送到鏈路系統(tǒng)巢钓,各系統(tǒng)根據(jù)自身需求決定如何處理熱點數(shù)據(jù),或限流或緩存疗垛,從而實現(xiàn)熱點保護
需要注意的是:
熱點數(shù)據(jù)采集最好采用異步方式症汹,一方面不會影響業(yè)務的核心交易鏈路,一方面可以保證采集方式的通用性
熱點發(fā)現(xiàn)最好做到秒級實時贷腕,這樣動態(tài)發(fā)現(xiàn)才有意義背镇,實際上也是對核心節(jié)點的數(shù)據(jù)采集和分析能力提出了較高的要求
2.2.2 熱點隔離
熱點數(shù)據(jù)識別出來之后,第一原則就是將熱點數(shù)據(jù)隔離出來泽裳,不要讓 1% 影響到另外的 99%瞒斩,可以基于以下幾個層次實現(xiàn)熱點隔離:
業(yè)務隔離。秒殺作為一種營銷活動诡壁,賣家需要單獨報名济瓢,從技術(shù)上來說,系統(tǒng)可以提前對已知熱點做緩存預熱
系統(tǒng)隔離妹卿。系統(tǒng)隔離是運行時隔離旺矾,通過分組部署和另外 99% 進行分離,另外秒殺也可以申請單獨的域名夺克,入口層就讓請求落到不同的集群中
數(shù)據(jù)隔離箕宙。秒殺數(shù)據(jù)作為熱點數(shù)據(jù),可以啟用單獨的緩存集群或者DB服務組铺纽,從而更好的實現(xiàn)橫向或縱向能力擴展
當然柬帕,實現(xiàn)隔離還有很多種辦法。比如狡门,可以按照用戶來區(qū)分陷寝,為不同的用戶分配不同的 Cookie,入口層路由到不同的服務接口中其馏;再比如凤跑,域名保持一致,但后端調(diào)用不同的服務接口叛复;又或者在數(shù)據(jù)層給數(shù)據(jù)打標進行區(qū)分等等仔引,這些措施的目的都是把已經(jīng)識別的熱點請求和普通請求區(qū)分開來。
2.2.3 熱點優(yōu)化
熱點數(shù)據(jù)隔離之后褐奥,也就方便對這 1% 的請求做針對性的優(yōu)化咖耘,方式無外乎兩種:
緩存:熱點緩存是最為有效的辦法。如果熱點數(shù)據(jù)做了動靜分離撬码,那么可以長期緩存靜態(tài)數(shù)據(jù)
限流:流量限制更多是一種保護機制儿倒。需要注意的是,各服務要時刻關(guān)注請求是否觸發(fā)限流并及時進行review
2.2.4 小結(jié)
數(shù)據(jù)的熱點優(yōu)化與動靜分離是不一樣的呜笑,熱點優(yōu)化是基于二八原則對數(shù)據(jù)進行了縱向拆分义桂,以便進行針對性地處理找筝。熱點識別和隔離不僅對“秒殺”這個場景有意義蹈垢,對其他的高性能分布式系統(tǒng)也非常有參考價值慷吊。
3 系統(tǒng)優(yōu)化
對于一個軟件系統(tǒng),提高性能可以有很多種手段曹抬,如提升硬件水平溉瓶、調(diào)優(yōu)JVM 性能,這里主要關(guān)注代碼層面的性能優(yōu)化——
減少序列化:減少 Java 中的序列化操作可以很好的提升系統(tǒng)性能谤民。序列化大部分是在 RPC 階段發(fā)生堰酿,因此應該盡量減少 RPC 調(diào)用,一種可行的方案是將多個關(guān)聯(lián)性較強的應用進行 “合并部署”张足,從而減少不同應用之間的 RPC 調(diào)用(微服務設(shè)計規(guī)范)
直接輸出流數(shù)據(jù):只要涉及字符串的I/O操作触创,無論是磁盤 I/O 還是網(wǎng)絡 I/O,都比較耗費 CPU 資源为牍,因為字符需要轉(zhuǎn)換成字節(jié)哼绑,而這個轉(zhuǎn)換又必須查表編碼。所以對于常用數(shù)據(jù)碉咆,比如靜態(tài)字符串抖韩,推薦提前編碼成字節(jié)并緩存,具體到代碼層面就是通過 OutputStream() 類函數(shù)從而減少數(shù)據(jù)的編碼轉(zhuǎn)換疫铜;另外茂浮,熱點方法toString()不要直接調(diào)用ReflectionToString實現(xiàn),推薦直接硬編碼壳咕,并且只打印DO的基礎(chǔ)要素和核心要素
裁剪日志異常堆棧:無論是外部系統(tǒng)異常還是應用本身異常席揽,都會有堆棧打出,超大流量下谓厘,頻繁的輸出完整堆棧幌羞,只會加劇系統(tǒng)當前負載∨优唬可以通過日志配置文件控制異常堆棧輸出的深度
去組件框架:極致優(yōu)化要求下新翎,可以去掉一些組件框架,比如去掉傳統(tǒng)的 MVC 框架住练,直接使用 Servlet 處理請求地啰。這樣可以繞過一大堆復雜且用處不大的處理邏輯,節(jié)省毫秒級的時間讲逛,當然亏吝,需要合理評估你對框架的依賴程度
4 總結(jié)一下
性能優(yōu)化需要一個基準值,所以系統(tǒng)還需要做好應用基線盏混,比如性能基線(何時性能突然下降)蔚鸥、成本基線(去年大促用了多少機器)惜论、鏈路基線(核心流程發(fā)生了哪些變化),通過基線持續(xù)關(guān)注系統(tǒng)性能止喷,促使系統(tǒng)在代碼層面持續(xù)提升編碼質(zhì)量馆类、業(yè)務層面及時下掉不合理調(diào)用、架構(gòu)層面不斷優(yōu)化改進弹谁。
一致性
秒殺系統(tǒng)中乾巧,庫存是個關(guān)鍵數(shù)據(jù),賣不出去是個問題预愤,超賣更是個問題沟于。秒殺場景下的一致性問題,主要就是庫存扣減的準確性問題植康。
1 減庫存的方式
電商場景下的購買過程一般分為兩步:下單和付款旷太。“提交訂單”即為下單销睁,“支付訂單”即為付款供璧。基于此設(shè)定榄攀,減庫存一般有以下幾個方式:
下單減庫存嗜傅。買家下單后,扣減商品庫存檩赢。下單減庫存是最簡單的減庫存方式吕嘀,也是控制最為精確的一種
付款減庫存。買家下單后贞瞒,并不立即扣減庫存偶房,而是等到付款后才真正扣減庫存。但因為付款時才減庫存军浆,如果并發(fā)比較高棕洋,可能出現(xiàn)買家下單后不能付款的情況,因為商品已經(jīng)被其他人買走了
預扣庫存乒融。這種方式相對復雜一些掰盘,買家下單后,庫存為其保留一定的時間(如 15 分鐘)赞季,超過這段時間愧捕,庫存自動釋放,釋放后其他買家可以購買
能夠看到申钩,減庫存方式是基于購物過程的多階段進行劃分的次绘,但無論是在下單階段還是付款階段,都會存在一些問題,下面進行具體分析邮偎。
2 減庫存的問題
2.1 下單減庫存
優(yōu)勢:用戶體驗最好管跺。下單減庫存是最簡單的減庫存方式,也是控制最精確的一種禾进。下單時可以直接通過數(shù)據(jù)庫事務機制控制商品庫存豁跑,所以一定不會出現(xiàn)已下單卻不能付款的情況。
劣勢:可能賣不出去命迈。正常情況下贩绕,買家下單后付款概率很高,所以不會有太大問題壶愤。但有一種場景例外,就是當賣家參加某個促銷活動時馏鹤,競爭對手通過惡意下單的方式將該商品全部下單征椒,導致庫存清零,那么這就不能正常售賣了——要知道湃累,惡意下單的人是不會真正付款的勃救,這正是 “下單減庫存” 的不足之處。
2.2 付款減庫存
優(yōu)勢:一定實際售賣治力∶擅耄“下單減庫存” 可能導致惡意下單,從而影響賣家的商品銷售宵统, “付款減庫存” 由于需要付出真金白銀晕讲,可以有效避免。
劣勢:用戶體驗較差马澈。用戶下單后瓢省,不一定會實際付款,假設(shè)有 100 件商品痊班,就可能出現(xiàn) 200 人下單成功的情況勤婚,因為下單時不會減庫存,所以也就可能出現(xiàn)下單成功數(shù)遠遠超過真正庫存數(shù)的情況涤伐,這尤其會發(fā)生在大促的熱門商品上馒胆。如此一來就會導致很多買家下單成功后卻不能付款,購物體驗自然是比較差的凝果。
2.3 預扣庫存
優(yōu)勢:緩解了以上兩種方式的問題祝迂。預扣庫存實際就是“下單減庫存”和 “付款減庫存”兩種方式的結(jié)合,將兩次操作進行了前后關(guān)聯(lián)豆村,下單時預扣庫存液兽,付款時釋放庫存。
劣勢:并沒有徹底解決以上問題。比如針對惡意下單的場景四啰,雖然可以把有效付款時間設(shè)置為 10 分鐘宁玫,但惡意買家完全可以在 10 分鐘之后再次下單。
2.4 小結(jié)
減庫存的問題主要體現(xiàn)在用戶體驗和商業(yè)訴求兩方面柑晒,其本質(zhì)原因在于購物過程存在兩步甚至多步操作欧瘪,在不同階段減庫存,容易存在被惡意利用的漏洞匙赞。
3 實際如何減庫存
業(yè)界最為常見的是預扣庫存佛掖。無論是外賣點餐還是電商購物,下單后一般都有個 “有效付款時間”涌庭,超過該時間訂單自動釋放芥被,這就是典型的預扣庫存方案。但如上所述坐榆,預扣庫存還需要解決惡意下單的問題拴魄,保證商品賣的出去;另一方面席镀,如何避免超賣匹中,也是一個痛點。
賣的出去:惡意下單的解決方案主要還是結(jié)合安全和反作弊措施來制止豪诲。比如顶捷,識別頻繁下單不付款的買家并進行打標,這樣可以在打標買家下單時不減庫存屎篱;再比如為大促商品設(shè)置單人最大購買件數(shù)服赎,一人最多只能買 N 件商品;又或者對重復下單不付款的行為進行次數(shù)限制阻斷等
避免超賣:庫存超賣的情況實際分為兩種芳室。對于普通商品专肪,秒殺只是一種大促手段,即使庫存超賣堪侯,商家也可以通過補貨來解決嚎尤;而對于一些商品,秒殺作為一種營銷手段伍宦,完全不允許庫存為負芽死,也就是在數(shù)據(jù)一致性上,需要保證大并發(fā)請求時數(shù)據(jù)庫中的庫存字段值不能為負次洼,一般有多種方案:一是在通過事務來判斷关贵,即保證減后庫存不能為負,否則就回滾卖毁;二是直接設(shè)置數(shù)據(jù)庫字段類型為無符號整數(shù)揖曾,這樣一旦庫存為負就會在執(zhí)行 SQL 時報錯落萎;三是使用 CASE WHEN 判斷語句:
sql UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END<br />
業(yè)務手段保證商品賣的出去,技術(shù)手段保證商品不會超賣炭剪,庫存問題從來就不是簡單的技術(shù)難題练链,解決問題的視角是多種多樣的。
4 一致性性能的優(yōu)化
庫存是個關(guān)鍵數(shù)據(jù)奴拦,更是個熱點數(shù)據(jù)媒鼓。對系統(tǒng)來說,熱點的實際影響就是 “高讀” 和 “高寫”错妖,也是秒殺場景下最為核心的一個技術(shù)難題绿鸣。
4.1 高并發(fā)讀
秒殺場景解決高并發(fā)讀問題,關(guān)鍵詞是“分層校驗”暂氯。即在讀鏈路時潮模,只進行不影響性能的檢查操作,如用戶是否具有秒殺資格株旷、商品狀態(tài)是否正常再登、用戶答題是否正確、秒殺是否已經(jīng)結(jié)束晾剖、是否非法請求等,而不做一致性校驗等容易引發(fā)瓶頸的檢查操作梯嗽;直到寫鏈路時齿尽,才對庫存做一致性檢查,在數(shù)據(jù)層保證最終準確性灯节。
因此循头,在分層校驗設(shè)定下,系統(tǒng)可以采用分布式緩存甚至LocalCache來抵抗高并發(fā)讀炎疆。即允許讀場景下一定的臟數(shù)據(jù)卡骂,這樣只會導致少量原本無庫存的下單請求被誤認為是有庫存的,等到真正寫數(shù)據(jù)時再保證最終一致性形入,由此做到高可用和一致性之間的平衡全跨。
實際上,分層校驗的核心思想是:不同層次盡可能過濾掉無效請求亿遂,只在“漏斗” 最末端進行有效處理浓若,從而縮短系統(tǒng)瓶頸的影響路徑。
4.2 高并發(fā)寫
高并發(fā)寫的優(yōu)化方式蛇数,一種是更換DB選型挪钓,一種是優(yōu)化DB性能,以下分別進行討論耳舅。
4.2.1 更換DB選型
秒殺商品和普通商品的減庫存是有差異的碌上,核心區(qū)別在數(shù)據(jù)量級小、交易時間短,因此能否把秒殺減庫存直接放到緩存系統(tǒng)中實現(xiàn)呢馏予,也就是直接在一個帶有持久化功能的緩存中進行減庫存操作天梧,比如 Redis?
如果減庫存邏輯非常單一的話吗蚌,比如沒有復雜的 SKU 庫存和總庫存這種聯(lián)動關(guān)系的話腿倚,個人認為是完全可以的。但如果有比較復雜的減庫存邏輯蚯妇,或者需要使用到事務敷燎,那就必須在數(shù)據(jù)庫中完成減庫存操作。
4.2.2 優(yōu)化DB性能
庫存數(shù)據(jù)落地到數(shù)據(jù)庫實現(xiàn)其實是一行存儲(MySQL)箩言,因此會有大量線程來競爭 InnoDB 行鎖硬贯。但并發(fā)越高,等待線程就會越多陨收,TPS 下降饭豹,RT 上升,吞吐量會受到嚴重影響——注意务漩,這里假設(shè)數(shù)據(jù)庫已基于上文【性能優(yōu)化】完成數(shù)據(jù)隔離拄衰,以便于討論聚焦 。
解決并發(fā)鎖的問題饵骨,有兩種辦法:
應用層排隊翘悉。通過緩存加入集群分布式鎖,從而控制集群對數(shù)據(jù)庫同一行記錄進行操作的并發(fā)度居触,同時也能控制單個商品占用數(shù)據(jù)庫連接的數(shù)量妖混,防止熱點商品占用過多的數(shù)據(jù)庫連接
數(shù)據(jù)層排隊。應用層排隊是有損性能的轮洋,數(shù)據(jù)層排隊是最為理想的制市。業(yè)界中,阿里的數(shù)據(jù)庫團隊開發(fā)了針對InnoDB 層上的補丁程序(patch)弊予,可以基于DB層對單行記錄做并發(fā)排隊祥楣,從而實現(xiàn)秒殺場景下的定制優(yōu)化——注意,排隊和鎖競爭是有區(qū)別的块促,如果熟悉 MySQL 的話荣堰,就會知道 InnoDB 內(nèi)部的死鎖檢測,以及 MySQL Server 和 InnoDB 的切換都是比較消耗性能的竭翠。另外阿里的數(shù)據(jù)庫團隊還做了很多其他方面的優(yōu)化振坚,如 COMMIT_ON_SUCCESS 和 ROLLBACK_ON_FAIL 的補丁程序,通過在 SQL 里加入提示(hint)斋扰,實現(xiàn)事務不需要等待實時提交渡八,而是在數(shù)據(jù)執(zhí)行完最后一條 SQL 后啃洋,直接根據(jù) TARGET_AFFECT_ROW 的結(jié)果進行提交或回滾,減少網(wǎng)絡等待的時間(毫秒級)屎鳍。目前阿里已將包含這些補丁程序的 MySQL 開源:AliSQL
4.3 小結(jié)
高讀和高寫的兩種處理方式大相徑庭宏娄。讀請求的優(yōu)化空間要大一些,而寫請求的瓶頸一般都在存儲層逮壁,優(yōu)化思路的本質(zhì)還是基于 CAP 理論做平衡孵坚。
5 總結(jié)一下
當然,減庫存還有很多細節(jié)問題窥淆,例如預扣的庫存超時后如何進行回補卖宠,再比如第三方支付如何保證減庫存和付款時的狀態(tài)一致性,這些也是很大的挑戰(zhàn)忧饭。
高可用
盯過秒殺流量監(jiān)控的話扛伍,會發(fā)現(xiàn)它不是一條蜿蜒而起的曲線,而是一條挺拔的直線词裤,這是因為秒殺請求高度集中于某一特定的時間點刺洒。這樣一來就會造成一個特別高的零點峰值,而對資源的消耗也幾乎是瞬時的吼砂。所以秒殺系統(tǒng)的可用性保護是不可或缺的逆航。
1 流量削峰
對于秒殺的目標場景,最終能夠搶到商品的人數(shù)是固定的渔肩,無論 100 人和 10000 人參加結(jié)果都是一樣的纸泡,即有效請求額度是有限的。并發(fā)度越高赖瞒,無效請求也就越多。但秒殺作為一種商業(yè)營銷手段蚤假,活動開始之前是希望有更多的人來刷頁面栏饮,只是真正開始后,秒殺請求不是越多越好磷仰。因此系統(tǒng)可以設(shè)計一些規(guī)則袍嬉,人為的延緩秒殺請求灶平,甚至可以過濾掉一些無效請求。
1.1 答題
早期秒殺只是簡單的點擊秒殺按鈕罐监,后來才增加了答題。為什么要增加答題呢瞒爬?主要是通過提升購買的復雜度弓柱,達到兩個目的:
防止作弊矢空。早期秒殺器比較猖獗航罗,存在惡意買家或競爭對手使用秒殺器掃貨的情況,商家沒有達到營銷的目的屁药,所以增加答題來進行限制
延緩請求粥血。零點流量的起效時間是毫秒級的酿箭,答題可以人為拉長峰值下單的時長七问,由之前的 <1s 延長到 <10s。這個時間對于服務端非常重要械巡,會大大減輕高峰期并發(fā)壓力;另外有勾,由于請求具有先后順序蓖扑,答題后置的請求到來時可能已經(jīng)沒有庫存了腿箩,因此根本無法下單雇逞,此階段落到數(shù)據(jù)層真正的寫也就非常有限了
需要注意的是茁裙,答題除了做正確性驗證晤锥,還需要對提交時間做驗證,比如<1s 人為操作的可能性就很小女轿,可以進一步防止機器答題的情況壕翩。
答題目前已經(jīng)使用的非常普遍了蛉迹,本質(zhì)是通過在入口層削減流量戈泼,從而讓系統(tǒng)更好地支撐瞬時峰值。
1.2 排隊
最為常見的削峰方案是使用消息隊列扭倾,通過把同步的直接調(diào)用轉(zhuǎn)換成異步的間接推送緩沖瞬時流量膛壹。除了消息隊列,類似的排隊方案還有很多肩民,例如:
線程池加鎖等待
本地內(nèi)存蓄洪等待
本地文件序列化寫链方,再順序讀
排隊方式的弊端也是顯而易見的,主要有兩點:
請求積壓工窍。流量高峰如果長時間持續(xù)前酿,達到了隊列的水位上限罢维,隊列同樣會被壓垮,這樣雖然保護了下游系統(tǒng)匀借,但是和請求直接丟棄也沒多大區(qū)別
用戶體驗平窘。異步推送的實時性和有序性自然是比不上同步調(diào)用的,由此可能出現(xiàn)請求先發(fā)后至的情況,影響部分敏感用戶的購物體驗
排隊本質(zhì)是在業(yè)務層將一步操作轉(zhuǎn)變成兩步操作磅叛,從而起到緩沖的作用萨赁,但鑒于此種方式的弊端,最終還是要基于業(yè)務量級和秒殺場景做出妥協(xié)和平衡敲董。
1.3 過濾
過濾的核心結(jié)構(gòu)在于分層腋寨,通過在不同層次過濾掉無效請求,達到數(shù)據(jù)讀寫的精準觸發(fā)铃剔。常見的過濾主要有以下幾層:
讀限流:對讀請求做限流保護查刻,將超出系統(tǒng)承載能力的請求過濾掉
讀緩存:對讀請求做數(shù)據(jù)緩存,將重復的請求過濾掉
寫限流:對寫請求做限流保護普气,將超出系統(tǒng)承載能力的請求過濾掉
寫校驗:對寫請求做一致性校驗佃延,只保留最終的有效數(shù)據(jù)
過濾的核心目的是通過減少無效請求的數(shù)據(jù)IO保障有效請求的IO性能苇侵。
1.4 小結(jié)
系統(tǒng)可以通過入口層的答題、業(yè)務層的排隊于未、數(shù)據(jù)層的過濾達到流量削峰的目的陡鹃,本質(zhì)是在尋求商業(yè)訴求與架構(gòu)性能之間的平衡。另外闷叉,新的削峰手段也層出不窮握侧,以業(yè)務切入居多嘿期,比如零點大促時同步發(fā)放優(yōu)惠券或發(fā)起抽獎活動,將一部分流量分散到其他系統(tǒng)萄传,這樣也能起到削峰的作用蜜猾。
Plan B
當一個系統(tǒng)面臨持續(xù)的高峰流量時,其實是很難單靠自身調(diào)整來恢復狀態(tài)的衍菱,日常運維沒有人能夠預估所有情況梦碗,意外總是無法避免。尤其在秒殺這一場景下印屁,為了保證系統(tǒng)的高可用斩例,必須設(shè)計一個 Plan B 方案來進行兜底念赶。
高可用建設(shè),其實是一個系統(tǒng)工程旗吁,貫穿在系統(tǒng)建設(shè)的整個生命周期停局。
具體來說董栽,系統(tǒng)的高可用建設(shè)涉及架構(gòu)階段、編碼階段袁稽、測試階段擒抛、發(fā)布階段歧沪、運行階段,以及故障發(fā)生時,逐一進行分析:
架構(gòu)階段:考慮系統(tǒng)的可擴展性和容錯性胁编,避免出現(xiàn)單點問題。例如多地單元化部署早直,即使某個IDC甚至地市出現(xiàn)故障霞扬,仍不會影響系統(tǒng)運轉(zhuǎn)
編碼階段:保證代碼的健壯性,例如RPC調(diào)用時萤彩,設(shè)置合理的超時退出機制斧拍,防止被其他系統(tǒng)拖垮肆汹,同時也要對無法預料的返回錯誤進行默認的處理
測試階段:保證CI的覆蓋度以及Sonar的容錯率,對基礎(chǔ)質(zhì)量進行二次校驗浪册,并定期產(chǎn)出整體質(zhì)量的趨勢報告
發(fā)布階段:系統(tǒng)部署最容易暴露錯誤岗照,因此要有前置的checklist模版谴返、中置的上下游周知機制以及后置的回滾機制
運行階段:系統(tǒng)多數(shù)時間處于運行態(tài),最重要的是運行時的實時監(jiān)控籍救,及時發(fā)現(xiàn)問題渠抹、準確報警并能提供詳細數(shù)據(jù)梧却,以便排查問題
故障發(fā)生:首要目標是及時止損,防止影響面擴大烈拒,然后定位原因、解決問題吓妆,最后恢復服務
對于日常運維而言行拢,高可用更多是針對運行階段而言的诞吱,此階段需要額外進行加強建設(shè),主要有以下幾種手段:
預防:建立常態(tài)壓測體系沼瘫,定期對服務進行單點壓測以及全鏈路壓測晕鹊,摸排水位
管控:做好線上運行的降級暴浦、限流和熔斷保護。需要注意的是飞几,無論是限流屑墨、降級還是熔斷纷铣,對業(yè)務都是有損的,所以在進行操作前以躯,一定要和上下游業(yè)務確認好再進行忧设。就拿限流來說颠通,哪些業(yè)務可以限顿锰、什么情況下限启搂、限流時間多長刘陶、什么情況下進行恢復易核,都要和業(yè)務方反復確認
監(jiān)控:建立性能基線浪默,記錄性能的變化趨勢纳决;建立報警體系,發(fā)現(xiàn)問題及時預警
恢復:遇到故障能夠及時止損饵史,并提供快速的數(shù)據(jù)訂正工具胜榔,不一定要好夭织,但一定要有
在系統(tǒng)建設(shè)的整個生命周期中,每個環(huán)節(jié)中都可能犯錯讲竿,甚至有些環(huán)節(jié)犯的錯题禀,后面是無法彌補的或者成本極高的膀捷。所以高可用是一個系統(tǒng)工程担孔,必須放到整個生命周期中進行全面考慮。同時啄育,考慮到服務的增長性拌消,高可用更需要長期規(guī)劃并進行體系化建設(shè)。
3 總結(jié)一下
高可用其實是在說 “穩(wěn)定性”侯勉,穩(wěn)定性是一個平時不重要铝阐,但出了問題就要命的事情徘键,然而它的落地又是一個問題——平時業(yè)務發(fā)展良好,穩(wěn)定性建設(shè)就會降級給業(yè)務讓路螟凭。解決這個問題必須在組織上有所保障它呀,比如讓業(yè)務負責人背上穩(wěn)定性績效指標纵穿,同時在部門中建立穩(wěn)定性建設(shè)小組政恍,小組成員由每條線的核心力量兼任,績效由穩(wěn)定性負責人來打分迫筑,這樣就可以把體系化的建設(shè)任務落實到具體的業(yè)務系統(tǒng)中了宗弯。
個人總結(jié)
一個秒殺系統(tǒng)的設(shè)計蒙保,可以根據(jù)不同級別的流量,由簡單到復雜打造出不同的架構(gòu)逝嚎,本質(zhì)是各方面的取舍和權(quán)衡补君。當然昧互,你可能注意到,本文并沒有涉及具體的選型方案叽掘,因為這些對于架構(gòu)來說并不重要更扁,作為架構(gòu)師浓镜,應該時刻提醒自己主線是什么。
同時也在這里抽象、提煉一下相叁,主要是個人對于秒殺設(shè)計的提綱式整理辽幌,方便各位同學進行參考—!
寫在最后
歡迎大家關(guān)注我的公眾號【風平浪靜如碼】乌企,海量Java相關(guān)文章加酵,學習資料都會在里面更新,整理的資料也會放在里面冗澈。
覺得寫的還不錯的就點個贊亚亲,加個關(guān)注唄腐缤!點關(guān)注岭粤,不迷路,持續(xù)更新C派取>始摹!