高并發(fā): 流量削峰 與 服務(wù)端優(yōu)化
如果看過(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)求也越多。而從業(yè)務(wù)上來(lái)說(shuō),秒殺活動(dòng)是希望更多的人來(lái)參與的,也就是開(kāi)始之前希望有更多的人來(lái)刷頁(yè)面,但是真正開(kāi)始下單時(shí),秒殺請(qǐng)求并不是越多越好。因此我們可以設(shè)計(jì)一些規(guī)則,讓并發(fā)的請(qǐng)求更多地延緩,而且我們甚至可以過(guò)濾掉一些無(wú)效請(qǐng)求
服務(wù)器的處理資源是恒定的,你用或者不用它的處理能力都是一樣的,所以出現(xiàn)峰值的話(huà),很容易導(dǎo)致忙到處理不過(guò)來(lái),閑的時(shí)候卻又沒(méi)有什么要處理。但是由于要保證服務(wù)質(zhì)量,我們的很多處理資源只能按照忙的時(shí)候來(lái)預(yù)估,而這會(huì)導(dǎo)致資源的一個(gè)浪費(fèi)。這就好比因?yàn)榇嬖谠绺叻搴屯砀叻宓膯?wèn)題,所以有了錯(cuò)峰限行的解決方案
怎么削峰
針對(duì)秒殺這一場(chǎng)景,削峰從本質(zhì)上來(lái)說(shuō)就是更多地延緩用戶(hù)請(qǐng)求的發(fā)出,以便減少和過(guò)濾掉一些無(wú)效請(qǐng)求,它遵從“請(qǐng)求數(shù)要盡量少”的原則
介紹一下流量削峰的一些操作思路:排隊(duì)、答題、分層過(guò)濾。這幾種方式都是無(wú)損(即不會(huì)損失用戶(hù)的發(fā)出請(qǐng)求)的實(shí)現(xiàn)方案,當(dāng)然還有些有損的實(shí)現(xiàn)方案,比如限流和機(jī)器負(fù)載保護(hù)等一些強(qiáng)制措施也能達(dá)到削峰保護(hù)的目的,當(dāng)然這都是不得已的一些措施
1、排隊(duì)
要對(duì)流量進(jìn)行削峰,最容易想到的解決方案就是用消息隊(duì)列來(lái)緩沖瞬時(shí)流量,把同步的直接調(diào)用轉(zhuǎn)換成異步的間接推送,中間通過(guò)一個(gè)隊(duì)列在一端承接瞬時(shí)的流量洪峰,在另一端平滑地將消息推送出去。在這里,消息隊(duì)列就像“水庫(kù)”一樣,攔蓄上游的洪水,削減進(jìn)入下游河道的洪峰流量,從而達(dá)到減免洪水災(zāi)害的目的
但是,如果流量峰值持續(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ì)方式還有很多,例如:
1、利用線(xiàn)程池加鎖等待也是一種常用的排隊(duì)方式
2、先進(jìn)先出、先進(jìn)后出等常用的內(nèi)存排隊(duì)算法的實(shí)現(xiàn)方式
3、把請(qǐng)求序列化到文件中,然后再順序地讀文件(例如基于MySQL binlog的同步機(jī)制)來(lái)恢復(fù)請(qǐng)求等方式
可以看到,這些方式都有一個(gè)共同特征,就是把“一步的操作”變成“兩步的操作”,其中增加的一步操作用來(lái)起到緩沖的作用。那么這樣一來(lái)增加了訪(fǎng)問(wèn)請(qǐng)求的路徑啊,并不符合之前介紹的“4要1不要”原則。沒(méi)錯(cuò),的確看起來(lái)不太合理,但是如果不增加一個(gè)緩沖步驟,那么在一些場(chǎng)景下系統(tǒng)很可能會(huì)直接崩潰,所以最終還是需要做出妥協(xié)和平衡
2、答題
答題主要是為了增加購(gòu)買(mǎi)的復(fù)雜度,從而達(dá)到兩個(gè)目的:
1、防止部分買(mǎi)家使用秒殺器在參加秒殺時(shí)作弊
2、延緩請(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ě)就非常有限了
整個(gè)秒殺答題的邏輯主要分為3部分:
1、題庫(kù)生成模塊,這個(gè)部分主要就是生成一個(gè)個(gè)問(wèn)題和答案,其實(shí)題目和答案本身并不需要很復(fù)雜,重要的是能夠防止由機(jī)器來(lái)算出結(jié)果,即防止秒殺器來(lái)答題
2、題庫(kù)的推送模塊,用于在秒殺答題前,把題目提前推送給詳情系統(tǒng)和交易系統(tǒng)。題庫(kù)的推送主要是為了保證每次用戶(hù)請(qǐng)求的題目是唯一的,目的也是防止答題作弊
3、題目的圖片生成模塊,用于把題目生成為圖片格式,并且在圖片里增加一些干擾因素。這也同樣是為防止機(jī)器直接來(lái)答題,它要求只有人才能理解題目本身的含義。這里還要注意一點(diǎn),由于答題時(shí)網(wǎng)絡(luò)比較擁擠,我們應(yīng)該把題目的圖片提前推送到CDN上并且要進(jìn)行預(yù)熱,不然的話(huà)當(dāng)用戶(hù)真正請(qǐng)求題目時(shí),圖片可能加載比較慢,從而影響答題的體驗(yàn)
當(dāng)用戶(hù)提交的答案和題目對(duì)應(yīng)的答案做比較,如果通過(guò)了就繼續(xù)進(jìn)行下一步的下單邏輯,否則就失敗。我們可以把問(wèn)題和答案用下面這樣的key來(lái)進(jìn)行MD5加密:
問(wèn)題key:userId+itemId+question_Id+time+PK
答案key:userId+itemId+answer+PK

這里面的驗(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ī)器答題的情況
3、分層過(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)求的,如下圖所示:

假如請(qǐng)求分別經(jīng)過(guò)CDN、前臺(tái)讀系統(tǒng)(如商品詳情系統(tǒng))、后臺(tái)系統(tǒng)(如交易系統(tǒng))和數(shù)據(jù)庫(kù)這幾層,那么:
1、大部分?jǐn)?shù)據(jù)和流量在用戶(hù)瀏覽器或者CDN上獲取,這一層可以攔截大部分?jǐn)?shù)據(jù)的讀取
2、經(jīng)過(guò)第二層(即前臺(tái)系統(tǒng))時(shí)數(shù)據(jù)(包括強(qiáng)一致性的數(shù)據(jù))盡量得走Cache,過(guò)濾一些無(wú)效的請(qǐng)求
3、再到第三層后臺(tái)系統(tǒng),主要做數(shù)據(jù)的二次檢驗(yàn),對(duì)系統(tǒng)做好保護(hù)和限流,這樣數(shù)據(jù)量和請(qǐng)求就進(jìn)一步減少
4、最后在數(shù)據(jù)層完成數(shù)據(jù)的強(qiáng)一致性校驗(yàn)
分層過(guò)濾的核心思想是:在不同的層次盡可能地過(guò)濾掉無(wú)效請(qǐng)求,讓“漏斗”最末端的才是有效請(qǐng)求。而要達(dá)到這種效果,我們就必須對(duì)數(shù)據(jù)做分層的校驗(yàn)
分層校驗(yàn)的基本原則是:
1、將動(dòng)態(tài)請(qǐng)求的讀數(shù)據(jù)緩存(Cache)在Web端,過(guò)濾掉無(wú)效的數(shù)據(jù)讀
2、對(duì)讀數(shù)據(jù)不做強(qiáng)一致性校驗(yàn),減少因?yàn)橐恢滦孕r?yàn)產(chǎn)生瓶頸的問(wèn)題
3、對(duì)寫(xiě)數(shù)據(jù)進(jìn)行基于時(shí)間的合理分片,過(guò)濾掉過(guò)期的失效請(qǐng)求
4、對(duì)寫(xiě)請(qǐng)求做限流保護(hù),將超出系統(tǒng)承載能力的請(qǐng)求過(guò)濾掉
5、對(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à)物是否充足等
總結(jié)
通過(guò)隊(duì)列來(lái)緩沖請(qǐng)求,即控制請(qǐng)求的發(fā)出
隊(duì)列緩沖方式更加通用,它適用于內(nèi)部上下游系統(tǒng)之間調(diào)用請(qǐng)求不平緩的場(chǎng)景,由于內(nèi)部系統(tǒng)的服務(wù)質(zhì)量要求不能隨意丟棄請(qǐng)求,所以使用消息隊(duì)列能起到很好的削峰和緩沖作用
通過(guò)答題來(lái)延長(zhǎng)請(qǐng)求發(fā)出的時(shí)間,在請(qǐng)求發(fā)出后承接請(qǐng)求時(shí)進(jìn)行控制,最后再對(duì)不符合條件的請(qǐng)求進(jìn)行過(guò)濾
答題更適用于秒殺或者營(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)資源的消耗
對(duì)請(qǐng)求進(jìn)行分層過(guò)濾
分層過(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á)到一定效果,例如在零點(diǎn)開(kāi)啟大促的時(shí)候由于流量太大導(dǎo)致支付系統(tǒng)阻塞,這個(gè)時(shí)候可以采用發(fā)放優(yōu)惠券、發(fā)起抽獎(jiǎng)活動(dòng)等方式,將一部分流量分散到其他地方,這樣也能起到緩沖流量的作用
服務(wù)端優(yōu)化影響性能的因素
想要提升性能,首先肯定要知道哪些因素對(duì)于系統(tǒng)性能的影響最大,然后再針對(duì)這些具體的因素想辦法做優(yōu)化
系統(tǒng)服務(wù)端性能,一般用QPS(Query Per Second,每秒請(qǐng)求數(shù))來(lái)衡量,還有一個(gè)影響和QPS也息息相關(guān),那就是響應(yīng)時(shí)間(Response Time,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ù)
1、先來(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í)間
2、再來(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ù)
所以要提升性能,就要減少CPU的執(zhí)行時(shí)間,另外就是要設(shè)置一個(gè)合理的并發(fā)線(xiàn)程數(shù),通過(guò)這兩方面來(lái)顯著提升服務(wù)器的性能
如何發(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更容易是瓶頸
對(duì)于秒殺這個(gè)場(chǎng)景,它的瓶頸更多地發(fā)生在CPU上。那么,如何發(fā)現(xiàn)CPU的瓶頸呢?其實(shí)有很多CPU診斷工具可以發(fā)現(xiàn)CPU的消耗,最常用的就是JProfiler和Yourkit這兩個(gè)工具,它們可以列出整個(gè)請(qǐng)求中每個(gè)函數(shù)的CPU執(zhí)行時(shí)間,可以發(fā)現(xiàn)哪個(gè)函數(shù)消耗的CPU時(shí)間最多,以便有針對(duì)性地做優(yōu)化。還有一些辦法也可以近似地統(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ā)生
如何優(yōu)化系統(tǒng)
對(duì)Java系統(tǒng)來(lái)說(shuō),可以?xún)?yōu)化的地方很多,這里重點(diǎn)說(shuō)一下比較有效的幾種手段
1、減少編碼
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ě)到頁(yè)面,就可以減少靜態(tài)數(shù)據(jù)的編碼轉(zhuǎn)換,從而大大減少編碼的性能消耗的,網(wǎng)頁(yè)輸出的性能比沒(méi)有提前進(jìn)行字符到字節(jié)轉(zhuǎn)換時(shí)提升了30%左右
2、減少序列化
序列化也是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)生
3、Java極致優(yōu)化
Java和通用的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è)面
4、并發(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)行處理:
1、像商品中的“標(biāo)題”和“描述”這些本身不變的數(shù)據(jù),會(huì)在秒殺開(kāi)始之前全量推送到秒殺機(jī)器上,并一直緩存到秒殺結(jié)束
2、像庫(kù)存這類(lèi)動(dòng)態(tài)數(shù)據(jù),會(huì)采用“被動(dòng)失效”的方式緩存一定時(shí)間(一般是數(shù)秒),失效后再去緩存拉取最新的數(shù)據(jù)
那么像庫(kù)存這種頻繁更新的數(shù)據(jù),一旦數(shù)據(jù)不一致,會(huì)不會(huì)導(dǎo)致超賣(mài)?
這就要用到前面介紹的讀數(shù)據(jù)的分層校驗(yàn)原則了,讀的場(chǎng)景可以允許一定的臟數(shù)據(jù),因?yàn)檫@里的誤判只會(huì)導(dǎo)致少量原本無(wú)庫(kù)存的下單請(qǐng)求被誤認(rèn)為有庫(kù)存,可以等到真正寫(xiě)數(shù)據(jù)時(shí)再保證最終的一致性,通過(guò)在數(shù)據(jù)的高可用性和一致性之間的平衡,來(lái)解決高并發(fā)的數(shù)據(jù)讀取問(wèn)題
總結(jié)
性能優(yōu)化的過(guò)程首先要從發(fā)現(xiàn)短板開(kāi)始,除了介紹的一些優(yōu)化措施外,還可以在減少數(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.24 Mbps,千兆帶寬下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)(之前秒殺用了多少臺(tái)機(jī)器)、鏈路基線(xiàn)(我們的系統(tǒng)發(fā)生了哪些變化),你可以通過(guò)這些基線(xiàn)持續(xù)關(guān)注系統(tǒng)的性能,做到在代碼上提升編碼質(zhì)量,在業(yè)務(wù)上改掉不合理的調(diào)用,在架構(gòu)和調(diào)用鏈路上不斷的改進(jìn)
source:https://zhouj000.github.io/2018/10/16/SecKill-System-3

喜歡,在看
