




版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
設(shè)計(jì)一個(gè)秒殺系統(tǒng)秒殺系統(tǒng)架構(gòu)設(shè)計(jì)都有哪些關(guān)鍵點(diǎn)?說(shuō)起來(lái)我的職業(yè)生涯算是比較簡(jiǎn)單,2009年大學(xué)畢業(yè)后就進(jìn)入了淘寶,一直工作了七年多。這七年多的時(shí)間里,我有幸看到了淘寶業(yè)務(wù)的快速增長(zhǎng),并且以開(kāi)發(fā)者的身份參與其中。說(shuō)實(shí)話(huà),作為一名程序員,我的技術(shù)能力也在公司業(yè)務(wù)的快速增長(zhǎng)過(guò)程中得到了歷練,并積累了一些大流量高并發(fā)網(wǎng)站架構(gòu)設(shè)計(jì)和優(yōu)化的經(jīng)驗(yàn),尤其是針對(duì)“秒殺”這個(gè)場(chǎng)景。因?yàn)槲掖_信,那個(gè)時(shí)候我們肯定是對(duì)系統(tǒng)做了足夠多的極致優(yōu)化,才能扛住當(dāng)時(shí)洪峰般的流量請(qǐng)求。記得早期的時(shí)候,淘寶商品詳情系統(tǒng)的PV還差不多是1億的樣子,但是到2016年差不多已經(jīng)升至50億了。尤其是2012年到2014年那個(gè)時(shí)間段,“秒殺”活動(dòng)特別流行,用戶(hù)的參與熱情一浪高過(guò)一浪,系統(tǒng)要面對(duì)的流量也是成倍增長(zhǎng)。而每一次的秒殺活動(dòng)對(duì)技術(shù)團(tuán)隊(duì)來(lái)說(shuō)都是一次考驗(yàn)?,F(xiàn)在想起來(lái),那個(gè)時(shí)候我們整個(gè)團(tuán)事情。記得有一年,為了應(yīng)對(duì)“雙十一”,我們整個(gè)商品詳情團(tuán)隊(duì)對(duì)系統(tǒng)做了很多優(yōu)化,我們自認(rèn)為已經(jīng)是整個(gè)公司最牛的系統(tǒng)了,性能也已經(jīng)是“業(yè)界之巔”。但是那年“雙十一”的晚上,我們的系統(tǒng)還是遇到了瓶頸。當(dāng)時(shí)老大就跑過(guò)來(lái)盯著我們,問(wèn)我們什么時(shí)候能夠恢復(fù),我們整個(gè)團(tuán)隊(duì)都承擔(dān)著巨大的心理壓力。事后我們復(fù)盤(pán)宕機(jī)的原因,發(fā)現(xiàn)當(dāng)時(shí)的秒殺流量遠(yuǎn)遠(yuǎn)超過(guò)了我們的預(yù)想,我們根本沒(méi)想到大家的參與熱情能有那么高。于是我們按照這個(gè)增長(zhǎng)率去預(yù)估下一年的流量和服務(wù)器,粗算下來(lái),我記得差不多要增加2000臺(tái)服務(wù)器,簡(jiǎn)直不可思議。怎么可能真正增加這么多機(jī)器,所以這也就倒逼我們必須找出一些特殊的手段來(lái)優(yōu)化系方案。秒殺系統(tǒng)也差不多那個(gè)時(shí)候才從商品詳情系統(tǒng)獨(dú)立出來(lái)成為一個(gè)獨(dú)立產(chǎn)品的。因?yàn)槲乙?jiàn)證了秒殺系統(tǒng)的建設(shè)過(guò)程,所以也有頗多感慨。秒殺系統(tǒng)的迭代又是一個(gè)升級(jí)打怪的過(guò)程,我們也都是遇到問(wèn)題解決問(wèn)題,逐一優(yōu)化。那么,如何才能更好地理解秒殺系統(tǒng)呢?我覺(jué)得作為一個(gè)程序員,你首先需要從高維度出發(fā),從整體上思考問(wèn)題。在我看來(lái),秒殺其實(shí)主要解決兩個(gè)問(wèn)題,一個(gè)是并發(fā)讀,一個(gè)是并發(fā)寫(xiě)。并發(fā)讀的核心優(yōu)化理念是盡量減少用戶(hù)到服務(wù)端來(lái)“讀”數(shù)據(jù),或者讓他們讀更少的數(shù)據(jù);并發(fā)寫(xiě)的處理原則也一樣,它要求我們?cè)跀?shù)據(jù)庫(kù)層面獨(dú)立出來(lái)一個(gè)庫(kù),做特殊的處理。另外,我們還要針對(duì)秒殺系統(tǒng)做一些保護(hù),針對(duì)意料之外的情況設(shè)計(jì)兜底方案,以防止最壞的情況發(fā)生。而從一個(gè)架構(gòu)師的角度來(lái)看,要想打造并維護(hù)一個(gè)超大流量并發(fā)讀寫(xiě)、高性能、高可用的系統(tǒng),在整個(gè)用戶(hù)請(qǐng)求路徑上從瀏覽器到服務(wù)端我們要遵循幾個(gè)原則,就是要保證用戶(hù)請(qǐng)求的數(shù)據(jù)盡量少、請(qǐng)求數(shù)盡量少、路徑盡量短、依賴(lài)盡量少,并且不要有單點(diǎn)。這些關(guān)鍵點(diǎn)我會(huì)在后面的文章里重點(diǎn)講解。其實(shí),秒殺的整體架構(gòu)可以概括為“穩(wěn)、準(zhǔn)、快”幾個(gè)關(guān)鍵字。所謂“穩(wěn)”,就是整個(gè)系統(tǒng)架構(gòu)要滿(mǎn)足高可用,流量符合預(yù)期時(shí)肯定要穩(wěn)定,就是超出預(yù)期時(shí)也同樣不能掉鏈子,你要保證秒殺活動(dòng)順利完成,即秒殺商品順利地賣(mài)出去,這個(gè)是最基本的前提。然后就是“準(zhǔn)”,就是秒殺10臺(tái)iPhone,那就只能成交10臺(tái),多一臺(tái)少一臺(tái)都不行。一旦庫(kù)存不對(duì),那平臺(tái)就要承擔(dān)損失,所以“準(zhǔn)”就是要求保證數(shù)據(jù)的一致性。最后再看“快”,“快”其實(shí)很好理解,它就是說(shuō)系統(tǒng)的性能要足夠高,否則你怎么支撐這么大的流量呢?不光是服務(wù)端要做極致的性能優(yōu)化,而且在整個(gè)請(qǐng)求鏈路上都要做協(xié)同的優(yōu)化,每個(gè)地方快一點(diǎn),整個(gè)系統(tǒng)就完美了。所以從技術(shù)角度上看“穩(wěn)、準(zhǔn)、快”,就對(duì)應(yīng)了我們架構(gòu)上的高可用、一致性和高性能的要求,我們的專(zhuān)欄也將主要圍繞這幾個(gè)方面來(lái)展開(kāi),具體如下。高性能。秒殺涉及大量的并發(fā)讀和并發(fā)寫(xiě),因此支持高并發(fā)訪(fǎng)問(wèn)這點(diǎn)非常關(guān)鍵。本專(zhuān)欄將從設(shè)計(jì)數(shù)據(jù)的動(dòng)靜分離方案、熱點(diǎn)的發(fā)現(xiàn)與隔離、請(qǐng)求的削峰與分層過(guò)濾、服務(wù)端的極致優(yōu)化這4個(gè)方面重點(diǎn)介紹。一致性。秒殺中商品減庫(kù)存的實(shí)現(xiàn)方式同樣關(guān)鍵??上攵?,有限數(shù)量的商品在同一時(shí)刻被很多倍的請(qǐng)求同時(shí)來(lái)減庫(kù)存,減庫(kù)存又分為“拍下減庫(kù)存”“付款減庫(kù)存”以及預(yù)扣等幾種,在大并發(fā)更新的過(guò)程中都要保證數(shù)據(jù)的準(zhǔn)確性,其難度可想而知。因此,我將用一篇文章來(lái)專(zhuān)門(mén)講解如何設(shè)計(jì)秒殺減庫(kù)存方案。高可用。雖然我介紹了很多極致的優(yōu)化思路,但現(xiàn)實(shí)中總難免出現(xiàn)一些我們考慮不到的情況,所以要保證系統(tǒng)的高可用和正確性,我們還要設(shè)計(jì)一個(gè)PlanB來(lái)兜底,以便在最壞情況發(fā)生時(shí)仍然能夠從容應(yīng)對(duì)。專(zhuān)欄的最后,我將帶你思考可以從哪些環(huán)節(jié)來(lái)設(shè)計(jì)兜底方案。最后,很幸運(yùn)能在極客時(shí)間遇到你,希望這堂課能讓你徹底理解大并發(fā)、高性能、高可用秒殺系統(tǒng)的設(shè)計(jì)之道,并能夠在思考解決類(lèi)似問(wèn)題時(shí)有更準(zhǔn)確的思考和判斷。|設(shè)計(jì)秒殺系統(tǒng)時(shí)應(yīng)該注意的5個(gè)架構(gòu)原則說(shuō)起秒殺,我想你肯定不陌生,這兩年,從雙十一購(gòu)物到春節(jié)搶紅包,再到12306搶火車(chē)票,“秒殺”的場(chǎng)景處處可見(jiàn)。簡(jiǎn)單來(lái)說(shuō),秒殺就是在同一個(gè)時(shí)刻有大量的請(qǐng)求爭(zhēng)搶購(gòu)買(mǎi)同一個(gè)商品并完成交易的過(guò)程,用技術(shù)的行話(huà)來(lái)說(shuō)就是大量的并發(fā)讀和并發(fā)寫(xiě)。樣,你可以很快增刪改查做出一個(gè)秒殺系統(tǒng),但是要讓它支持高并發(fā)訪(fǎng)問(wèn)就沒(méi)那么容易一致性寫(xiě)?完全靠堆服務(wù)器來(lái)解決嗎?這顯然不是最好的解決方案。在我看來(lái),秒殺系統(tǒng)本質(zhì)上就是一個(gè)滿(mǎn)足大并發(fā)、高性能和高可用的分布式系統(tǒng)。今天,我們就來(lái)聊聊,如何在滿(mǎn)足一個(gè)良好架構(gòu)的分布式系統(tǒng)基礎(chǔ)上,針對(duì)秒殺這種業(yè)務(wù)做到極致的性能改進(jìn)。架構(gòu)原則:“4要1不要”如果你是一個(gè)架構(gòu)師,你首先要勾勒出一個(gè)輪廓,想一想如何構(gòu)建一個(gè)超大流量并發(fā)讀寫(xiě)、高性能,以及高可用的系統(tǒng),這其中有哪些要素需要考慮。我把這些要素總結(jié)為“4要1不要”。數(shù)據(jù)要盡量少所謂“數(shù)據(jù)要盡量少”,首先是指用戶(hù)請(qǐng)求的數(shù)據(jù)能少就少。請(qǐng)求的數(shù)據(jù)包括上傳給系統(tǒng)的數(shù)據(jù)和系統(tǒng)返回給用戶(hù)的數(shù)據(jù)(通常就是網(wǎng)頁(yè))。為啥“數(shù)據(jù)要盡量少”呢?因?yàn)槭紫冗@些數(shù)據(jù)在網(wǎng)絡(luò)上傳輸需要時(shí)間,其次不管是請(qǐng)求數(shù)據(jù)還是返回?cái)?shù)據(jù)都需要服務(wù)器做處理,而服務(wù)器在寫(xiě)網(wǎng)絡(luò)時(shí)通常都要做壓縮和字符編碼,這些都非常消耗CPU,所以減少傳輸?shù)臄?shù)據(jù)量可以顯著減少CPU的使用。例如,我們可以簡(jiǎn)化秒殺頁(yè)面的大小,去掉不必要的頁(yè)面裝修效果,等等。其次,“數(shù)據(jù)要盡量少”還要求系統(tǒng)依賴(lài)的數(shù)據(jù)能少就少,包括系統(tǒng)完成某些業(yè)務(wù)邏輯需要讀取和保存的數(shù)據(jù),這些數(shù)據(jù)一般是和后臺(tái)服務(wù)以及數(shù)據(jù)庫(kù)打交道的。調(diào)用其他服務(wù)會(huì)涉及數(shù)據(jù)的序列化和反序列化,而這也是CPU的一大殺手,同樣也會(huì)增加延時(shí)。而且,數(shù)據(jù)庫(kù)本身也容易成為一個(gè)瓶頸,所以和數(shù)據(jù)庫(kù)打交道越少越好,數(shù)據(jù)越簡(jiǎn)單、越小則越好。請(qǐng)求數(shù)要盡量少用戶(hù)請(qǐng)求的頁(yè)面返回后,瀏覽器渲染這個(gè)頁(yè)面還要包含其他的額外請(qǐng)求,比如說(shuō),這個(gè)頁(yè)CSS/JavaScriptAjax次握手,有的時(shí)候有頁(yè)面依賴(lài)或者連接數(shù)限制,一些請(qǐng)求(JavaScript)還需要串DNS例如,減少請(qǐng)求數(shù)最常用的一個(gè)實(shí)踐就是合并CSS和JavaScript文件,把多個(gè)\hJavaScript文件合并成一個(gè)文件,在URL中用逗號(hào)隔開(kāi)(/tm/xx-b/4.0.94/mods/??module-preview/index.xtpl.js,module-jhs/index.xtpl.js,module-focus/index.xtpl.js)。這種方式在服務(wù)端仍然是單個(gè)文件各自存放,只是服務(wù)端會(huì)有一個(gè)組件解析這個(gè)URL,然后動(dòng)態(tài)把這些文件合并起來(lái)一起返回。路徑要盡量短所謂“路徑”,就是用戶(hù)發(fā)出請(qǐng)求到返回?cái)?shù)據(jù)這個(gè)過(guò)程中,需求經(jīng)過(guò)的中間的節(jié)點(diǎn)數(shù)。通常,這些節(jié)點(diǎn)可以表示為一個(gè)系統(tǒng)或者一個(gè)新的Socket連接(比如代理服務(wù)器只是創(chuàng)建一個(gè)新的Socket連接來(lái)轉(zhuǎn)發(fā)請(qǐng)求)。每經(jīng)過(guò)一個(gè)節(jié)點(diǎn),一般都會(huì)產(chǎn)生一個(gè)新的Socket連接。然而,每增加一個(gè)連接都會(huì)增加新的不確定性。從概率統(tǒng)計(jì)上來(lái)說(shuō),假如一次請(qǐng)求經(jīng)過(guò)5個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)的可用性是99.9%的話(huà),那么整個(gè)請(qǐng)求的可用性是:99.9%的5次方,約等于99.5%。所以縮短請(qǐng)求路徑不僅可以增加可用性,同樣可以有效提升性能(減少中間節(jié)點(diǎn)可以減少數(shù)據(jù)的序列化與反序列化),并減少延時(shí)(可以減少網(wǎng)絡(luò)傳輸耗時(shí))。要縮短訪(fǎng)問(wèn)路徑有一種辦法,就是多個(gè)相互強(qiáng)依賴(lài)的應(yīng)用合并部署在一起,把遠(yuǎn)程過(guò)程調(diào)用(RPC)變成JVM內(nèi)部之間的方法調(diào)用。在《大型網(wǎng)站技術(shù)架構(gòu)演進(jìn)與性能優(yōu)化》一書(shū)中,我也有一章介紹了這種技術(shù)的詳細(xì)實(shí)現(xiàn)。依賴(lài)要盡量少所謂依賴(lài),指的是要完成一次用戶(hù)請(qǐng)求必須依賴(lài)的系統(tǒng)或者服務(wù),這里的依賴(lài)指的是強(qiáng)依賴(lài)。舉個(gè)例子,比如說(shuō)你要展示秒殺頁(yè)面,而這個(gè)頁(yè)面必須強(qiáng)依賴(lài)商品信息、用戶(hù)信息,還有其他如優(yōu)惠券、成交列表等這些對(duì)秒殺不是非要不可的信息(弱依賴(lài)),這些弱依賴(lài)在緊急情況下就可以去掉。要減少依賴(lài),我們可以給系統(tǒng)進(jìn)行分級(jí),比如0級(jí)系統(tǒng)、1級(jí)系統(tǒng)、2級(jí)系統(tǒng)、3級(jí)系統(tǒng),0級(jí)系統(tǒng)如果是最重要的系統(tǒng),那么0級(jí)系統(tǒng)強(qiáng)依賴(lài)的系統(tǒng)也同樣是最重要的系統(tǒng),以此類(lèi)推。注意,0級(jí)系統(tǒng)要盡量減少對(duì)1級(jí)系統(tǒng)的強(qiáng)依賴(lài),防止重要的系統(tǒng)被不重要的系統(tǒng)拖垮。例如支付系統(tǒng)是0級(jí)系統(tǒng),而優(yōu)惠券是1級(jí)系統(tǒng)的話(huà),在極端情況下可以把優(yōu)惠券給降級(jí),防止支付系統(tǒng)被優(yōu)惠券這個(gè)1級(jí)系統(tǒng)給拖垮。不要有單點(diǎn)系統(tǒng)中的單點(diǎn)可以說(shuō)是系統(tǒng)架構(gòu)上的一個(gè)大忌,因?yàn)閱吸c(diǎn)意味著沒(méi)有備份,風(fēng)險(xiǎn)不可控,我們?cè)O(shè)計(jì)分布式系統(tǒng)最重要的原則就是“消除單點(diǎn)”。那如何避免單點(diǎn)呢?我認(rèn)為關(guān)鍵點(diǎn)是避免將服務(wù)的狀態(tài)和機(jī)器綁定,即把服務(wù)無(wú)狀態(tài)化,這樣服務(wù)就可以在機(jī)器中隨意移動(dòng)。如何那把服務(wù)的狀態(tài)和機(jī)器解耦呢?這里也有很多實(shí)現(xiàn)方式。例如把和機(jī)器相關(guān)的配置動(dòng)態(tài)化,這些參數(shù)可以通過(guò)配置中心來(lái)動(dòng)態(tài)推送,在服務(wù)啟動(dòng)時(shí)動(dòng)態(tài)拉取下來(lái),我們?cè)谶@些配置中心設(shè)置一些規(guī)則來(lái)方便地改變這些映射關(guān)系。應(yīng)用無(wú)狀態(tài)化是有效避免單點(diǎn)的一種方式,但是像存儲(chǔ)服務(wù)本身很難無(wú)狀態(tài)化,因?yàn)閿?shù)據(jù)要存儲(chǔ)在磁盤(pán)上,本身就要和機(jī)器綁定,那么這種場(chǎng)景一般要通過(guò)冗余多個(gè)備份的方式來(lái)解決單點(diǎn)問(wèn)題。前面介紹了這些設(shè)計(jì)上的一些原則,但是你有沒(méi)有發(fā)現(xiàn),我一直說(shuō)的是“盡量”而不是“絕對(duì)”?我想你肯定會(huì)問(wèn)是不是請(qǐng)求最少就一定最好,我的答案是“不一定”。我們?cè)?jīng)把有些CSS內(nèi)聯(lián)進(jìn)頁(yè)面里,這樣做可以減少依賴(lài)一個(gè)CSS的請(qǐng)求從而加快首頁(yè)的渲染,但是同樣也增大了頁(yè)面的大小,又不符合“數(shù)據(jù)要盡量少”的原則,這種情況下我們?yōu)榱颂嵘灼恋匿秩舅俣?,只把首屏的HTML依賴(lài)的CSS內(nèi)聯(lián)進(jìn)來(lái),其他CSS仍然放到文件中作為依賴(lài)加載,盡量實(shí)現(xiàn)首屏的打開(kāi)速度與整個(gè)頁(yè)面加載性能的平衡。所以說(shuō),架構(gòu)是一種平衡的藝術(shù),而最好的架構(gòu)一旦脫離了它所適應(yīng)的場(chǎng)景,一切都將是空談。我希望你記住的是,這里所說(shuō)的幾點(diǎn)都只是一個(gè)個(gè)方向,你應(yīng)該盡量往這些方向上去努力,但也要考慮平衡其他因素。不同場(chǎng)景下的不同架構(gòu)案例前面我說(shuō)了一些架構(gòu)上的原則,那么針對(duì)“秒殺”這個(gè)場(chǎng)景,怎樣才是一個(gè)好的架構(gòu)呢?下面我以淘寶早期秒殺系統(tǒng)架構(gòu)的演進(jìn)為主線(xiàn),來(lái)幫你梳理不同的請(qǐng)求體量下,我認(rèn)為的最佳秒殺系統(tǒng)架構(gòu)。如果你想快速搭建一個(gè)簡(jiǎn)單的秒殺系統(tǒng),只需要把你的商品購(gòu)買(mǎi)頁(yè)面增加一個(gè)“定時(shí)上就是當(dāng)時(shí)第一個(gè)版本的秒殺系統(tǒng)實(shí)現(xiàn)方式。但隨著請(qǐng)求量的加大(比如從1w/s到了10w/s的量級(jí)),這個(gè)簡(jiǎn)單的架構(gòu)很快就遇到了瓶頸,因此需要做架構(gòu)改造來(lái)提升系統(tǒng)性能。這些架構(gòu)改造包括:把秒殺系統(tǒng)獨(dú)立出來(lái)單獨(dú)打造一個(gè)系統(tǒng),這樣可以有針對(duì)性地做優(yōu)化,例如這個(gè)獨(dú)立出來(lái)的系統(tǒng)就減少了店鋪裝修的功能,減少了頁(yè)面的復(fù)雜度;在系統(tǒng)部署上也獨(dú)立做一個(gè)機(jī)器集群,這樣秒殺的大流量就不會(huì)影響到正常的商品購(gòu)買(mǎi)集群的機(jī)器負(fù)載;將熱點(diǎn)數(shù)據(jù)(如庫(kù)存數(shù)據(jù))單獨(dú)放到一個(gè)緩存系統(tǒng)中,以提高“讀性能”;增加秒殺答題,防止有秒殺器搶單。此時(shí)的系統(tǒng)架構(gòu)變成了下圖這個(gè)樣子。最重要的就是,秒殺詳情成為了一個(gè)獨(dú)立的新系統(tǒng),另外核心的一些數(shù)據(jù)放到了緩存(Cache)中,其他的關(guān)聯(lián)系統(tǒng)也都以獨(dú)立集群的方式進(jìn)行部署。圖1改造后的系統(tǒng)架構(gòu)然而這個(gè)架構(gòu)仍然支持不了超過(guò)100w/s的請(qǐng)求量,所以為了進(jìn)一步提升秒殺系統(tǒng)的性能,我們又對(duì)架構(gòu)做進(jìn)一步升級(jí),比如:對(duì)頁(yè)面進(jìn)行徹底的動(dòng)靜分離,使得用戶(hù)秒殺時(shí)不需要刷新整個(gè)頁(yè)面,而只需要點(diǎn)擊搶寶按鈕,借此把頁(yè)面刷新的數(shù)據(jù)降到最少;在服務(wù)端對(duì)秒殺商品進(jìn)行本地緩存,不需要再調(diào)用依賴(lài)系統(tǒng)的后臺(tái)服務(wù)獲取數(shù)據(jù),甚至不需要去公共的緩存集群中查詢(xún)數(shù)據(jù),這樣不僅可以減少系統(tǒng)調(diào)用,而且能夠避免壓垮公共緩存集群。增加系統(tǒng)限流保護(hù),防止最壞情況發(fā)生。經(jīng)過(guò)這些優(yōu)化,系統(tǒng)架構(gòu)變成了下圖中的樣子。在這里,我們對(duì)頁(yè)面進(jìn)行了進(jìn)一步的靜態(tài)化,秒殺過(guò)程中不需要刷新整個(gè)頁(yè)面,而只需要向服務(wù)端請(qǐng)求很少的動(dòng)態(tài)數(shù)據(jù)。而且,最關(guān)鍵的詳情和交易系統(tǒng)都增加了本地緩存,來(lái)提前緩存秒殺商品的信息,熱點(diǎn)數(shù)據(jù)庫(kù)也做了獨(dú)立部署,等等。圖2進(jìn)一步改造后的系統(tǒng)架構(gòu)從前面的幾次升級(jí)來(lái)看,其實(shí)越到后面需要定制的地方越多,也就是越“不通用”。例的情況,因?yàn)閱螜C(jī)的內(nèi)存始終有限。所以要取得極致的性能,就要在其他地方(用性、易用性、成本等方面)有所犧牲。總結(jié)來(lái)讓我們回顧下前面的內(nèi)容,我首先介紹了構(gòu)建大并發(fā)、高性能、高可用系統(tǒng)中幾種通用的優(yōu)化思路,并抽象總結(jié)為“4要1不要”原則,也就是:數(shù)據(jù)要盡量少、請(qǐng)求數(shù)要盡量少、路徑要盡量短、依賴(lài)要盡量少,以及不要有單點(diǎn)。當(dāng)然,這幾點(diǎn)是你要努力的方向,具體操作時(shí)還是要密切結(jié)合實(shí)際的場(chǎng)景和具體條件來(lái)進(jìn)行。然后,我給出了實(shí)際構(gòu)建秒殺系統(tǒng)時(shí),根據(jù)不同級(jí)別的流量,由簡(jiǎn)單到復(fù)雜打造的幾種系統(tǒng)架構(gòu),希望能供你參考。當(dāng)然,這里面我沒(méi)有說(shuō)具體的解決方案,比如緩存用什么、頁(yè)面靜態(tài)化用什么,因?yàn)檫@些對(duì)于架構(gòu)來(lái)說(shuō)并不重要,作為架構(gòu)師,你應(yīng)該時(shí)刻提醒自己主線(xiàn)是什么。說(shuō)了這么多,總體上我希望給你一個(gè)方向,就是想構(gòu)建大并發(fā)、高性能、高可用的系統(tǒng)應(yīng)該從哪幾個(gè)方向上去努力,然后在不同性能要求的情況下系統(tǒng)架構(gòu)應(yīng)該從哪幾個(gè)方面去做取舍。同時(shí)你也要明白,越追求極致性能,系統(tǒng)定制開(kāi)發(fā)就會(huì)越多,同時(shí)系統(tǒng)的通用性也就會(huì)越差。最后,歡迎你在評(píng)論區(qū)和我分享你在設(shè)計(jì)秒殺系統(tǒng)時(shí)的一些經(jīng)驗(yàn)和思考,你的經(jīng)驗(yàn)對(duì)我們這個(gè)專(zhuān)欄來(lái)說(shuō)也很重要。|如何才能做好動(dòng)靜分離?有哪些方案可選?上一篇文章中,我介紹了秒殺系統(tǒng)在架構(gòu)上要考慮的幾個(gè)原則,我估計(jì)你很快就會(huì)問(wèn):“知易行難,這些原則應(yīng)該怎么應(yīng)用到系統(tǒng)中呢?”別急,從這篇文章開(kāi)始,我就會(huì)逐一介紹秒殺系統(tǒng)的各個(gè)關(guān)鍵環(huán)節(jié)中涉及的關(guān)鍵技術(shù)。今天我們就先來(lái)討論第一個(gè)關(guān)鍵點(diǎn):數(shù)據(jù)的動(dòng)靜分離。不知道你之前聽(tīng)過(guò)這個(gè)解決方案量根本用不著?不過(guò)我可以確信地說(shuō),如果你在一個(gè)業(yè)務(wù)飛速發(fā)展的公司里,并且你在深度參與公司內(nèi)類(lèi)秒殺類(lèi)系統(tǒng)的架構(gòu)或者開(kāi)發(fā)工作,那么你遲早會(huì)想到動(dòng)靜分離的方案。為什么?很簡(jiǎn)單,秒殺的場(chǎng)景中,對(duì)于系統(tǒng)的要求其實(shí)就三個(gè)字:快、準(zhǔn)、穩(wěn)。那怎么才能“快”起來(lái)呢?我覺(jué)得抽象起來(lái)講,就只有兩點(diǎn),一點(diǎn)是提高單次請(qǐng)求的效的。不知道你是否還記得,最早的秒殺系統(tǒng)其實(shí)是要刷新整體頁(yè)面的,但后來(lái)秒殺的時(shí)候,你只要點(diǎn)擊“刷新?lián)寣殹卑粹o就夠了,這種變化的本質(zhì)就是動(dòng)靜分離,分離之后,客戶(hù)端大幅度減少了請(qǐng)求的數(shù)據(jù)量。這不自然就“快”了嗎?何為動(dòng)靜數(shù)據(jù)那到底什么才是動(dòng)靜分離呢?所謂“動(dòng)靜分離”,其實(shí)就是把用戶(hù)請(qǐng)求的數(shù)據(jù)(如HTML頁(yè)面)劃分為“動(dòng)態(tài)數(shù)據(jù)”和“靜態(tài)數(shù)據(jù)”。簡(jiǎn)單來(lái)說(shuō),“動(dòng)態(tài)數(shù)據(jù)”和“靜態(tài)數(shù)據(jù)”的主要區(qū)別就是看頁(yè)面中輸出的數(shù)據(jù)是否和URL、瀏覽者、時(shí)間、地域相關(guān),以及是否含有Cookie等私密數(shù)據(jù)。比如說(shuō):很多媒體類(lèi)的網(wǎng)站,某一篇文章的內(nèi)容不管是你訪(fǎng)問(wèn)還是我訪(fǎng)問(wèn),它都是一樣的。所以它就是一個(gè)典型的靜態(tài)數(shù)據(jù),但是它是個(gè)動(dòng)態(tài)頁(yè)面。我們?nèi)绻F(xiàn)在訪(fǎng)問(wèn)淘寶的首頁(yè),每個(gè)人看到的頁(yè)面可能都是不一樣的,淘寶首頁(yè)中包含了很多根據(jù)訪(fǎng)問(wèn)者特征推薦的信息,而這些個(gè)性化的數(shù)據(jù)就可以理解為動(dòng)態(tài)數(shù)據(jù)了。這里再?gòu)?qiáng)調(diào)一下,我們所說(shuō)的靜態(tài)數(shù)據(jù),不能僅僅理解為傳統(tǒng)意義上完全存在磁盤(pán)上的HTML頁(yè)面,它也可能是經(jīng)過(guò)Java系統(tǒng)產(chǎn)生的頁(yè)面,但是它輸出的頁(yè)面本身不包含上面所說(shuō)的那些因素。也就是所謂“動(dòng)態(tài)”還是“靜態(tài)”,并不是說(shuō)數(shù)據(jù)本身是否動(dòng)靜,而是數(shù)據(jù)中是否含有和訪(fǎng)問(wèn)者相關(guān)的個(gè)性化數(shù)據(jù)。還有一點(diǎn)要注意,就是頁(yè)面中“不包含”,指的是“頁(yè)面的HTML源碼中不含有”,這一點(diǎn)務(wù)必要清楚。理解了靜態(tài)數(shù)據(jù)和動(dòng)態(tài)數(shù)據(jù),我估計(jì)你很容易就能想明白“動(dòng)靜分離”這個(gè)方案的來(lái)龍去脈了。分離了動(dòng)靜數(shù)據(jù),我們就可以對(duì)分離出來(lái)的靜態(tài)數(shù)據(jù)做緩存,有了緩存之后,靜態(tài)數(shù)據(jù)的“訪(fǎng)問(wèn)效率”自然就提高了。那么,怎樣對(duì)靜態(tài)數(shù)據(jù)做緩存呢?我在這里總結(jié)了幾個(gè)重點(diǎn)。第一,你應(yīng)該把靜態(tài)數(shù)據(jù)緩存到離用戶(hù)最近的地方。靜態(tài)數(shù)據(jù)就是那些相對(duì)不會(huì)變化的數(shù)據(jù),因此我們可以把它們緩存起來(lái)。緩存到哪里呢?常見(jiàn)的就三種,用戶(hù)瀏覽器里、CDN上或者在服務(wù)端的Cache中。你應(yīng)該根據(jù)情況,把它們盡量緩存到離用戶(hù)最近的地方。第二,靜態(tài)化改造就是要直接緩存HTTP連接。相較于普通的數(shù)據(jù)緩存而言,你肯定還聽(tīng)過(guò)系統(tǒng)的靜態(tài)化改造。靜態(tài)化改造是直接緩存HTTP連接而不是僅僅緩存數(shù)據(jù),如下圖所示,Web代理服務(wù)器根據(jù)請(qǐng)求URL,直接取出對(duì)應(yīng)的HTTP響應(yīng)頭和響應(yīng)體然后直接返回,這個(gè)響應(yīng)過(guò)程簡(jiǎn)單得連HTTP協(xié)議都不用重新組裝,甚至連HTTP請(qǐng)求頭也不需要解析。圖1靜態(tài)化改造第三,讓誰(shuí)來(lái)緩存靜態(tài)數(shù)據(jù)也很重要。不同語(yǔ)言寫(xiě)的Cache軟件處理緩存數(shù)據(jù)的效率也各不相同。以Java為例,因?yàn)镴ava系統(tǒng)本身也有其弱點(diǎn)(比如不擅長(zhǎng)處理大量連接請(qǐng)求,每個(gè)連接消耗的內(nèi)存較多,Servlet容器解析HTTP協(xié)議較慢),所以你可以不在Java層做緩存,而是直接在Web服務(wù)器層上做,這樣你就可以屏蔽Java語(yǔ)言層面的一些弱點(diǎn);而相比起來(lái),Web服務(wù)器(如Nginx、Apache、Varnish)也更擅長(zhǎng)處理大并發(fā)的靜態(tài)文件請(qǐng)求。如何做動(dòng)靜分離的改造理解了動(dòng)靜態(tài)數(shù)據(jù)的“why”和“what”,接下來(lái)我們就要看“how”了。我們?nèi)绾伟褎?dòng)態(tài)頁(yè)面改造成適合緩存的靜態(tài)頁(yè)面呢?其實(shí)也很簡(jiǎn)單,就是去除前面所說(shuō)的那幾個(gè)影響因素,把它們單獨(dú)分離出來(lái),做動(dòng)靜分離。下面,我以典型的商品詳情系統(tǒng)為例來(lái)詳細(xì)介紹。這里,你可以先打開(kāi)京東或者淘寶的商品詳情頁(yè),看看這個(gè)頁(yè)面里都有哪些動(dòng)靜數(shù)據(jù)。我們從以下5個(gè)方面來(lái)分離出動(dòng)態(tài)內(nèi)容。URL唯一化。商品詳情系統(tǒng)天然地就可以做到URL唯一化,比如每個(gè)商品都由ID來(lái)標(biāo)識(shí),那么\h/item.htm?id=xxxx就可以作為唯一的URL標(biāo)識(shí)。為啥要URL唯一呢?前面說(shuō)了我們是要緩存整個(gè)HTTP連接,那么以什么作為Key呢?就以URL作為緩存的Key,例如以id=xxx這個(gè)格式進(jìn)行區(qū)分。分離瀏覽者相關(guān)的因素。瀏覽者相關(guān)的因素包括是否已登錄,以及登錄身份等,這些相關(guān)因素我們可以單獨(dú)拆分出來(lái),通過(guò)動(dòng)態(tài)請(qǐng)求來(lái)獲取。分離時(shí)間因素。服務(wù)端輸出的時(shí)間也通過(guò)動(dòng)態(tài)請(qǐng)求獲取。異步化地域因素。詳情頁(yè)面上與地域相關(guān)的因素做成異步方式獲取,當(dāng)然你也可以通過(guò)動(dòng)態(tài)請(qǐng)求方式獲取,只是這里通過(guò)異步獲取更合適。去掉Cookie。服務(wù)端輸出的頁(yè)面包含的Cookie可以通過(guò)代碼軟件來(lái)刪除,如Web服務(wù)器Varnish可以通過(guò)unsetreq.http.cookie命令去掉Cookie。注意,這里說(shuō)的去掉Cookie并不是用戶(hù)端收到的頁(yè)面就不含Cookie了,而是說(shuō),在緩存的靜態(tài)數(shù)據(jù)中不含有Cookie。分離出動(dòng)態(tài)內(nèi)容之后,如何組織這些內(nèi)容頁(yè)就變得非常關(guān)鍵了。這里我要提醒你一點(diǎn),因?yàn)檫@其中很多動(dòng)態(tài)內(nèi)容都會(huì)被頁(yè)面中的其他模塊用到,如判斷該用戶(hù)是否已登錄、用戶(hù)ID是否匹配等,所以這個(gè)時(shí)候我們應(yīng)該將這些信息JSON化(用JSON格式組織這些數(shù)據(jù)),以方便前端獲取。前面我們介紹里用緩存的方式來(lái)處理靜態(tài)數(shù)據(jù)。而動(dòng)態(tài)內(nèi)容的處理通常有兩種方案:ESI(EdgeSideIncludes)方案和CSI(ClientSideInclude)方案。ESI方案(或者SSI):即在Web代理服務(wù)器上做動(dòng)態(tài)內(nèi)容請(qǐng)求,并將請(qǐng)求插入到靜態(tài)頁(yè)面中,當(dāng)用戶(hù)拿到頁(yè)面時(shí)已經(jīng)是一個(gè)完整的頁(yè)面了。這種方式對(duì)服務(wù)端性能有些影響,但是用戶(hù)體驗(yàn)較好。CSI方案。即單獨(dú)發(fā)起一個(gè)異步JavaScript請(qǐng)求,以向服務(wù)端獲取動(dòng)態(tài)內(nèi)容。這種方式服務(wù)端性能更佳,但是用戶(hù)端頁(yè)面可能會(huì)延時(shí),體驗(yàn)稍差。動(dòng)靜分離的幾種架構(gòu)方案前面我們通過(guò)改造把靜態(tài)數(shù)據(jù)和動(dòng)態(tài)數(shù)據(jù)做了分離,那么如何在系統(tǒng)架構(gòu)上進(jìn)一步對(duì)這些動(dòng)態(tài)和靜態(tài)數(shù)據(jù)重新組合,再完整地輸出給用戶(hù)呢?這就涉及對(duì)用戶(hù)請(qǐng)求路徑進(jìn)行合理的架構(gòu)了。根據(jù)架構(gòu)上的復(fù)雜度,有3種方案可選:實(shí)體機(jī)單機(jī)部署;統(tǒng)一Cache層;上CDN。方案1:實(shí)體機(jī)單機(jī)部署這種方案是將虛擬機(jī)改為實(shí)體機(jī),以增大Cache的容量,并且采用了一致性Hash分組的方式來(lái)提升命中率。這里將Cache分成若干組,是希望能達(dá)到命中率和訪(fǎng)問(wèn)熱點(diǎn)的平衡。Hash分組越少,緩存的命中率肯定就會(huì)越高,但短板是也會(huì)使單個(gè)商品集中在一個(gè)分組中,容易導(dǎo)致Cache被擊穿,所以我們應(yīng)該適當(dāng)增加多個(gè)相同的分組,來(lái)平衡訪(fǎng)問(wèn)熱點(diǎn)和命中率的問(wèn)題。這里我給出了實(shí)體機(jī)單機(jī)部署方案的結(jié)構(gòu)圖,如下:圖2Nginx+Cache+Java結(jié)構(gòu)實(shí)體機(jī)單機(jī)部署實(shí)體機(jī)單機(jī)部署有以下幾個(gè)優(yōu)點(diǎn):沒(méi)有網(wǎng)絡(luò)瓶頸,而且能使用大內(nèi)存;既能提升命中率,又能減少Gzip壓縮;減少Cache失效壓力,因?yàn)椴捎枚〞r(shí)失效方式,例如只緩存3秒鐘,過(guò)期即自動(dòng)失效。這個(gè)方案中,雖然把通常只需要虛擬機(jī)或者容器運(yùn)行的Java應(yīng)用換成實(shí)體機(jī),優(yōu)勢(shì)很明顯,它會(huì)增加單機(jī)的內(nèi)存容量,但是一定程度上也造成了CPU的浪費(fèi),因?yàn)閱蝹€(gè)的Java進(jìn)程很難用完整個(gè)實(shí)體機(jī)的CPU。另外就是,一個(gè)實(shí)體機(jī)上部署了Java應(yīng)用又作為Cache來(lái)使用,這造成了運(yùn)維上的高復(fù)雜度,所以這是一個(gè)折中的方案。如果你的公司里,沒(méi)有更多的系統(tǒng)有類(lèi)似需求,那么這樣做也比較合適,如果你們有多個(gè)業(yè)務(wù)系統(tǒng)都有靜態(tài)化改造的需求,那還是建議把Cache層單獨(dú)抽出來(lái)公用比較合理,如下面的方案2所示。方案2:統(tǒng)一Cache層所謂統(tǒng)一Cache層,就是將單機(jī)的Cache統(tǒng)一分離出來(lái),形成一個(gè)單獨(dú)的Cache集群。統(tǒng)一Cache層是個(gè)更理想的可推廣方案,該方案的結(jié)構(gòu)圖如下:圖3統(tǒng)一Cache將Cache層單獨(dú)拿出來(lái)統(tǒng)一管理可以減少運(yùn)維成本,同時(shí)也方便接入其他靜態(tài)化系統(tǒng)。此外,它還有一些優(yōu)點(diǎn)。單獨(dú)一個(gè)Cache層,可以減少多個(gè)應(yīng)用接入時(shí)使用Cache的成本。這樣接入的應(yīng)用只要維護(hù)自己的Java系統(tǒng)就好,不需要單獨(dú)維護(hù)Cache,而只關(guān)心如何使用即可。統(tǒng)一Cache的方案更易于維護(hù),如后面加強(qiáng)監(jiān)控、配置的自動(dòng)化,只需要一套解決方案就行,統(tǒng)一起來(lái)維護(hù)升級(jí)也比較方便??梢怨蚕韮?nèi)存,最大化利用內(nèi)存,不同系統(tǒng)之間的內(nèi)存可以動(dòng)態(tài)切換,從而能夠有效應(yīng)對(duì)各種攻擊。這種方案雖然維護(hù)上更方便了,但是也帶來(lái)了其他一些問(wèn)題,比如緩存更加集中,導(dǎo)致:Cache層內(nèi)部交換網(wǎng)絡(luò)成為瓶頸;緩存服務(wù)器的網(wǎng)卡也會(huì)是瓶頸;機(jī)器少風(fēng)險(xiǎn)較大,掛掉一臺(tái)就會(huì)影響很大一部分緩存數(shù)據(jù)。要解決上面這些問(wèn)題,可以再對(duì)Cache做Hash分組,即一組Cache緩存的內(nèi)容相同,這樣能夠避免熱點(diǎn)數(shù)據(jù)過(guò)度集中導(dǎo)致新的瓶頸產(chǎn)生。方案3:上CDN在將整個(gè)系統(tǒng)做動(dòng)靜分離后,我們自然會(huì)想到更進(jìn)一步的方案,就是將Cache進(jìn)一步前移到CDN上,因?yàn)镃DN離用戶(hù)最近,效果會(huì)更好。但是要想這么做,有以下幾個(gè)問(wèn)題需要解決。失效問(wèn)題。前面我們也有提到過(guò)緩存時(shí)效的問(wèn)題,不知道你有沒(méi)有理解,我再來(lái)解釋一下。談到靜態(tài)數(shù)據(jù)時(shí),我說(shuō)過(guò)一個(gè)關(guān)鍵詞叫“相對(duì)不變”,它的言外之意是“可能會(huì)變CDNCacheCDN的失效系統(tǒng)要求很高。命中率問(wèn)題。Cache最重要的一個(gè)衡量指標(biāo)就是“高命中率”,不然Cache的存在就失去了意義。同樣,如果將數(shù)據(jù)全部放到全國(guó)的CDN上,必然導(dǎo)致Cache分散,而Cache分散又會(huì)導(dǎo)致訪(fǎng)問(wèn)請(qǐng)求命中同一個(gè)Cache的可能性降低,那么命中率就成為一個(gè)問(wèn)題。發(fā)布更新問(wèn)題。如果一個(gè)業(yè)務(wù)系統(tǒng)每周都有日常業(yè)務(wù)需要發(fā)布,那么發(fā)布系統(tǒng)必須足夠簡(jiǎn)潔高效,而且你還要考慮有問(wèn)題時(shí)快速回滾和排查問(wèn)題的簡(jiǎn)便性。從前面的分析來(lái)看,將商品詳情系統(tǒng)放到全國(guó)的所有CDN節(jié)點(diǎn)上是不太現(xiàn)實(shí)的,因?yàn)榇嬖谑?wèn)題、命中率問(wèn)題以及系統(tǒng)的發(fā)布更新問(wèn)題。那么是否可以選擇若干個(gè)節(jié)點(diǎn)來(lái)嘗試實(shí)施呢?答案是“可以”,但是這樣的節(jié)點(diǎn)需要滿(mǎn)足幾個(gè)條件:靠近訪(fǎng)問(wèn)量比較集中的地區(qū);離主站相對(duì)較遠(yuǎn);節(jié)點(diǎn)到主站間的網(wǎng)絡(luò)比較好,而且穩(wěn)定;節(jié)點(diǎn)容量比較大,不會(huì)占用其他CDN太多的資源。最后,還有一點(diǎn)也很重要,那就是:節(jié)點(diǎn)不要太多?;谏厦鎺讉€(gè)因素,選擇CDN的二級(jí)Cache比較合適,因?yàn)槎?jí)Cache數(shù)量偏少,容量也更大,讓用戶(hù)的請(qǐng)求先回源的CDN的二級(jí)Cache中,如果沒(méi)命中再回源站獲取數(shù)據(jù),部署方式如下圖所示:圖4CDN化部署方案使用CDN的二級(jí)Cache作為緩存,可以達(dá)到和當(dāng)前服務(wù)端靜態(tài)化Cache類(lèi)似的命中率,因?yàn)楣?jié)點(diǎn)數(shù)不多,Cache不是很分散,訪(fǎng)問(wèn)量也比較集中,這樣也就解決了命中率問(wèn)題,同時(shí)能夠給用戶(hù)最好的訪(fǎng)問(wèn)體驗(yàn),是當(dāng)前比較理想的一種CDN化方案。除此之外,CDN化部署方案還有以下幾個(gè)特點(diǎn):把整個(gè)頁(yè)面緩存在用戶(hù)瀏覽器中;如果強(qiáng)制刷新整個(gè)頁(yè)面,也會(huì)請(qǐng)求CDN;實(shí)際有效請(qǐng)求,只是用戶(hù)對(duì)“刷新?lián)寣殹卑粹o的點(diǎn)擊。這樣就把90%的靜態(tài)數(shù)據(jù)緩存在了用戶(hù)端或者CDN上,當(dāng)真正秒殺時(shí),用戶(hù)只需要點(diǎn)擊特殊的“刷新?lián)寣殹卑粹o,而不需要刷新整個(gè)頁(yè)面。這樣一來(lái),系統(tǒng)只是向服務(wù)端請(qǐng)求很少的有效數(shù)據(jù),而不需要重復(fù)請(qǐng)求大量的靜態(tài)數(shù)據(jù)。秒殺的動(dòng)態(tài)數(shù)據(jù)和普通詳情頁(yè)面的動(dòng)態(tài)數(shù)據(jù)相比更少,性能也提升了3倍以上。所以“搶寶”這種設(shè)計(jì)思路,讓我們不用刷新頁(yè)面就能夠很好地請(qǐng)求到服務(wù)端最新的動(dòng)態(tài)數(shù)據(jù)??偨Y(jié)一下CDN你可能會(huì)問(wèn),存儲(chǔ)在瀏覽器或CDN上,有多大區(qū)別?我的回答是:區(qū)別很大!因?yàn)樵贑DN上,我們可以做主動(dòng)失效,而在用戶(hù)的瀏覽器里就更不可控,如果用戶(hù)不主動(dòng)刷新的話(huà),你很難主動(dòng)地把消息推送給用戶(hù)的瀏覽器。另外,在什么地方把靜態(tài)數(shù)據(jù)和動(dòng)態(tài)數(shù)據(jù)合并并渲染出一個(gè)完整的頁(yè)面也很關(guān)鍵,假如在用戶(hù)的瀏覽器里合并,那么服務(wù)端可以減少渲染整個(gè)頁(yè)面的CPU消耗。如果在服務(wù)端合并的話(huà),就要考慮緩存的數(shù)據(jù)是否進(jìn)行Gzip壓縮了:如果緩存Gzip壓縮后的靜態(tài)數(shù)據(jù)可以減少緩存的數(shù)據(jù)量,但是進(jìn)行頁(yè)面合并渲染時(shí)就要先解壓,然后再壓縮完整的頁(yè)面數(shù)據(jù)輸出給用戶(hù);如果緩存未壓縮的靜態(tài)數(shù)據(jù),這樣不用解壓靜態(tài)數(shù)據(jù),但是會(huì)增加緩存容量。雖然這些都是細(xì)節(jié)問(wèn)題,但你在設(shè)計(jì)架構(gòu)方案時(shí)都需要考慮清楚。最后,歡迎在留言區(qū)分享討論你對(duì)于數(shù)據(jù)動(dòng)靜分離的一些關(guān)鍵認(rèn)知,我會(huì)第一時(shí)間給你反饋。|二八原則:有針對(duì)性地處理好系統(tǒng)的“熱點(diǎn)數(shù)據(jù)”假設(shè)你的系統(tǒng)中存儲(chǔ)有幾十億上百億的商品,而每天有千萬(wàn)級(jí)的商品被上億的用戶(hù)訪(fǎng)問(wèn),那么肯定有一部分被大量用戶(hù)訪(fǎng)問(wèn)的熱賣(mài)商品,這就是我們常說(shuō)的“熱點(diǎn)商品”。這些熱點(diǎn)商品中最極端的例子就是秒殺商品,它們?cè)诤芏虝r(shí)間內(nèi)被大量用戶(hù)執(zhí)行訪(fǎng)問(wèn)、添加購(gòu)物車(chē)、下單等操作,這些操作我們就稱(chēng)為“熱點(diǎn)操作”。那么問(wèn)題來(lái)了:這些熱點(diǎn)對(duì)系統(tǒng)有啥影響,我們非要關(guān)注這些熱點(diǎn)嗎?為什么要關(guān)注熱點(diǎn)我們一定要關(guān)注熱點(diǎn),因?yàn)闊狳c(diǎn)會(huì)對(duì)系統(tǒng)產(chǎn)生一系列的影響。首先,熱點(diǎn)請(qǐng)求會(huì)大量占用服務(wù)器處理資源,雖然這個(gè)熱點(diǎn)可能只占請(qǐng)求總量的億分之一,然而卻可能搶占90%的服務(wù)器資源,如果這個(gè)熱點(diǎn)請(qǐng)求還是沒(méi)有價(jià)值的無(wú)效請(qǐng)求,那么對(duì)系統(tǒng)資源來(lái)說(shuō)完全是浪費(fèi)。其次,即使這些熱點(diǎn)是有效的請(qǐng)求,我們也要識(shí)別出來(lái)做針對(duì)性的優(yōu)化,從而用更低的代價(jià)來(lái)支撐這些熱點(diǎn)請(qǐng)求。既然熱點(diǎn)對(duì)系統(tǒng)來(lái)說(shuō)這么重要,那么熱點(diǎn)到底包含哪些內(nèi)容呢?什么是“熱點(diǎn)”熱點(diǎn)分為熱點(diǎn)操作和熱點(diǎn)數(shù)據(jù)請(qǐng)求”和“寫(xiě)請(qǐng)求”,這兩種熱點(diǎn)請(qǐng)求的處理方式大相徑庭,讀請(qǐng)求的優(yōu)化空間要大一CAP而“熱點(diǎn)數(shù)據(jù)”比較好理解,那就是用戶(hù)的熱點(diǎn)請(qǐng)求對(duì)應(yīng)的數(shù)據(jù)。而熱點(diǎn)數(shù)據(jù)又分為“靜態(tài)熱點(diǎn)數(shù)據(jù)”和“動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)”。所謂“靜態(tài)熱點(diǎn)數(shù)據(jù)”,就是能夠提前預(yù)測(cè)的熱點(diǎn)數(shù)據(jù)。例如,我們可以通過(guò)賣(mài)家報(bào)名的方式提前篩選出來(lái),通過(guò)報(bào)名系統(tǒng)對(duì)這些熱點(diǎn)商品進(jìn)行打標(biāo)。另外,我們還可以通過(guò)大數(shù)據(jù)分析來(lái)提前發(fā)現(xiàn)熱點(diǎn)商品,比如我們分析歷史成交記錄、用戶(hù)的購(gòu)物車(chē)記錄,來(lái)發(fā)現(xiàn)哪些商品可能更熱門(mén)、更好賣(mài),這些都是可以提前分析出來(lái)的熱點(diǎn)。例如,賣(mài)家在抖音上做了廣告,然后商品一下就火了,導(dǎo)致它在短時(shí)間內(nèi)被大量購(gòu)買(mǎi)。對(duì)熱點(diǎn)數(shù)據(jù)來(lái)介紹如何進(jìn)行優(yōu)化。發(fā)現(xiàn)熱點(diǎn)數(shù)據(jù)前面,我介紹了如何對(duì)單個(gè)秒殺商品的頁(yè)面數(shù)據(jù)進(jìn)行動(dòng)靜分離,以便針對(duì)性地對(duì)靜態(tài)數(shù)據(jù)做優(yōu)化處理,那么另外一個(gè)關(guān)鍵的問(wèn)題來(lái)了:如何發(fā)現(xiàn)這些秒殺商品,或者更準(zhǔn)確地說(shuō),如何發(fā)現(xiàn)熱點(diǎn)商品呢?你可能會(huì)說(shuō)“參加秒殺的商品就是秒殺商品啊”,沒(méi)錯(cuò),關(guān)鍵是系統(tǒng)怎么知道哪些商品參加了秒殺活動(dòng)呢?所以,你要有一個(gè)機(jī)制提前來(lái)區(qū)分普通商品和秒殺商品。我們從發(fā)現(xiàn)靜態(tài)熱點(diǎn)和發(fā)現(xiàn)動(dòng)態(tài)熱點(diǎn)兩個(gè)方面來(lái)看一下。發(fā)現(xiàn)靜態(tài)熱點(diǎn)數(shù)據(jù)如前面講的,靜態(tài)熱點(diǎn)數(shù)據(jù)可以通過(guò)商業(yè)手段,例如強(qiáng)制讓賣(mài)家通過(guò)報(bào)名參加的方式提前把熱點(diǎn)商品篩選出來(lái),實(shí)現(xiàn)方式是通過(guò)一個(gè)運(yùn)營(yíng)系統(tǒng),把參加活動(dòng)的商品數(shù)據(jù)進(jìn)行打標(biāo),然后通過(guò)一個(gè)后臺(tái)系統(tǒng)對(duì)這些熱點(diǎn)商品進(jìn)行預(yù)處理,如提前進(jìn)行緩存。但是這種通過(guò)報(bào)名提前篩選的方式也會(huì)帶來(lái)新的問(wèn)題,即增加賣(mài)家的使用成本,而且實(shí)時(shí)性較差,也不太靈活。TOPNTOPN發(fā)現(xiàn)動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)個(gè)痛點(diǎn),就是實(shí)時(shí)性較差,如果我們的系統(tǒng)能在秒級(jí)內(nèi)自動(dòng)發(fā)現(xiàn)熱點(diǎn)商品那就完美了。想辦法實(shí)現(xiàn)熱點(diǎn)的動(dòng)態(tài)發(fā)現(xiàn)功能。這里我給出一個(gè)動(dòng)態(tài)熱點(diǎn)發(fā)現(xiàn)系統(tǒng)的具體實(shí)現(xiàn)。構(gòu)建一個(gè)異步的系統(tǒng),它可以收集交易鏈路上各個(gè)環(huán)節(jié)中的中間件產(chǎn)品的熱點(diǎn)Key,如Nginx、緩存、RPC服務(wù)框架等這些中間件(一些中間件產(chǎn)品本身已經(jīng)有熱點(diǎn)統(tǒng)計(jì)模塊)。建立一個(gè)熱點(diǎn)上報(bào)和可以按照需求訂閱的熱點(diǎn)服務(wù)的下發(fā)規(guī)范,主要目的是通過(guò)交易鏈路上各個(gè)系統(tǒng)(包括詳情、購(gòu)物車(chē)、交易、優(yōu)惠、庫(kù)存、物流等)訪(fǎng)問(wèn)的時(shí)間差,把上游已經(jīng)發(fā)現(xiàn)的熱點(diǎn)透?jìng)鹘o下游系統(tǒng),提前做好保護(hù)。比如,對(duì)于大促高峰期,詳情系統(tǒng)是最早知道的,在統(tǒng)一接入層上Nginx模塊統(tǒng)計(jì)的熱點(diǎn)URL。將上游系統(tǒng)收集的熱點(diǎn)數(shù)據(jù)發(fā)送到熱點(diǎn)服務(wù)臺(tái),然后下游系統(tǒng)(如交易系統(tǒng))就會(huì)知道哪些商品會(huì)被頻繁調(diào)用,然后做熱點(diǎn)保護(hù)。購(gòu)頁(yè)面(包括首頁(yè)、搜索頁(yè)面、商品詳情、購(gòu)物車(chē)等)過(guò)這些系統(tǒng)中的中間件來(lái)收集熱點(diǎn)數(shù)據(jù),并記錄到日志中。1我們通過(guò)部署在每臺(tái)機(jī)器上的Agent把日志匯總到聚合和分析集群中,然后把符合一定規(guī)則的熱點(diǎn)數(shù)據(jù),通過(guò)訂閱分發(fā)系統(tǒng)再推送到相應(yīng)的系統(tǒng)中。你可以是把熱點(diǎn)數(shù)據(jù)填充到Cache中,或者直接推送到應(yīng)用服務(wù)器的內(nèi)存中,還可以對(duì)這些數(shù)據(jù)進(jìn)行攔截,總之下游系統(tǒng)可以訂閱這些數(shù)據(jù),然后根據(jù)自己的需求決定如何處理這些數(shù)據(jù)。打造熱點(diǎn)發(fā)現(xiàn)系統(tǒng)時(shí),我根據(jù)以往經(jīng)驗(yàn)總結(jié)了幾點(diǎn)注意事項(xiàng)。這個(gè)熱點(diǎn)服務(wù)后臺(tái)抓取熱點(diǎn)數(shù)據(jù)日志最好采用異步方式,因?yàn)椤爱惒健币环矫姹阌诒WC通用性,另一方面又不影響業(yè)務(wù)系統(tǒng)和中間件產(chǎn)品的主流程。熱點(diǎn)服務(wù)發(fā)現(xiàn)和中間件自身的熱點(diǎn)保護(hù)模塊并存,每個(gè)中間件和應(yīng)用還需要保護(hù)自己。熱點(diǎn)服務(wù)臺(tái)提供熱點(diǎn)數(shù)據(jù)的收集和訂閱服務(wù),便于把各個(gè)系統(tǒng)的熱點(diǎn)數(shù)據(jù)透明出來(lái)。熱點(diǎn)發(fā)現(xiàn)要做到接近實(shí)時(shí)(3s內(nèi)完成熱點(diǎn)數(shù)據(jù)的發(fā)現(xiàn)),因?yàn)橹挥凶龅浇咏鼘?shí)時(shí),動(dòng)態(tài)發(fā)現(xiàn)才有意義,才能實(shí)時(shí)地對(duì)下游系統(tǒng)提供保護(hù)。處理熱點(diǎn)數(shù)據(jù)處理熱點(diǎn)數(shù)據(jù)通常有幾種思路:一是優(yōu)化,二是限制,三是隔離。先來(lái)說(shuō)說(shuō)優(yōu)化。優(yōu)化熱點(diǎn)數(shù)據(jù)最有效的辦法就是緩存熱點(diǎn)數(shù)據(jù),如果熱點(diǎn)數(shù)據(jù)做了動(dòng)靜分離,那么可以長(zhǎng)期緩存靜態(tài)數(shù)據(jù)。但是,緩存熱點(diǎn)數(shù)據(jù)更多的是“臨時(shí)”緩存,即不管是靜態(tài)數(shù)據(jù)還是動(dòng)態(tài)數(shù)據(jù),都用一個(gè)隊(duì)列短暫地緩存數(shù)秒鐘,由于隊(duì)列長(zhǎng)度有限,可以采用LRU淘汰算法替換。再來(lái)說(shuō)說(shuō)限制。限制更多的是一種保護(hù)機(jī)制,限制的辦法也有很多,例如對(duì)被訪(fǎng)問(wèn)商品的ID做一致性Hash,然后根據(jù)Hash做分桶,每個(gè)分桶設(shè)置一個(gè)處理隊(duì)列,這樣可以把熱點(diǎn)商品限制在一個(gè)請(qǐng)求隊(duì)列里,防止因某些熱點(diǎn)商品占用太多的服務(wù)器資源,而使其他請(qǐng)求始終得不到服務(wù)器的處理資源。最后介紹一下隔離。秒殺系統(tǒng)設(shè)計(jì)的第一個(gè)原則就是將這種熱點(diǎn)數(shù)據(jù)隔離出來(lái),不要讓1%的請(qǐng)求影響到另外的99%,隔離出來(lái)后也更方便對(duì)這1%的請(qǐng)求做針對(duì)性的優(yōu)化。具體到“秒殺”業(yè)務(wù),我們可以在以下幾個(gè)層次實(shí)現(xiàn)隔離。業(yè)務(wù)隔離。把秒殺做成一種營(yíng)銷(xiāo)活動(dòng),賣(mài)家要參加秒殺這種營(yíng)銷(xiāo)活動(dòng)需要單獨(dú)報(bào)名,從技術(shù)上來(lái)說(shuō),賣(mài)家報(bào)名后對(duì)我們來(lái)說(shuō)就有了已知熱點(diǎn),因此可以提前做好預(yù)熱。系統(tǒng)隔離。系統(tǒng)隔離更多的是運(yùn)行時(shí)的隔離,可以通過(guò)分組部署的方式和另外99%分開(kāi)。秒殺可以申請(qǐng)單獨(dú)的域名,目的也是讓請(qǐng)求落到不同的集群中。數(shù)據(jù)隔離。秒殺所調(diào)用的數(shù)據(jù)大部分都是熱點(diǎn)數(shù)據(jù),比如會(huì)啟用單獨(dú)的Cache集群或者M(jìn)ySQL數(shù)據(jù)庫(kù)來(lái)放熱點(diǎn)數(shù)據(jù),目的也是不想0.01%的數(shù)據(jù)有機(jī)會(huì)影響99.99%數(shù)據(jù)。當(dāng)然了,實(shí)現(xiàn)隔離有很多種辦法。比如,你可以按照用戶(hù)來(lái)區(qū)分,給不同的用戶(hù)分配不同的Cookie,在接入層,路由到不同的服務(wù)接口中;再比如,你還可以在接入層針對(duì)URL中的不同Path來(lái)設(shè)置限流策略。服務(wù)層調(diào)用不同的服務(wù)接口,以及數(shù)據(jù)層通過(guò)給數(shù)據(jù)打標(biāo)來(lái)區(qū)分等等這些措施,其目的都是把已經(jīng)識(shí)別出來(lái)的熱點(diǎn)請(qǐng)求和普通的請(qǐng)求區(qū)分開(kāi)??偨Y(jié)一下本文與數(shù)據(jù)的動(dòng)靜分離不一樣,它從另外一個(gè)維度對(duì)數(shù)據(jù)進(jìn)行了區(qū)分處理。你要明白,區(qū)分的目的主要還是對(duì)讀熱點(diǎn)數(shù)據(jù)加以?xún)?yōu)化,對(duì)照“4要1不要”原則,它可以減少請(qǐng)求量,也可以減少請(qǐng)求的路徑。因?yàn)榫彺娴臄?shù)據(jù)都是經(jīng)過(guò)多個(gè)請(qǐng)求,或者從多個(gè)系統(tǒng)中獲取的數(shù)據(jù)經(jīng)過(guò)計(jì)算后的結(jié)果。熱點(diǎn)的發(fā)現(xiàn)和隔離不僅對(duì)“秒殺”這個(gè)場(chǎng)景有意義,對(duì)其他的高性能分布式系統(tǒng)也非常有價(jià)值,尤其是熱點(diǎn)的隔離非常重要。我介紹了業(yè)務(wù)層面的隔離和數(shù)據(jù)層面的隔離方式,最重要最簡(jiǎn)單的方式就是獨(dú)立出來(lái)一個(gè)集群,單獨(dú)處理熱點(diǎn)數(shù)據(jù)。方式,比如人工標(biāo)識(shí)、大數(shù)據(jù)統(tǒng)計(jì)計(jì)算,以及實(shí)時(shí)熱點(diǎn)發(fā)現(xiàn)方案,希望能夠給你啟發(fā)。問(wèn)題的不同思路或方案,非常期待。|流量削峰這事應(yīng)該怎么做?如果你看過(guò)秒殺系統(tǒng)的流量監(jiān)控圖的話(huà),你會(huì)發(fā)現(xiàn)它是一條直線(xiàn),就在秒殺開(kāi)始那一秒是一條很直很直的線(xiàn),這是因?yàn)槊霘⒄?qǐng)求在時(shí)間上高度集中于某一特定的時(shí)間點(diǎn)。這樣一來(lái),就會(huì)導(dǎo)致一個(gè)特別高的流量峰值,它對(duì)資源的消耗是瞬時(shí)的。但是對(duì)秒殺這個(gè)場(chǎng)景來(lái)說(shuō),最終能夠搶到商品的人數(shù)是固定的,也就是說(shuō)100人和10000人發(fā)起請(qǐng)求的結(jié)果都是一樣的,并發(fā)度越高,無(wú)效請(qǐng)求也越多。來(lái)刷頁(yè)面,但是真正開(kāi)始下單時(shí),秒殺請(qǐng)求并不是越多越好。因此我們可以設(shè)計(jì)一些規(guī)則,讓并發(fā)的請(qǐng)求更多地延緩,而且我們甚至可以過(guò)濾掉一些無(wú)效請(qǐng)求。為什么要削峰為什么要削峰呢?或者說(shuō)峰值會(huì)帶來(lái)哪些壞處?景,削峰從本質(zhì)上來(lái)說(shuō)就是更多地延緩用戶(hù)請(qǐng)求的發(fā)出,以便減少和過(guò)濾掉一些無(wú)效請(qǐng)求,它遵從“請(qǐng)求數(shù)要盡量少”的原則。今天,我就來(lái)介紹一下流量削峰的一些操作思路:排隊(duì)、答題、分層過(guò)濾。這幾種方式都是無(wú)損(即不會(huì)損失用戶(hù)的發(fā)出請(qǐng)求)的實(shí)現(xiàn)方案,當(dāng)然還有些有損的實(shí)現(xiàn)方案,包括我們后面要介紹的關(guān)于穩(wěn)定性的一些辦法,比如限流和機(jī)器負(fù)載保護(hù)等一些強(qiáng)制措施也能達(dá)到削峰保護(hù)的目的,當(dāng)然這都是不得已的一些措施,因此就不歸類(lèi)到這里了。排隊(duì)要對(duì)流量進(jìn)行削峰,最容易想到的解決方案就是用消息隊(duì)列來(lái)緩沖瞬時(shí)流量,把同步的直接調(diào)用轉(zhuǎn)換成異步的間接推送,中間通過(guò)一個(gè)隊(duì)列在一端承接瞬時(shí)的流量洪峰,在另一端平滑地將消息推送出去。在這里,消息隊(duì)列就像“水庫(kù)”一樣,攔蓄上游的洪水,削減進(jìn)入下游河道的洪峰流量,從而達(dá)到減免洪水災(zāi)害的目的。用消息隊(duì)列來(lái)緩沖瞬時(shí)流量的方案,如下圖所示:圖1用消息隊(duì)列來(lái)緩沖瞬時(shí)流量但是,如果流量峰值持續(xù)一段時(shí)間達(dá)到了消息隊(duì)列的處理上限,例如本機(jī)的消息積壓達(dá)到了存儲(chǔ)空間的上限,消息隊(duì)列同樣也會(huì)被壓垮,這樣雖然保護(hù)了下游的系統(tǒng),但是和直接把請(qǐng)求丟棄也沒(méi)多大的區(qū)別。就像遇到洪水爆發(fā)時(shí),即使是有水庫(kù)恐怕也無(wú)濟(jì)于事。除了消息隊(duì)列,類(lèi)似的排隊(duì)方式還有很多,例如:利用線(xiàn)程池加鎖等待也是一種常用的排隊(duì)方式;先進(jìn)先出、先進(jìn)后出等常用的內(nèi)存排隊(duì)算法的實(shí)現(xiàn)方式;把請(qǐng)求序列化到文件中,然后再順序地讀文件(例如基于MySQLbinlog的同步機(jī)制)來(lái)恢復(fù)請(qǐng)求等方式??梢钥吹?,這些方式都有一個(gè)共同特征,就是把“一步的操作”變成“兩步的操作”,其中增加的一步操作用來(lái)起到緩沖的作用。說(shuō)到這里你可能會(huì)說(shuō),這樣一來(lái)增加了訪(fǎng)問(wèn)請(qǐng)求的路徑啊,并不符合我們介紹的“4要1不要”原則。沒(méi)錯(cuò),的確看起來(lái)不太合理,但是如果不增加一個(gè)緩沖步驟,那么在一些場(chǎng)景下系統(tǒng)很可能會(huì)直接崩潰,所以最終還是需要你做出妥協(xié)和平衡。答題你是否還記得,最早期的秒殺只是純粹地刷新頁(yè)面和點(diǎn)擊購(gòu)買(mǎi)按鈕,它是后來(lái)才增加了答題功能的。那么,為什么要增加答題功能呢?這主要是為了增加購(gòu)買(mǎi)的復(fù)雜度,從而達(dá)到兩個(gè)目的。第一個(gè)目的是防止部分買(mǎi)家使用秒殺器在參加秒殺時(shí)作弊。2011年秒殺非常火的時(shí)候,秒殺器也比較猖獗,因而沒(méi)有達(dá)到全民參與和營(yíng)銷(xiāo)的目的,所以系統(tǒng)增加了答題來(lái)限制秒殺器。增加答題后,下單的時(shí)間基本控制在2s后,秒殺器的下單比例也大大下降。答題頁(yè)面如下圖所示。圖2答題頁(yè)面第二個(gè)目的其實(shí)就是延緩請(qǐng)求,起到對(duì)請(qǐng)求流量進(jìn)行削峰的作用,從而讓系統(tǒng)能夠更好地支持瞬時(shí)的流量高峰。這個(gè)重要的功能就是把峰值的下單請(qǐng)求拉長(zhǎng),從以前的1s之內(nèi)延長(zhǎng)到2s~10s。這樣一來(lái),請(qǐng)求峰值基于時(shí)間分片了。這個(gè)時(shí)間的分片對(duì)服務(wù)端處理并發(fā)非常重要,會(huì)大大減輕壓力。而且,由于請(qǐng)求具有先后順序,靠后的請(qǐng)求到來(lái)時(shí)自然也就沒(méi)有庫(kù)存了,因此根本到不了最后的下單步驟,所以真正的并發(fā)寫(xiě)就非常有限了。這種設(shè)計(jì)思路目前用得非常普遍,如當(dāng)年支付寶的“咻一咻”、微信的“搖一搖”都是類(lèi)似的方式。這里,我重點(diǎn)說(shuō)一下秒殺答題的設(shè)計(jì)思路。圖3秒殺答題如上圖所示,整個(gè)秒殺答題的邏輯主要分為3部分。題庫(kù)生成模塊,這個(gè)部分主要就是生成一個(gè)個(gè)問(wèn)題和答案,其實(shí)題目和答案本身并不需要很復(fù)雜,重要的是能夠防止由機(jī)器來(lái)算出結(jié)果,即防止秒殺器來(lái)答題。題庫(kù)的推送模塊,用于在秒殺答題前,把題目提前推送給詳情系統(tǒng)和交易系統(tǒng)。題庫(kù)的推送主要是為了保證每次用戶(hù)請(qǐng)求的題目是唯一的,目的也是防止答題作弊。題目的圖片生成模塊CDNkeyMD5問(wèn)題key:userId+itemId+question_Id+time+PK答案key:userId+itemId+answer+PK驗(yàn)證的邏輯如下圖所示:圖4答題的驗(yàn)證邏輯注意,這里面的驗(yàn)證邏輯,除了驗(yàn)證問(wèn)題的答案以外,還包括用戶(hù)本身身份的驗(yàn)證,例如是否已經(jīng)登錄、用戶(hù)的Cookie是否完整、用戶(hù)是否重復(fù)頻繁提交等。除了做正確性驗(yàn)證,我們還可以對(duì)提交答案的時(shí)間做些限制,例如從開(kāi)始答題到接受答案要超過(guò)1s,因?yàn)樾∮?s是人為操作的可能性很小,這樣也能防止機(jī)器答題的情況。分層過(guò)濾前面介紹的排隊(duì)和答題要么是少發(fā)請(qǐng)求,要么對(duì)發(fā)出來(lái)的請(qǐng)求進(jìn)行緩沖,而針對(duì)秒殺場(chǎng)景還有一種方法,就是對(duì)請(qǐng)求進(jìn)行分層過(guò)濾,從而過(guò)濾掉一些無(wú)效的請(qǐng)求。分層過(guò)濾其實(shí)就是采用“漏斗”式設(shè)計(jì)來(lái)處理請(qǐng)求的,如下圖所示。圖5分層過(guò)濾假如請(qǐng)求分別經(jīng)過(guò)CDN、前臺(tái)讀系統(tǒng)(如商品詳情系統(tǒng))、后臺(tái)系統(tǒng)(如交易系統(tǒng))和數(shù)據(jù)庫(kù)這幾層,那么:大部分?jǐn)?shù)據(jù)和流量在用戶(hù)瀏覽器或者CDN上獲取,這一層可以攔截大部分?jǐn)?shù)據(jù)的讀取;經(jīng)過(guò)第二層(即前臺(tái)系統(tǒng))時(shí)數(shù)據(jù)(包括強(qiáng)一致性的數(shù)據(jù))盡量得走Cache,過(guò)濾一些無(wú)效的請(qǐng)求;再到第三層后臺(tái)系統(tǒng),主要做數(shù)據(jù)的二次檢驗(yàn),對(duì)系統(tǒng)做好保護(hù)和限流,這樣數(shù)據(jù)量和請(qǐng)求就進(jìn)一步減少;最后在數(shù)據(jù)層完成數(shù)據(jù)的強(qiáng)一致性校驗(yàn)。這樣就像漏斗一樣,盡量把數(shù)據(jù)量和請(qǐng)求量一層一層地過(guò)濾和減少了。分層過(guò)濾的核心思想是:在不同的層次盡可能地過(guò)濾掉無(wú)效請(qǐng)求,讓“漏斗”最末端的才是有效請(qǐng)求。而要達(dá)到這種效果,我們就必須對(duì)數(shù)據(jù)做分層的校驗(yàn)。分層校驗(yàn)的基本原則是:將動(dòng)態(tài)請(qǐng)求的讀數(shù)據(jù)緩存(Cache)在Web端,過(guò)濾掉無(wú)效的數(shù)據(jù)讀;對(duì)讀數(shù)據(jù)不做強(qiáng)一致性校驗(yàn),減少因?yàn)橐恢滦孕r?yàn)產(chǎn)生瓶頸的問(wèn)題;對(duì)寫(xiě)請(qǐng)求做限流保護(hù),將超出系統(tǒng)承載能力的請(qǐng)求過(guò)濾掉;對(duì)寫(xiě)數(shù)據(jù)進(jìn)行強(qiáng)一致性校驗(yàn),只保留最后有效的數(shù)據(jù)。分層校驗(yàn)的目的是:在讀系統(tǒng)中,盡量減少由于一致性校驗(yàn)帶來(lái)的系統(tǒng)瓶頸,但是盡量將不影響性能的檢查條件提前,如用戶(hù)是否具有秒殺資格、商品狀態(tài)是否正常、用戶(hù)答題是否正確、秒殺是否已經(jīng)結(jié)束、是否非法請(qǐng)求、營(yíng)銷(xiāo)等價(jià)物是否充足等;在寫(xiě)數(shù)據(jù)系統(tǒng)中,主要對(duì)寫(xiě)的數(shù)據(jù)(如“庫(kù)存”)做一致性檢查,最后在數(shù)據(jù)庫(kù)層保證數(shù)據(jù)的最終準(zhǔn)確性(如“庫(kù)存”不能減為負(fù)數(shù))??偨Y(jié)一下今天,我介紹了如何在網(wǎng)站面臨大流量沖擊時(shí)進(jìn)行請(qǐng)求的削峰,并主要介紹了削峰的3求發(fā)出的時(shí)間,在請(qǐng)求發(fā)出后承接請(qǐng)求時(shí)進(jìn)行控制,最后再對(duì)不符合條件的請(qǐng)求進(jìn)行過(guò)濾;最后一種是對(duì)請(qǐng)求進(jìn)行分層過(guò)濾。其中,隊(duì)列緩沖方式更加通用,它適用于內(nèi)部上下游系統(tǒng)之間調(diào)用請(qǐng)求不平緩的場(chǎng)景,由于內(nèi)部系統(tǒng)的服務(wù)質(zhì)量要求不能隨意丟棄請(qǐng)求,所以使用消息隊(duì)列能起到很好的削峰和緩沖作用。而答題更適用于秒殺或者營(yíng)銷(xiāo)活動(dòng)等應(yīng)用場(chǎng)景,在請(qǐng)求發(fā)起端就控制發(fā)起請(qǐng)求的速度,因?yàn)樵降胶竺鏌o(wú)效請(qǐng)求也會(huì)越多,所以配合后面介紹的分層攔截的方式,可以更進(jìn)一步減少無(wú)效請(qǐng)求對(duì)系統(tǒng)資源的消耗。分層過(guò)濾非常適合交易性的寫(xiě)請(qǐng)求,比如減庫(kù)存或者拼車(chē)這種場(chǎng)景,在讀的時(shí)候需要知道還有沒(méi)有庫(kù)存或者是否還有剩余空座位。但是由于庫(kù)存和座位又是不停變化的,所以讀的數(shù)據(jù)是否一定要非常準(zhǔn)確呢?其實(shí)不一定,你可以放一些請(qǐng)求過(guò)去,然后在真正減的時(shí)候再做強(qiáng)一致性保證,這樣既過(guò)濾一些請(qǐng)求又解決了強(qiáng)一致性讀的瓶頸。不過(guò),在削峰的處理方式上除了采用技術(shù)手段,其實(shí)還可以采用業(yè)務(wù)手段來(lái)達(dá)到一定效作用。最后,歡迎你在留言區(qū)和我交流,你也可以說(shuō)說(shuō)在實(shí)際工作中,還有哪些對(duì)流量進(jìn)行削峰的不同思路或方案,非常期待。|影響性能的因素有哪些?又該如何提高系統(tǒng)的性能?不知不覺(jué),我們已經(jīng)講到第五篇了,不知道聽(tīng)到這里,你對(duì)于秒殺系統(tǒng)的構(gòu)建有沒(méi)有形成一些框架性的認(rèn)識(shí),這里我再帶你簡(jiǎn)單回憶下前面的主線(xiàn)。前面的四篇文章里,我介紹的內(nèi)容多少都和優(yōu)化有關(guān):第一篇介紹了一些指導(dǎo)原則;第二篇和第三篇從動(dòng)靜分離和熱點(diǎn)數(shù)據(jù)兩個(gè)維度,介紹了如何有針對(duì)性地對(duì)數(shù)據(jù)進(jìn)行區(qū)分和優(yōu)化處理;第四篇介紹了在保證實(shí)現(xiàn)基本業(yè)務(wù)功能的前提下,盡量減少和過(guò)濾一些無(wú)效請(qǐng)求的思路。這幾篇文章既是在講根據(jù)指導(dǎo)原則實(shí)現(xiàn)的具體案例,也是在講如何實(shí)現(xiàn)能夠讓整個(gè)系統(tǒng)結(jié)合秒殺這一場(chǎng)景,重點(diǎn)給你介紹下服務(wù)端的一些優(yōu)化技巧。影響性能的因素你想要提升性能,首先肯定要知道哪些因素對(duì)于系統(tǒng)性能的影響最大,然后再針對(duì)這些具體的因素想辦法做優(yōu)化,是不是這個(gè)邏輯?那么,哪些因素對(duì)性能有影響呢?在回答這個(gè)問(wèn)題之前,我們先定義一下“性能”,服務(wù)設(shè)備不同對(duì)性能的定義也是不一樣的,例如CPU主要看主頻、磁盤(pán)主要看IOPS(Input/OutputOperationsPerSecond,即每秒進(jìn)行讀寫(xiě)操作的次數(shù))。而今天我們討論的主要是系統(tǒng)服務(wù)端性能,一般用QPS(QueryPerSecond,每秒請(qǐng)求數(shù))來(lái)衡量,還有一個(gè)影響和QPS也息息相關(guān),那就是響應(yīng)時(shí)間(ResponseTime,RT),它可以理解為服務(wù)器處理響應(yīng)的耗時(shí)。正常情況下響應(yīng)時(shí)間(RT)越短,一秒鐘處理的請(qǐng)求數(shù)(QPS)自然也就會(huì)越多,這在單線(xiàn)程處理的情況下看起來(lái)是線(xiàn)性的關(guān)系,即我們只要把每個(gè)請(qǐng)求的響應(yīng)時(shí)間降到最低,那么性能就會(huì)最高。但是你可能想到響應(yīng)時(shí)間總有一個(gè)極限,不可能無(wú)限下降,所以又出現(xiàn)了另外一個(gè)維度,即通過(guò)多線(xiàn)程,來(lái)處理請(qǐng)求。這樣理論上就變成了“總QPS=(1000ms/響應(yīng)時(shí)間)×線(xiàn)程數(shù)量”,這樣性能就和兩個(gè)因素相關(guān)了,一個(gè)是一次響應(yīng)的服務(wù)端耗時(shí),一個(gè)是處理請(qǐng)求的線(xiàn)程數(shù)。接下來(lái),我們一起看看這個(gè)兩個(gè)因素到底會(huì)造成什么樣的影響。首先,我們先來(lái)看看響應(yīng)時(shí)間和QPS有啥關(guān)系。對(duì)于大部分的Web系統(tǒng)而言,響應(yīng)時(shí)間一般都是由CPU執(zhí)行時(shí)間和線(xiàn)程等待時(shí)間(比如RPC、IO等待、Sleep、Wait等)組成,即服務(wù)器在處理一個(gè)請(qǐng)求時(shí),一部分是CPU本身在做運(yùn)算,還有一部分是在各種等待。理解了服務(wù)器處理請(qǐng)求的邏輯,估計(jì)你會(huì)說(shuō)為什么我們不去減少這種等待時(shí)間。很遺憾,根據(jù)我們實(shí)際的測(cè)試發(fā)現(xiàn),減少線(xiàn)程等待時(shí)間對(duì)提升性能的影響沒(méi)有我們想象得那么大,它并不是線(xiàn)性的提升關(guān)系,這點(diǎn)在很多代理服務(wù)器(Proxy)上可以做驗(yàn)證。如果代理服務(wù)器本身沒(méi)有CPU消耗,我們?cè)诿看谓o代理服務(wù)器代理的請(qǐng)求加個(gè)延時(shí),即增加響應(yīng)時(shí)間,但是這對(duì)代理服務(wù)器本身的吞吐量并沒(méi)有多大的影響,因?yàn)榇矸?wù)器本身的資源并沒(méi)有被消耗,可以通過(guò)增加代理服務(wù)器的處理線(xiàn)程數(shù),來(lái)彌補(bǔ)響應(yīng)時(shí)間對(duì)代理服務(wù)器的QPS的影響。其實(shí),真正對(duì)性能有影響的是CPU的執(zhí)行時(shí)間。這也很好理解,因?yàn)镃PU的執(zhí)行真正消耗了服務(wù)器的資源。經(jīng)過(guò)實(shí)際的測(cè)試,如果減少CPU一半的執(zhí)行時(shí)間,就可以增加一倍的QPS。也就是說(shuō),我們應(yīng)該致力于減少CPU的執(zhí)行時(shí)間。其次,我們?cè)賮?lái)看看線(xiàn)程數(shù)對(duì)QPS的影響。單看“總QPS”的計(jì)算公式,你會(huì)覺(jué)得線(xiàn)程數(shù)越多QPS也就會(huì)越高,但這會(huì)一直正確嗎?顯然不是,線(xiàn)程數(shù)不是越多越好,因?yàn)榫€(xiàn)程本身也消耗資源,也受到其他因素的制約。例如,線(xiàn)程越多系統(tǒng)的線(xiàn)程切換成本就會(huì)越高,而且每個(gè)線(xiàn)程也都會(huì)耗費(fèi)一定內(nèi)存。那么,設(shè)置什么樣的線(xiàn)程數(shù)最合理呢?其實(shí)很多多線(xiàn)程的場(chǎng)景都有一個(gè)默認(rèn)配置,即“線(xiàn)程數(shù)=2*CPU核數(shù)+1”。除去這個(gè)配置,還有一個(gè)根據(jù)最佳實(shí)踐得出來(lái)的公式:線(xiàn)程數(shù)=[(線(xiàn)程等待時(shí)間+線(xiàn)程CPU時(shí)間)/線(xiàn)程CPU時(shí)間]×CPU數(shù)量當(dāng)然,最好的辦法是通過(guò)性能測(cè)試來(lái)發(fā)現(xiàn)最佳的線(xiàn)程數(shù)。換句話(huà)說(shuō),要提升性能我們就要減少CPU的執(zhí)行時(shí)間,另外就是要設(shè)置一個(gè)合理的并發(fā)線(xiàn)程數(shù),通過(guò)這兩方面來(lái)顯著提升服務(wù)器的性能。現(xiàn)在,你知道了如何來(lái)快速提升性能,那接下來(lái)你估計(jì)會(huì)問(wèn),我應(yīng)該怎么發(fā)現(xiàn)系統(tǒng)哪里最消耗CPU資源呢?如何發(fā)現(xiàn)瓶頸就服務(wù)器而言,會(huì)出現(xiàn)瓶頸的地方有很多,例如CPU、內(nèi)存、磁盤(pán)以及網(wǎng)絡(luò)等都可能會(huì)導(dǎo)致瓶頸。此外,不同的系統(tǒng)對(duì)瓶頸的關(guān)注度也不一樣,例如對(duì)緩存系統(tǒng)而言,制約它的是內(nèi)存,而對(duì)存儲(chǔ)型系統(tǒng)來(lái)說(shuō)I/O更容易是瓶頸。這個(gè)專(zhuān)欄中,我們定位的場(chǎng)景是秒殺,它的瓶頸更多地發(fā)生在CPU上。CPUCPUCPUJProfiler和Yourkit這兩個(gè)工具,它們可以列出整個(gè)請(qǐng)求中每個(gè)函數(shù)的CPUCPU當(dāng)然還有一些辦法也可以近似地統(tǒng)計(jì)CPU的耗時(shí),例如通過(guò)jstack定時(shí)地打印調(diào)用棧,如果某些函數(shù)調(diào)用頻繁或者耗時(shí)較多,那么那些函數(shù)就會(huì)多次出現(xiàn)在系統(tǒng)調(diào)用棧里,這樣相當(dāng)于采樣的方式也能夠發(fā)現(xiàn)耗時(shí)較多的函數(shù)。雖說(shuō)秒殺系統(tǒng)的瓶頸大部分在CPU,但這并不表示其他方面就一定不出現(xiàn)瓶頸。例如,如果海量請(qǐng)求涌過(guò)來(lái),你的頁(yè)面又比較大,那么網(wǎng)絡(luò)就有可能出現(xiàn)瓶頸。怎樣簡(jiǎn)單地判斷CPU是不是瓶頸呢?一個(gè)辦法就是看當(dāng)QPS達(dá)到極限時(shí),你的服務(wù)器的CPU使用率是不是超過(guò)了95%,如果沒(méi)有超過(guò),那么表示CPU還有提升的空間,要么是有鎖限制,要么是有過(guò)多的本地I/O等待發(fā)生?,F(xiàn)在你知道了優(yōu)化哪些因素,又發(fā)現(xiàn)了瓶頸,那么接下來(lái)就要關(guān)注如何優(yōu)化了。如何優(yōu)化系統(tǒng)對(duì)Java系統(tǒng)來(lái)說(shuō),可以?xún)?yōu)化的地方很多,這里我重點(diǎn)說(shuō)一下比較有效的幾種手段,供你參考,它們是:減少編碼、減少序列化、Java極致優(yōu)化、并發(fā)讀優(yōu)化。接下來(lái),我們分別來(lái)看一下。減少編碼Java的編碼運(yùn)行比較慢,這是Java的一大硬傷。在很多場(chǎng)景下,只要涉及字符串的操作(如輸入輸出操作、I/O操作)都比較耗CPU資源,不管它是磁盤(pán)I/O還是網(wǎng)絡(luò)I/O,因?yàn)槎夹枰獙⒆址D(zhuǎn)換成字節(jié),而這個(gè)轉(zhuǎn)換必須編碼。每個(gè)字符的編碼都需要查表,而這種查表的操作非常耗資源,所以減少字符到字節(jié)或者相反的轉(zhuǎn)換、減少字符編碼會(huì)非常有成效。減少編碼就可以大大提升性能。那么如何才能減少編碼呢?例如,網(wǎng)頁(yè)輸出是可以直接進(jìn)行流輸出的,即用resp.getOutputStream()函數(shù)寫(xiě)數(shù)據(jù),把一些靜態(tài)的數(shù)據(jù)提前轉(zhuǎn)化成字節(jié),等到真正往外寫(xiě)的時(shí)候再直接用OutputStream()函數(shù)寫(xiě),就可以減少靜態(tài)數(shù)據(jù)的編碼轉(zhuǎn)換。我在《深入分析JavaWeb技術(shù)內(nèi)幕》一書(shū)中介紹的“Velocity優(yōu)化實(shí)踐”一章的內(nèi)容,就是基于把靜態(tài)的字符串提前編碼成字節(jié)并緩存,然后直接輸出字節(jié)內(nèi)容到頁(yè)面,從而大大減少編碼的性能消耗的,網(wǎng)頁(yè)輸出的性能比沒(méi)有提前進(jìn)行字符到字節(jié)轉(zhuǎn)換時(shí)提升了30%左右。減少序列化序列化也是Java性能的一大天敵,減少Java中的序列化操作也能大大提升性能。又因?yàn)樾蛄谢呛途幋a同時(shí)發(fā)生的,所以減少序列化也就減少了編碼。序列化大部分是在RPC中發(fā)生的,因此避免或者減少RPC就可以減少序列化,當(dāng)然當(dāng)前的序列化協(xié)議也已經(jīng)做了很多優(yōu)化來(lái)提升性能。有一種新的方案,就是可以將多個(gè)關(guān)聯(lián)性比較強(qiáng)的應(yīng)用進(jìn)行“合并部署”,而減少不同應(yīng)用之間的RPC也可以減少序列化的消耗。所謂“合并部署”,就是把兩個(gè)原本在不同機(jī)器上的不同應(yīng)用合并部署到一臺(tái)機(jī)器上,當(dāng)然不僅僅是部署在一臺(tái)機(jī)器上,還要在同一個(gè)Tomcat容器中,且不能走本機(jī)的Socket,這樣才能避免序列化的產(chǎn)生。另外針對(duì)秒殺場(chǎng)景,我們還可以做得更極致一些,接下來(lái)我們來(lái)看第3點(diǎn):Java極致優(yōu)化。JavaJava和通用的Web服務(wù)器(如Nginx或Apache服務(wù)器)相比,在處理大并發(fā)的HTTP請(qǐng)求時(shí)要弱一點(diǎn),所以一般我們都會(huì)對(duì)大流量的Web系統(tǒng)做靜態(tài)化改造,讓大部分請(qǐng)求和數(shù)據(jù)直接在Nginx服務(wù)器或者Web代理服務(wù)器(如Varnish、Squid等)上直接返回(這樣可以減少數(shù)據(jù)的序列化與反序列化),而Java層只需處理少量數(shù)據(jù)的動(dòng)態(tài)請(qǐng)求。針對(duì)這些請(qǐng)求,我們可以使用以下手段進(jìn)行優(yōu)化:直接使用Servlet處理請(qǐng)求。避免使用傳統(tǒng)的MVC框架,這樣可以繞過(guò)一大堆復(fù)雜且用處不大的處理邏輯,節(jié)省1ms時(shí)間(具體取決于你對(duì)MVC框架的依賴(lài)程度)。直接輸出流數(shù)據(jù)。使用resp.getOutputStream()而不是resp.getWriter()函數(shù),可以省掉一些不變字符數(shù)據(jù)的編碼,從而提升性能;數(shù)據(jù)輸出時(shí)推薦使用JSON而不是模板引擎(一般都是解釋執(zhí)行)來(lái)輸出頁(yè)面。并發(fā)讀優(yōu)化也許有讀者會(huì)覺(jué)得這個(gè)問(wèn)題很容易解決,無(wú)非就是放到Tair緩存里面。集中式緩存為了保證命中率一般都會(huì)采用一致性Hash,所以同一個(gè)key會(huì)落到同一臺(tái)機(jī)器上。雖然單臺(tái)緩存機(jī)器也能支撐30w/s的請(qǐng)求,但還是遠(yuǎn)不足以應(yīng)對(duì)像“大秒”這種級(jí)別的熱點(diǎn)商品。那么,該如何徹底解決單點(diǎn)的瓶頸呢?答案是采用應(yīng)用層的LocalCache,即在秒殺系統(tǒng)的單機(jī)上緩存商品相關(guān)的數(shù)據(jù)。那么,又如何緩存(Cache)數(shù)據(jù)呢?你需要?jiǎng)澐殖蓜?dòng)態(tài)數(shù)據(jù)和靜態(tài)數(shù)據(jù)分別進(jìn)行處理:像商品中的“標(biāo)題”和“描述”這些本身不變的數(shù)據(jù),會(huì)在秒殺開(kāi)始之前全量推送到秒殺機(jī)器上,并一直緩存到秒殺結(jié)束;像庫(kù)存這類(lèi)動(dòng)態(tài)數(shù)據(jù),會(huì)采用“被動(dòng)失效”的方式緩存一定時(shí)間(一般是數(shù)秒),失效后再去緩存拉取最新的數(shù)據(jù)。你可能還會(huì)有疑問(wèn):像庫(kù)存這種頻繁更新的數(shù)據(jù),一旦數(shù)據(jù)不一致,會(huì)不會(huì)導(dǎo)致超賣(mài)?讀取問(wèn)題??偨Y(jié)一下減少數(shù)據(jù)、數(shù)據(jù)分級(jí)(動(dòng)靜分離),以及減少中間環(huán)節(jié)、增加預(yù)處理等這些環(huán)節(jié)上做優(yōu)化。首先是“發(fā)現(xiàn)短板”,比如考慮以下因素的一些限制:光速(光速:C=30萬(wàn)千米/秒;光纖:V=C/1.5=20萬(wàn)千米/秒,即數(shù)據(jù)傳輸是有物理距離的限制的)、網(wǎng)速(2017年11月知名測(cè)速網(wǎng)站Ookla發(fā)布報(bào)告,全國(guó)平均上網(wǎng)帶寬達(dá)到61.24Mbps,千兆帶寬下10KB數(shù)據(jù)的極限QPS為1.25萬(wàn)QPS=1000Mbps/8/10KB)、網(wǎng)絡(luò)結(jié)構(gòu)(交換機(jī)/網(wǎng)卡的限制)、TCP/IP、虛擬機(jī)(內(nèi)存/CPU/IO等資源的限制)和應(yīng)用本身的一些瓶頸等。其次是減少數(shù)據(jù)。事實(shí)上,有兩個(gè)地方特別影響性能,一是服務(wù)端在處理數(shù)據(jù)時(shí)不可避免地存在字符到字節(jié)的相互轉(zhuǎn)化,二是HTTP請(qǐng)求時(shí)要做Gzip壓縮,還有網(wǎng)絡(luò)傳輸?shù)暮臅r(shí),這些都和數(shù)據(jù)大小密切相關(guān)。再次,就是數(shù)據(jù)分級(jí),也就是要保證首屏為先、重要信息為先,次要信息則異步加載,以這種方式提升用戶(hù)獲取數(shù)據(jù)的體驗(yàn)。最后就是要減少中間環(huán)節(jié),減少字符到字節(jié)的轉(zhuǎn)換,增加預(yù)處理(提前做字符到字節(jié)的轉(zhuǎn)換)去掉不需要的操作。此外,要做好優(yōu)化,你還需要做好應(yīng)用基線(xiàn),比如性能基線(xiàn)(何時(shí)性能突然下降)、成本基線(xiàn)(11)、鏈路基線(xiàn)(我們的系統(tǒng)發(fā)生了哪些變化),你可以調(diào)用,在架構(gòu)和調(diào)用鏈路上不斷的改進(jìn)。好的思路或者方案,我們一起溝通探討。|秒殺系統(tǒng)“減庫(kù)存”設(shè)計(jì)的核心邏輯如果要設(shè)計(jì)一套秒殺系統(tǒng),那我想你的老板肯定會(huì)先對(duì)你說(shuō):千萬(wàn)不要超賣(mài),這是大前提。如果你第一次接觸秒殺,那你可能還不太理解,庫(kù)存100件就賣(mài)100件,在數(shù)據(jù)庫(kù)里減到0就好了啊,這有什么麻煩的?是的,理論上是這樣,但是具體到業(yè)務(wù)場(chǎng)景中,“減庫(kù)存”就不是這么簡(jiǎn)單了。例如,我們平常購(gòu)物都是這樣,看到喜歡的商品然后下單,但并不是每個(gè)下單請(qǐng)求你都最后付款了。你說(shuō)系統(tǒng)是用戶(hù)下單了就算這個(gè)商品賣(mài)出去了,還是等到用戶(hù)真正付款了才算賣(mài)出了呢?這的確是個(gè)問(wèn)題!我們可以先根據(jù)減庫(kù)存是發(fā)生在下單階段還是付款階段,把減庫(kù)存做一下劃分。減庫(kù)存有哪幾種方式在正常的電商平臺(tái)購(gòu)物場(chǎng)景中,用戶(hù)的實(shí)際購(gòu)買(mǎi)過(guò)程一般分為兩步:下單和付款。你想買(mǎi)一臺(tái)iPhone手機(jī),在商品頁(yè)面點(diǎn)了“立即購(gòu)買(mǎi)”按鈕,核對(duì)信息之后點(diǎn)擊“提交訂單”,這一步稱(chēng)為下單操作。下單之后,你只有真正完成付款操作才能算真正購(gòu)買(mǎi),也就是俗話(huà)說(shuō)的“落袋為安”。那如果你是架構(gòu)師,你會(huì)在哪個(gè)環(huán)節(jié)完成減庫(kù)存的操作呢?總結(jié)來(lái)說(shuō),減庫(kù)存操作一般有如下幾個(gè)方式:下單減庫(kù)存,即當(dāng)買(mǎi)家下單后,在商品的總庫(kù)存中減去買(mǎi)家購(gòu)買(mǎi)數(shù)量。下單減庫(kù)存是最簡(jiǎn)單的減庫(kù)存方式,也是控制最精確的一種,下單時(shí)直接通過(guò)數(shù)據(jù)庫(kù)的事務(wù)機(jī)制控制商品庫(kù)存,這樣一定不會(huì)出現(xiàn)超賣(mài)的情況。但是你要知道,有些人下完單可能并不會(huì)付款。付款減庫(kù)存,即買(mǎi)家下單后,并不立即減庫(kù)存,而是等到有用戶(hù)付款后才真正減庫(kù)存,否則庫(kù)存一直保留給其他買(mǎi)家。但因?yàn)楦犊顣r(shí)才減庫(kù)存,如果并發(fā)比較高,有可能出現(xiàn)買(mǎi)家下單后付不了款的情況,因?yàn)榭赡苌唐芬呀?jīng)被其他人買(mǎi)走了。預(yù)扣庫(kù)存,這種方式相對(duì)復(fù)雜一些,買(mǎi)家下單后,庫(kù)存為其保留一定的時(shí)間(如10分鐘),超過(guò)這個(gè)時(shí)間,庫(kù)存將會(huì)自動(dòng)釋放,釋放后其他買(mǎi)家就可以繼續(xù)購(gòu)買(mǎi)。在買(mǎi)家付款前,系統(tǒng)會(huì)校驗(yàn)該訂單的庫(kù)存是否還有保留:如果沒(méi)有保留,則再次嘗試預(yù)扣;如果庫(kù)存不足(也就是預(yù)扣失敗)則不允許繼續(xù)付款;如果預(yù)扣成功,則完成付款并實(shí)際地減去庫(kù)存。以上這幾種減庫(kù)存的方式都會(huì)存在一些問(wèn)題,下面我們一起來(lái)看下。減庫(kù)存可能存在的問(wèn)題由于購(gòu)物過(guò)程中存在兩步或者多步的操作,因此在不同的操作步驟中減庫(kù)存,就會(huì)存在一些可能被惡意買(mǎi)家利用的漏洞,例如發(fā)生惡意下單的情況。式將該賣(mài)家的商品全部下單,讓這款商品的庫(kù)存減為零,那么這款商品就不能正常售賣(mài)處。既然“下單減庫(kù)存”可能導(dǎo)致惡意下單,從而影響賣(mài)家的商品銷(xiāo)售,那么有沒(méi)有辦法解決呢?你可能會(huì)想,采用“付款減庫(kù)存”的方式是不是就可以了?的確可以。但是,“付款減庫(kù)存”又會(huì)導(dǎo)致另外一個(gè)問(wèn)題:庫(kù)存超賣(mài)。假如有100件商品,就可能出現(xiàn)300人下單成功的情況,因?yàn)橄聠螘r(shí)不會(huì)減庫(kù)存,所以也就可能出現(xiàn)下單成功數(shù)遠(yuǎn)遠(yuǎn)超過(guò)真正庫(kù)存數(shù)的情況,這尤其會(huì)發(fā)生在做活動(dòng)的熱門(mén)商品上。這樣一來(lái),就會(huì)導(dǎo)致很多買(mǎi)家下單成功但是付不了款,買(mǎi)家的購(gòu)物體驗(yàn)自然比較差??梢钥吹剑还苁恰跋聠螠p庫(kù)存”還是“付款減庫(kù)存”,都會(huì)導(dǎo)致商品庫(kù)存不能完全和實(shí)際售賣(mài)情況對(duì)應(yīng)起來(lái)的情況,看來(lái)要把商品準(zhǔn)確地賣(mài)出去還真是不容易??!那么,既然“下單減庫(kù)存”和“付款減庫(kù)存”都有缺點(diǎn),我們能否把兩者相結(jié)合,將兩次操作進(jìn)行前后關(guān)聯(lián)起來(lái),下單時(shí)先預(yù)扣,在規(guī)定時(shí)間內(nèi)不付款再釋放庫(kù)存,即采用“預(yù)扣庫(kù)存”這種方式呢?這種方案確實(shí)可以在一定程度上緩解上面的問(wèn)題。但是否就徹底解決了呢?其實(shí)沒(méi)有!針對(duì)惡意下單這種情況,雖然把有效的付款時(shí)間設(shè)置為10分鐘,但是惡意買(mǎi)家完全可以在10分鐘后再次下單,或者采用一次下單很多件的方式把庫(kù)存減完。針對(duì)這種情況,解決辦法還是要結(jié)合安全和反作弊的措施來(lái)制止。例如,給經(jīng)常下單不付款的買(mǎi)家進(jìn)行識(shí)別打標(biāo)(可以在被打標(biāo)的買(mǎi)家下單時(shí)不減庫(kù)存)、給某些類(lèi)目設(shè)置最大購(gòu)買(mǎi)件數(shù)(例如,參加活動(dòng)的商品一人最多只能買(mǎi)3件),以及對(duì)重復(fù)下單不付款的操作進(jìn)行次數(shù)限制等。針對(duì)“庫(kù)存超賣(mài)”這種情況,在10分鐘時(shí)間內(nèi)下單的數(shù)量仍然有可能超過(guò)庫(kù)存數(shù)量,遇到這種情況我們只能區(qū)別對(duì)待:對(duì)普通的商品下單數(shù)量超過(guò)庫(kù)存數(shù)量的情況,可以通過(guò)補(bǔ)貨來(lái)解決;但是有些賣(mài)家完全不允許庫(kù)存為負(fù)數(shù)的情況,那只能在買(mǎi)家付款時(shí)提示庫(kù)存不足。大型秒殺中如何減庫(kù)存?一般都有個(gè)“有效付款時(shí)間”,超過(guò)這個(gè)時(shí)間訂單自動(dòng)釋放,這都是典型的預(yù)扣庫(kù)存方案。而具體到秒殺這個(gè)場(chǎng)景,應(yīng)該采用哪種方案比較好呢?由于參加秒殺的商品,一般都是“搶到就是賺到”,所以成功下單后卻不付款的情況比較少,再加上賣(mài)家對(duì)秒殺商品的庫(kù)存有嚴(yán)格限制,所以秒殺商品采用“下單減庫(kù)存”更加合理。另外,理論上由于“下單減庫(kù)存”比“預(yù)扣庫(kù)存”以及涉及第三方支付的“付款減庫(kù)存”在邏輯上更為簡(jiǎn)單,所以性能上更占優(yōu)勢(shì)。UPDATEitemSETinventory=CASEWHENinventory>=xxxTHENinventory-xxxELSEUPDATEitemSETinventory=CASEWHENinventory>=xxxTHENinventory-xxxELSEinventor秒殺減庫(kù)存的極致優(yōu)化在交易環(huán)節(jié)中,“庫(kù)存”是個(gè)關(guān)鍵數(shù)據(jù),也是個(gè)熱點(diǎn)數(shù)據(jù),因?yàn)榻灰椎母鱾€(gè)環(huán)節(jié)中都可能涉及對(duì)庫(kù)存的查詢(xún)。但是,我在前面介紹分層過(guò)濾時(shí)提到過(guò),秒殺中并不需要對(duì)庫(kù)存有精確的一致性讀,把庫(kù)存數(shù)據(jù)放到緩存(Cache)中,可以大大提升讀性能。解決大并發(fā)讀問(wèn)題,可以采用LocalCache(即在秒殺系統(tǒng)的單機(jī)上緩存商品相關(guān)的數(shù)據(jù))和對(duì)數(shù)據(jù)進(jìn)行分層過(guò)濾的方式,但是像減庫(kù)存這種大并發(fā)寫(xiě)無(wú)論如何還是避免不了,這也是秒殺場(chǎng)景下最為核心的一個(gè)技術(shù)難題。因此,這里我想專(zhuān)門(mén)來(lái)說(shuō)一下秒殺場(chǎng)景下減庫(kù)存的極致優(yōu)化思路,包括如何在緩存中減庫(kù)存以及如何在數(shù)據(jù)庫(kù)中減庫(kù)存。秒殺商品和普通商品的減庫(kù)存還是有些差異的,例如商品數(shù)量比較少,交易時(shí)間段也比較短,因此這里有一個(gè)大膽的假設(shè),即能否把秒殺商品減庫(kù)存直接放到緩存系統(tǒng)中實(shí)現(xiàn),也就是直接在緩存中減庫(kù)存或者在一個(gè)帶有持久化功能的緩存系統(tǒng)(如Redis)中完成呢?如果你的秒殺商品的減庫(kù)存邏輯非常單一,比如沒(méi)有復(fù)雜的SKU庫(kù)存和總庫(kù)存這種聯(lián)動(dòng)關(guān)系的話(huà),我覺(jué)得完全可以。但是如果有比較復(fù)雜的減庫(kù)存邏輯,或者需要使用事務(wù),你還是必須在數(shù)據(jù)庫(kù)中完成減庫(kù)存。由于MySQL存儲(chǔ)數(shù)據(jù)的特點(diǎn),同一數(shù)據(jù)在數(shù)據(jù)庫(kù)里肯定是一行存儲(chǔ)(MySQL),因此會(huì)有大量線(xiàn)程來(lái)競(jìng)爭(zhēng)InnoDB行鎖,而并發(fā)度越高時(shí)等待線(xiàn)程會(huì)越多,TPS(TransactionPerSecond,即每秒處理的消息數(shù))會(huì)下降,響應(yīng)時(shí)間(RT)會(huì)上升,數(shù)據(jù)庫(kù)的吞吐量就會(huì)嚴(yán)重受影響。這就可能引發(fā)一個(gè)問(wèn)題,就是單個(gè)熱點(diǎn)商品會(huì)影響整個(gè)數(shù)據(jù)庫(kù)的性能,導(dǎo)致0.01%的商品影響99.99%的商品的售賣(mài),這是我們不愿意看到的情況。一個(gè)解決思路是遵循前面介紹的原則進(jìn)行隔離,把熱點(diǎn)商品放到單獨(dú)的熱點(diǎn)庫(kù)中。但是這無(wú)疑會(huì)帶來(lái)維護(hù)上的麻煩,比如要做熱點(diǎn)數(shù)據(jù)的動(dòng)態(tài)遷移以及單獨(dú)的數(shù)據(jù)庫(kù)等。而分離熱點(diǎn)商品到單獨(dú)的數(shù)據(jù)庫(kù)還是沒(méi)有解決并發(fā)鎖的問(wèn)題,我們應(yīng)該怎么辦呢?要解決并發(fā)鎖的問(wèn)題,有兩種辦法:應(yīng)用層做排隊(duì)。按照商品維度設(shè)置隊(duì)列順序執(zhí)行,這樣能減少同一臺(tái)機(jī)器對(duì)數(shù)據(jù)庫(kù)同一行記錄進(jìn)行操作的并發(fā)度,同時(shí)也能控制單個(gè)商品占用數(shù)據(jù)庫(kù)連接的數(shù)量,防止熱點(diǎn)商品占用太多的數(shù)據(jù)庫(kù)連接。數(shù)據(jù)庫(kù)層做排隊(duì)。應(yīng)用層只能做到單機(jī)的排隊(duì),但是應(yīng)用機(jī)器數(shù)本身很多,這種排隊(duì)方式控制并發(fā)的能力仍然有限,所以如果能在數(shù)據(jù)庫(kù)層做全局排隊(duì)是最理想的。阿里的數(shù)據(jù)庫(kù)團(tuán)隊(duì)開(kāi)發(fā)了針對(duì)這種MySQL的InnoDB層上的補(bǔ)丁程序(patch),可以在數(shù)據(jù)庫(kù)層上對(duì)單行記錄做到并發(fā)排隊(duì)。你可能有疑問(wèn)了,排隊(duì)和鎖競(jìng)爭(zhēng)不都是要等待嗎,有啥區(qū)別?如果熟悉MySQL的話(huà),你會(huì)知道InnoDB內(nèi)部的死鎖檢測(cè),以及MySQLServer和InnoDB的切換會(huì)比較消耗性能,淘寶的MySQL核心團(tuán)隊(duì)還做了很多其他方面的優(yōu)化,如COMMIT_ON_SUCCESS和ROLLBACK_ON_FAIL的補(bǔ)丁程序,配合在SQL里面加提示(hint),在事務(wù)里不需要等待應(yīng)用層提交(COMMIT),而在數(shù)據(jù)執(zhí)行完最后一條SQL后,直接根據(jù)TARGET_AFFECT_ROW的結(jié)果進(jìn)行提交或回滾,可以減少網(wǎng)絡(luò)等待時(shí)間(平均約0.7ms)。據(jù)我所知,目前阿里MySQL團(tuán)隊(duì)已經(jīng)將包含這些補(bǔ)丁程序的MySQL開(kāi)源。另外,數(shù)據(jù)更新問(wèn)題除了前面介紹的熱點(diǎn)隔離和排隊(duì)處理之外,還有些場(chǎng)景(如對(duì)商品的lastmodifytime字段的)更新會(huì)非常頻繁,在某些場(chǎng)景下這些多條SQL是可以合并的,一定時(shí)間內(nèi)只要執(zhí)行最后一條SQL就行了,以便減少對(duì)數(shù)據(jù)庫(kù)的更新操作??偨Y(jié)一下今天,我圍繞商品減庫(kù)存的場(chǎng)景,介紹了減庫(kù)存的三種實(shí)現(xiàn)方案,以及分別存在的問(wèn)題和可能的緩解辦法。最后,我又聚焦秒殺這個(gè)場(chǎng)景說(shuō)了如何實(shí)現(xiàn)減庫(kù)存,以及在這個(gè)場(chǎng)景下做到極致優(yōu)化的一些思路。當(dāng)然減庫(kù)存還有很多細(xì)節(jié)問(wèn)題,例如預(yù)扣的庫(kù)存超時(shí)后如何進(jìn)行庫(kù)存回補(bǔ),再比如目前都是第三方支付,如何在付款時(shí)保證減庫(kù)存和成功付款時(shí)的狀態(tài)一致性,這些都是很大的挑戰(zhàn)。如果你也有實(shí)現(xiàn)減庫(kù)存的經(jīng)驗(yàn)或者問(wèn)題,歡迎留言與我分享。|準(zhǔn)備PlanB:如何設(shè)計(jì)兜底方案?這是《如何設(shè)計(jì)一個(gè)秒殺系統(tǒng)》專(zhuān)欄的最后一篇文章,前面我們一起看了很多極致的優(yōu)化思路,以及架構(gòu)的優(yōu)化方案。但是很遺憾,現(xiàn)實(shí)中總難免會(huì)發(fā)生一些這樣或者那樣的意外,而這些看似不經(jīng)意的意外,卻可能帶來(lái)非常嚴(yán)重的后果。我想對(duì)于很多秒殺系統(tǒng)而言,在諸如雙十一這樣的大流量的迅猛沖擊下,都曾經(jīng)或多或少發(fā)生過(guò)宕機(jī)的情況。當(dāng)一個(gè)系統(tǒng)面臨持續(xù)的大流量時(shí),它其實(shí)很難單靠自身調(diào)整來(lái)恢復(fù)狀態(tài),你必須等待流量自然下降或者人為地把流量切走才行,這無(wú)疑會(huì)嚴(yán)重影響用戶(hù)的購(gòu)物體驗(yàn)。同時(shí),你也要知道,沒(méi)有人能夠提前預(yù)估所有情況,意外無(wú)法避免。那么,我們是不是就沒(méi)辦法了呢?當(dāng)然不是,我們可以在系統(tǒng)達(dá)到不可用狀態(tài)之前就做好流量限制,防止最壞情況的發(fā)生。用現(xiàn)在流行的話(huà)來(lái)說(shuō),任何一個(gè)系統(tǒng),都需要“反脆弱”。具體到秒殺這一場(chǎng)景下,為了保證系統(tǒng)的高可用,我們必須設(shè)計(jì)一個(gè)PlanB方案來(lái)兜底,這樣在最壞情況發(fā)生時(shí)我們?nèi)匀荒軌驈娜輵?yīng)對(duì)。今天,我們就來(lái)看下兜底方案設(shè)計(jì)的一些具體思路。高可用建設(shè)應(yīng)該從哪里著手說(shuō)到系統(tǒng)的高可用建設(shè),它其實(shí)是一個(gè)系統(tǒng)工程,需要考慮到系統(tǒng)建設(shè)的各個(gè)階段,也就是說(shuō)它其實(shí)貫穿了系統(tǒng)建設(shè)的整個(gè)生命周期,如下圖所示:圖1高可用系統(tǒng)建設(shè)具體來(lái)說(shuō),系統(tǒng)的高可用建設(shè)涉及架構(gòu)階段、編碼階段、測(cè)試階段、發(fā)布階段、運(yùn)行階段,以及故障發(fā)生時(shí)。接下來(lái),我們分別看一下。架構(gòu)階段:架構(gòu)階段主要考慮系統(tǒng)的可擴(kuò)展性和容錯(cuò)性,要避免系統(tǒng)出現(xiàn)單點(diǎn)問(wèn)題。例如多機(jī)房單元化部署,即使某個(gè)城市的某個(gè)機(jī)房出現(xiàn)整體故障,仍然不會(huì)影響整體網(wǎng)站的運(yùn)轉(zhuǎn)。編碼階段:編碼最重要的是保證代碼的健壯性,例如涉及遠(yuǎn)程調(diào)用問(wèn)題時(shí),要設(shè)置合理的超時(shí)退出機(jī)制,防止被其他系統(tǒng)拖垮,也要對(duì)調(diào)用的返回結(jié)果集有預(yù)期,防止返回的結(jié)果超出程序處理范圍,最常見(jiàn)的做法就是對(duì)錯(cuò)誤異常進(jìn)行捕獲,對(duì)無(wú)法預(yù)料的錯(cuò)誤要有默認(rèn)處理結(jié)果。測(cè)試階段:測(cè)試主要是保證測(cè)試用例的覆蓋度,保證最壞情況發(fā)生時(shí),我們也有相應(yīng)的處理流程。發(fā)布階段:發(fā)布時(shí)也有一些地方需要注意,因?yàn)榘l(fā)布時(shí)最容易出現(xiàn)錯(cuò)誤,因此要有緊急的回滾機(jī)制。運(yùn)行階段:運(yùn)行時(shí)是系統(tǒng)的常態(tài),系統(tǒng)大部分時(shí)間都會(huì)處于運(yùn)行態(tài),運(yùn)行態(tài)最重要的是對(duì)系統(tǒng)的監(jiān)控要準(zhǔn)確及時(shí),發(fā)現(xiàn)問(wèn)題能夠準(zhǔn)確報(bào)警并且報(bào)警數(shù)據(jù)要準(zhǔn)確詳細(xì),以便于排查問(wèn)題。故障發(fā)生:故障發(fā)生時(shí)首先最重要的就是及時(shí)止損,例如由于程序問(wèn)題導(dǎo)致商品價(jià)格錯(cuò)時(shí)恢復(fù)服務(wù),并定位原因解決問(wèn)題。為什么系統(tǒng)的高可用建設(shè)要放到整個(gè)生命周期中全面考慮?因?yàn)槲覀冊(cè)诿總€(gè)環(huán)節(jié)中都可能犯錯(cuò),而有些環(huán)節(jié)犯的錯(cuò),你在后面是無(wú)法彌補(bǔ)的。例如在架構(gòu)階段,你沒(méi)有消除單點(diǎn)問(wèn)題,那么系統(tǒng)上線(xiàn)后,遇到突發(fā)流量把單點(diǎn)給掛了,你就只能干瞪眼,有時(shí)候想加機(jī)器都加不進(jìn)去。所以高可用建設(shè)是一個(gè)系統(tǒng)工程,必須在每個(gè)環(huán)節(jié)都做好。那么針對(duì)秒殺系統(tǒng),我們重點(diǎn)介紹在遇到大流量時(shí),應(yīng)該
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 供用苗木合同范本
- 加盟教育協(xié)議合同范本
- 與收款合同范本
- 儀器協(xié)議合同范本
- 化驗(yàn)用品購(gòu)銷(xiāo)合同范本
- 2024年四川旅游學(xué)院引進(jìn)考試真題
- 2024年省廈門(mén)市梧村小學(xué)招聘考試真題
- 第二單元 遵守社會(huì)規(guī)則 大單元教學(xué)設(shè)計(jì)-2023-2024學(xué)年統(tǒng)編版道德與法治八年級(jí)上冊(cè)
- 買(mǎi)賣(mài)物品交易合同范本
- 保溫發(fā)泡板合同范本
- 2024年12月重慶大學(xué)醫(yī)院公開(kāi)招聘醫(yī)生崗位2人(有編制)筆試歷年典型考題(歷年真題考點(diǎn))解題思路附帶答案詳解
- 主題班會(huì):新學(xué)期 新起點(diǎn) 新期待
- 披薩制作流程
- 2024 河北公務(wù)員考試(筆試、省直、A類(lèi)、C類(lèi))4套真題及答案
- 廈門(mén)2025年福建廈門(mén)市公安文職人員服務(wù)中心招聘17人筆試歷年參考題庫(kù)附帶答案詳解
- 2025年高三歷史教學(xué)工作計(jì)劃
- 【化學(xué)】高中化學(xué)手寫(xiě)筆記
- 膽管惡性腫瘤護(hù)理查房課件
- 電烤箱的使用方法ppt
- 中班:語(yǔ)言擠啊擠
- 上海市有線(xiàn)電視(衛(wèi)星)接收設(shè)施安裝許可證申請(qǐng)表
評(píng)論
0/150
提交評(píng)論