如果你看過秒殺系統(tǒng)的流量監(jiān)控圖的話岩齿,你會發(fā)現(xiàn)它是一條直線,就在秒殺開始那一秒是一條很直很直的線苞俘,這是因?yàn)槊霘⒄埱笤跁r間上高度集中于某一特定的時間點(diǎn)纯衍。這樣一來,就會導(dǎo)致一個特別高的流量峰值苗胀,它對資源的消耗是瞬時的襟诸。
但是對秒殺這個場景來說,最終能夠搶到商品的人數(shù)是固定的基协,也就是說100人和10000人發(fā)起請求的結(jié)果都是一樣的歌亲,并發(fā)度越高,無效請求也越多澜驮。
但是從業(yè)務(wù)上來說陷揪,秒殺活動是希望更多的人來參與的,也就是開始之前希望有更多的人來刷頁面,但是真正開始下單時悍缠,秒殺請求并不是越多越好卦绣。因此我們可以設(shè)計(jì)一些規(guī)則,讓并發(fā)的請求更多地延緩飞蚓,而且我們甚至可以過濾掉一些無效請求滤港。
為什么要削峰
為什么要削峰呢?或者說峰值會帶來哪些壞處趴拧?
我們知道服務(wù)器的處理資源是恒定的溅漾,你用或者不用它的處理能力都是一樣的,所以出現(xiàn)峰值的話著榴,很容易導(dǎo)致忙到處理不過來添履,閑的時候卻又沒有什么要處理。但是由于要保證服務(wù)質(zhì)量脑又,我們的很多處理資源只能按照忙的時候來預(yù)估暮胧,而這會導(dǎo)致資源的一個浪費(fèi)。
這就好比因?yàn)榇嬖谠绺叻搴屯砀叻宓膯栴}问麸,所以有了錯峰限行的解決方案叔壤。
削峰的存在,一是可以讓服務(wù)端處理變得更加平穩(wěn)口叙,二是可以節(jié)省服務(wù)器的資源成本炼绘。
針對秒殺這一場景,削峰從本質(zhì)上來說就是更多地延緩用戶請求的發(fā)出妄田,以便減少和過濾掉一些無效請求俺亮,它遵從“請求數(shù)要盡量少”的原則。
今天疟呐,我就來介紹一下流量削峰的一些操作思路:排隊(duì)脚曾、答題、分層過濾启具。
這幾種方式都是無損(即不會損失用戶的發(fā)出請求)的實(shí)現(xiàn)方案本讥,當(dāng)然還有些有損的實(shí)現(xiàn)方案,包括我們后面要介紹的關(guān)于穩(wěn)定性的一些辦法鲁冯,比如限流和機(jī)器負(fù)載保護(hù)等一些強(qiáng)制措施也能達(dá)到削峰保護(hù)的目的拷沸,當(dāng)然這都是不得已的一些措施,因此就不歸類到這里了薯演。
排隊(duì)
要對流量進(jìn)行削峰撞芍,最容易想到的解決方案就是用消息隊(duì)列來緩沖瞬時流量,把同步的直接調(diào)用轉(zhuǎn)換成異步的間接推送跨扮,中間通過一個隊(duì)列在一端承接瞬時的流量洪峰序无,在另一端平滑地將消息推送出去验毡。在這里,消息隊(duì)列就像“水庫”一樣帝嗡,攔蓄上游的洪水晶通,削減進(jìn)入下游河道的洪峰流量,從而達(dá)到減免洪水災(zāi)害的目的哟玷。
用消息隊(duì)列來緩沖瞬時流量的方案狮辽,如下圖所示:
l消息隊(duì)列來緩沖瞬時流量
但是,如果流量峰值持續(xù)一段時間達(dá)到了消息隊(duì)列的處理上限碗降,例如本機(jī)的消息積壓達(dá)到了存儲空間的上限隘竭,消息隊(duì)列同樣也會被壓垮塘秦,這樣雖然保護(hù)了下游的系統(tǒng)讼渊,但是和直接把請求丟棄也沒多大的區(qū)別爪幻。就像遇到洪水爆發(fā)時,即使是有水庫恐怕也無濟(jì)于事。
除了消息隊(duì)列,類似的排隊(duì)方式還有很多钉赁,例如:
利用線程池加鎖等待也是一種常用的排隊(duì)方式;
先進(jìn)先出、先進(jìn)后出等常用的內(nèi)存排隊(duì)算法的實(shí)現(xiàn)方式钱慢;
把請求序列化到文件中,然后再順序地讀文件(例如基于MySQL binlog的同步機(jī)制)來恢復(fù)請求等方式策严。
可以看到怀各,這些方式都有一個共同特征寿酌,就是把“一步的操作”變成“兩步的操作”法焰,其中增加的一步操作用來起到緩沖的作用。
說到這里你可能會說颁股,這樣一來增加了訪問請求的路徑啊,并不符合我們介紹的“4要1不要”原則梧疲。沒錯胁澳,的確看起來不太合理宇智,但是如果不增加一個緩沖步驟喂分,那么在一些場景下系統(tǒng)很可能會直接崩潰萝嘁,所以最終還是需要你做出妥協(xié)和平衡。
答題
你是否還記得钝鸽,最早期的秒殺只是純粹地刷新頁面和點(diǎn)擊購買按鈕汇恤,它是后來才增加了答題功能的庞钢。那么,為什么要增加答題功能呢因谎?
這主要是為了增加購買的復(fù)雜度基括,從而達(dá)到兩個目的。
第一個目的是防止部分買家使用秒殺器在參加秒殺時作弊财岔。2011年秒殺非撤缑螅火的時候,秒殺器也比較猖獗匠璧,因而沒有達(dá)到全民參與和營銷的目的桐款,所以系統(tǒng)增加了答題來限制秒殺器。增加答題后夷恍,下單的時間基本控制在2s后魔眨,秒殺器的下單比例也大大下降。答題頁面如下圖所示酿雪。
答題頁面
第二個目的其實(shí)就是延緩請求遏暴,起到對請求流量進(jìn)行削峰的作用,從而讓系統(tǒng)能夠更好地支持瞬時的流量高峰指黎。這個重要的功能就是把峰值的下單請求拉長朋凉,從以前的1s之內(nèi)延長到2s~10s。這樣一來醋安,請求峰值基于時間分片了杂彭。這個時間的分片對服務(wù)端處理并發(fā)非常重要墓毒,會大大減輕壓力。
而且亲怠,由于請求具有先后順序蚁鳖,靠后的請求到來時自然也就沒有庫存了,因此根本到不了最后的下單步驟赁炎,所以真正的并發(fā)寫就非常有限了醉箕。這種設(shè)計(jì)思路目前用得非常普遍,如當(dāng)年支付寶的“咻一咻”徙垫、微信的“搖一搖”都是類似的方式讥裤。
這里,我重點(diǎn)說一下秒殺答題的設(shè)計(jì)思路姻报。
秒殺答題
如上圖所示己英,整個秒殺答題的邏輯主要分為3部分。
題庫生成模塊吴旋,這個部分主要就是生成一個個問題和答案损肛,其實(shí)題目和答案本身并不需要很復(fù)雜,重要的是能夠防止由機(jī)器來算出結(jié)果荣瑟,即防止秒殺器來答題治拿。
題庫的推送模塊,用于在秒殺答題前笆焰,把題目提前推送給詳情系統(tǒng)和交易系統(tǒng)劫谅。題庫的推送主要是為了保證每次用戶請求的題目是唯一的,目的也是防止答題作弊嚷掠。
題目的圖片生成模塊捏检,用于把題目生成為圖片格式,并且在圖片里增加一些干擾因素不皆。這也同樣是為防止機(jī)器直接來答題贯城,它要求只有人才能理解題目本身的含義。這里還要注意一點(diǎn)霹娄,由于答題時網(wǎng)絡(luò)比較擁擠能犯,我們應(yīng)該把題目的圖片提前推送到CDN上并且要進(jìn)行預(yù)熱,不然的話當(dāng)用戶真正請求題目時项棠,圖片可能加載比較慢悲雳,從而影響答題的體驗(yàn)。
其實(shí)真正答題的邏輯比較簡單香追,很好理解:當(dāng)用戶提交的答案和題目對應(yīng)的答案做比較合瓢,如果通過了就繼續(xù)進(jìn)行下一步的下單邏輯,否則就失敗透典。
我們可以把問題和答案用下面這樣的key來進(jìn)行MD5加密:
問題key:userId+itemId+question_Id+time+PK
答案key:userId+itemId+answer+PK
驗(yàn)證的邏輯如下圖所示:
答題的驗(yàn)證邏輯
注意晴楔,這里面的驗(yàn)證邏輯顿苇,除了驗(yàn)證問題的答案以外,還包括用戶本身身份的驗(yàn)證税弃,例如是否已經(jīng)登錄纪岁、用戶的Cookie是否完整、用戶是否重復(fù)頻繁提交等则果。
除了做正確性驗(yàn)證幔翰,我們還可以對提交答案的時間做些限制,例如從開始答題到接受答案要超過1s西壮,因?yàn)樾∮?s是人為操作的可能性很小遗增,這樣也能防止機(jī)器答題的情況。
分層過濾
前面介紹的排隊(duì)和答題要么是少發(fā)請求款青,要么對發(fā)出來的請求進(jìn)行緩沖做修,而針對秒殺場景還有一種方法,就是對請求進(jìn)行分層過濾抡草,從而過濾掉一些無效的請求饰及。分層過濾其實(shí)就是采用“漏斗”式設(shè)計(jì)來處理請求的,如下圖所示康震。
假如請求分別經(jīng)過CDN燎含、前臺讀系統(tǒng)(如商品詳情系統(tǒng))、后臺系統(tǒng)(如交易系統(tǒng))和數(shù)據(jù)庫這幾層签杈,那么:
大部分?jǐn)?shù)據(jù)和流量在用戶瀏覽器或者CDN上獲取瘫镇,這一層可以攔截大部分?jǐn)?shù)據(jù)的讀榷κ蕖答姥;
經(jīng)過第二層(即前臺系統(tǒng))時數(shù)據(jù)(包括強(qiáng)一致性的數(shù)據(jù))盡量得走Cache,過濾一些無效的請求谚咬;
再到第三層后臺系統(tǒng)鹦付,主要做數(shù)據(jù)的二次檢驗(yàn),對系統(tǒng)做好保護(hù)和限流择卦,這樣數(shù)據(jù)量和請求就進(jìn)一步減少敲长;
最后在數(shù)據(jù)層完成數(shù)據(jù)的強(qiáng)一致性校驗(yàn)。
這樣就像漏斗一樣秉继,盡量把數(shù)據(jù)量和請求量一層一層地過濾和減少了祈噪。
分層過濾的核心思想是:在不同的層次盡可能地過濾掉無效請求,讓“漏斗”最末端的才是有效請求尚辑。而要達(dá)到這種效果辑鲤,我們就必須對數(shù)據(jù)做分層的校驗(yàn)。
分層校驗(yàn)的基本原則是:
將動態(tài)請求的讀數(shù)據(jù)緩存(Cache)在Web端杠茬,過濾掉無效的數(shù)據(jù)讀月褥;
對讀數(shù)據(jù)不做強(qiáng)一致性校驗(yàn)弛随,減少因?yàn)橐恢滦孕r?yàn)產(chǎn)生瓶頸的問題;
對寫數(shù)據(jù)進(jìn)行基于時間的合理分片宁赤,過濾掉過期的失效請求舀透;
對寫請求做限流保護(hù),將超出系統(tǒng)承載能力的請求過濾掉决左;
對寫數(shù)據(jù)進(jìn)行強(qiáng)一致性校驗(yàn)愕够,只保留最后有效的數(shù)據(jù)。
分層校驗(yàn)的目的是:
在讀系統(tǒng)中佛猛,盡量減少由于一致性校驗(yàn)帶來的系統(tǒng)瓶頸链烈,但是盡量將不影響性能的檢查條件提前,如用戶是否具有秒殺資格挚躯、商品狀態(tài)是否正常强衡、用戶答題是否正確、秒殺是否已經(jīng)結(jié)束码荔、是否非法請求漩勤、營銷等價物是否充足等;
在寫數(shù)據(jù)系統(tǒng)中缩搅,主要對寫的數(shù)據(jù)(如“庫存”)做一致性檢查越败,最后在數(shù)據(jù)庫層保證數(shù)據(jù)的最終準(zhǔn)確性(如“庫存”不能減為負(fù)數(shù))。
總結(jié)一下
今天硼瓣,我介紹了如何在網(wǎng)站面臨大流量沖擊時進(jìn)行請求的削峰究飞,并主要介紹了削峰的3種處理方式:
一個是通過隊(duì)列來緩沖請求,即控制請求的發(fā)出堂鲤;
一個是通過答題來延長請求發(fā)出的時間亿傅,在請求發(fā)出后承接請求時進(jìn)行控制,最后再對不符合條件的請求進(jìn)行過濾瘟栖;
最后一種是對請求進(jìn)行分層過濾葵擎。
其中,隊(duì)列緩沖方式更加通用半哟,它適用于內(nèi)部上下游系統(tǒng)之間調(diào)用請求不平緩的場景酬滤,由于內(nèi)部系統(tǒng)的服務(wù)質(zhì)量要求不能隨意丟棄請求,所以使用消息隊(duì)列能起到很好的削峰和緩沖作用寓涨。
而答題更適用于秒殺或者營銷活動等應(yīng)用場景盯串,在請求發(fā)起端就控制發(fā)起請求的速度,因?yàn)樵降胶竺鏌o效請求也會越多戒良,所以配合后面介紹的分層攔截的方式体捏,可以更進(jìn)一步減少無效請求對系統(tǒng)資源的消耗。
分層過濾非常適合交易性的寫請求,比如減庫存或者拼車這種場景译打,在讀的時候需要知道還有沒有庫存或者是否還有剩余空座位耗拓。但是由于庫存和座位又是不停變化的,所以讀的數(shù)據(jù)是否一定要非常準(zhǔn)確呢奏司?其實(shí)不一定乔询,你可以放一些請求過去,然后在真正減的時候再做強(qiáng)一致性保證韵洋,這樣既過濾一些請求又解決了強(qiáng)一致性讀的瓶頸竿刁。
不過,在削峰的處理方式上除了采用技術(shù)手段搪缨,其實(shí)還可以采用業(yè)務(wù)手段來達(dá)到一定效果食拜,例如在零點(diǎn)開啟大促的時候由于流量太大導(dǎo)致支付系統(tǒng)阻塞,這個時候可以采用發(fā)放優(yōu)惠券副编、發(fā)起抽獎活動等方式负甸,將一部分流量分散到其他地方,這樣也能起到緩沖流量的作用痹届。
如果想學(xué)習(xí)Java工程化呻待、高性能及分布式、深入淺出队腐。微服務(wù)蚕捉、Spring,MyBatis柴淘,Netty源碼分析的朋友可以加我的Java高級交流:787707172迫淹,群里有阿里大牛直播講解技術(shù),以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家为严。