找到了性能瓶頸,然后呢?
作者 | 田博文
前言
本文直接從性能優(yōu)化開始談起,并非意味著尋找性能瓶頸無關(guān)緊要,性能優(yōu)化一般都存在于發(fā)現(xiàn)性能瓶頸之后。找到性能瓶頸自然是優(yōu)化的第一步,畢竟所謂有的放矢。我們今天主要討論的是找到了性能問題之后,到底該怎么辦?
為什么要進(jìn)行性能優(yōu)化?
相信大家對(duì)這些句子都不陌生:“通過解決了XX提升了30%吞吐量”、“優(yōu)化了XXX后降低了40%的延遲”等等,可能會(huì)有人覺得這么大的性能提升是不是在開玩笑。其實(shí)不然,性能優(yōu)化的效果在早期是相當(dāng)顯著的,我們先從一個(gè)簡(jiǎn)單的小例子一窺端倪:
小寅是一位Java開發(fā)程序員,他今天的工作是把花果山小賣部的每一筆訂單金額都存到數(shù)據(jù)庫中并統(tǒng)計(jì)合計(jì)值。為了盡快完成工作,他把所有訂單都寫入到一個(gè)數(shù)組中并循環(huán)插入到數(shù)據(jù)庫,他的程序邏輯是這個(gè)樣子的:
很明顯,這個(gè)邏輯下完成任務(wù)總時(shí)長(zhǎng)等于“幾毫秒×數(shù)組數(shù)量”,由于平日里花果山?jīng)]什么人,小寅的程序提交后一直運(yùn)行良好,直到有一天,花果山變成了5A級(jí)景區(qū)……
小寅的代碼在花果山游客大量增加之后延遲過高,已經(jīng)影響到了其他的業(yè)務(wù),而要解決上面的性能問題很簡(jiǎn)單,只需要將循環(huán)內(nèi)的insert語句移到循環(huán)之外即可。雖然看上去僅改了一行代碼,但這一行的改變中就包含了批處理的解決方案,它減少了與數(shù)據(jù)庫的交互,與原代碼之間的時(shí)間成本天差地別,這就是性能優(yōu)化帶來的的好處。
什么是性能優(yōu)化模式?
聊完了性能優(yōu)化的好處,我們接下來就討論一下什么是性能優(yōu)化模式,這個(gè)說法也是最近看到的一篇博客中提到的:
性能優(yōu)化模式是一個(gè)模型對(duì)模型的方式,我們把性能問題想象(抽象)成模型,再把解決它的辦法也抽象成模型,這樣一來就成了惡化模型對(duì)應(yīng)優(yōu)化模型,而這種組合拳就是性能優(yōu)化模式,也可以說是解決方案。當(dāng)然,性能問題千奇百怪,解決方案自然也并非是一成不變,具體問題還得具體分析,我們?cè)挷欢嗾f,直接開始。
請(qǐng)求泛洪與數(shù)據(jù)局部性模式
問題來了!假設(shè)我們自己開發(fā)了一個(gè)外賣平臺(tái)“餓了美”,為了保證客戶用盡可能少的時(shí)間去找商家以提高客戶留存率,我們推出了推薦列表用于推薦用戶可能喜歡的商家或者團(tuán)購,每一個(gè)商家都是由多個(gè)標(biāo)簽或?qū)傩詷?gòu)成的,這些商家根據(jù)不同的標(biāo)簽或?qū)傩苑植荚诓煌奈⒎?wù)甚至不同服務(wù)器上。此時(shí),要展示推薦列表將會(huì)發(fā)生下圖這樣多次請(qǐng)求的情況,即請(qǐng)求泛洪:
我們可以看到,雖然在用戶端只是展現(xiàn)了平臺(tái)最推薦的十幾個(gè)商家或團(tuán)購的信息,但是可能背后存在數(shù)百甚至數(shù)千次的底層服務(wù)調(diào)用。對(duì)于這種存在多次請(qǐng)求問題的分布式系統(tǒng),請(qǐng)求泛洪所導(dǎo)致的性能惡化幾乎是隨流量呈指數(shù)關(guān)系增長(zhǎng)的。那么可以想像,隨著流量高峰的到來,其需要臨時(shí)增加的服務(wù)器數(shù)量也是指數(shù)級(jí)的。當(dāng)然這里有人可能會(huì)說,我這個(gè)服務(wù)是云服務(wù),可以通過一些自動(dòng)縮放策略來滿足峰時(shí)設(shè)備需求,比如AWS的Auto Scaling Groups。但是,這只是治標(biāo)不治本的方案,而且云設(shè)備按需付費(fèi),設(shè)備越多開銷越大,隨著應(yīng)用越做越大,流量越來越多,這方面的成本將會(huì)無限擴(kuò)大。
這里,我們就要隆重介紹一番針對(duì)此問題的解決方案:數(shù)據(jù)局部性模式。
數(shù)據(jù)局部性模式擁有一個(gè)中心,兩個(gè)基本點(diǎn):一個(gè)中心是合理組織數(shù)據(jù)業(yè)務(wù),以減少服務(wù)調(diào)用次數(shù)為中心,兩個(gè)基本點(diǎn)是從服務(wù)端和客戶端兩點(diǎn)進(jìn)行優(yōu)化。在服務(wù)端,數(shù)據(jù)應(yīng)當(dāng)由盡可能少的服務(wù)器來提供,且常在一起被消費(fèi)的數(shù)據(jù)應(yīng)盡可能放在同一服務(wù)器上,如上圖所示。當(dāng)我們返回給客戶的響應(yīng)中每一條數(shù)據(jù)塊都是從數(shù)百個(gè)服務(wù)器上取來拼成的時(shí)候,通過木桶效應(yīng)理論可知,客戶需要等待的時(shí)間取決于最慢的那個(gè)服務(wù)響應(yīng)的時(shí)間。
不僅如此,該模式還解決了依賴的服務(wù)器過多造成的系統(tǒng)可用性下降問題,畢竟只要任意一個(gè)服務(wù)器宕機(jī)都會(huì)導(dǎo)致整個(gè)任務(wù)的失敗。在客戶端,我們可以采用本地緩存,批處理請(qǐng)求,一致性哈希左移等方案。一致性哈希左移指的是將需要通過哈希進(jìn)行負(fù)載均衡的服務(wù)中的哈希放到客戶端去做,減少服務(wù)端的接口調(diào)用。說到緩存和批處理,它們帶來的好處是顯而易見的,不只是在各大應(yīng)用上發(fā)光發(fā)熱,CPU、操作系統(tǒng)等我們?nèi)粘=佑|的很多東西的設(shè)計(jì)理念里也少不了它們。當(dāng)然,事物都有兩面性,這個(gè)模式的最大弊端在于它需要進(jìn)行重構(gòu),重構(gòu)帶來的改變就意味著龐大的工作量以及可能引入的BUG、可靠性問題等。
請(qǐng)求擁塞與水平分割模式
除了可以解決數(shù)據(jù)泛洪的數(shù)據(jù)局部性模式,現(xiàn)在讓我們來想象這樣一個(gè)場(chǎng)景:我們?yōu)槟衬炽y行開發(fā)了一個(gè)節(jié)日福利活動(dòng)業(yè)務(wù),它需要依賴多個(gè)服務(wù)并依次執(zhí)行:認(rèn)證登錄、查詢賬戶過去一個(gè)月平均余額、查詢歷史交易信息、查詢用戶福利等級(jí)、查詢客戶是否為新用戶等,業(yè)務(wù)上線后活動(dòng)極其火爆,參與者眾多,沒過一會(huì)兒我們就收到了這樣的反饋:“不行,業(yè)務(wù)卡死了”、“太慢了”。此時(shí),我們遇到的就是單次請(qǐng)求時(shí)延變長(zhǎng)導(dǎo)致的系統(tǒng)性能惡化甚至崩潰的情況,即請(qǐng)求擁塞:
用戶想?yún)⑴c福利活動(dòng)薅點(diǎn)羊毛,但是眾多參與者的加入?yún)s造成了其中某個(gè)環(huán)節(jié)的響應(yīng)時(shí)間變長(zhǎng),在這個(gè)系統(tǒng)下,該服務(wù)的響應(yīng)時(shí)間上升勢(shì)必造成系統(tǒng)整體的響應(yīng)時(shí)間上升,進(jìn)而出現(xiàn)CPU、內(nèi)存、Swap報(bào)警等一系列問題。自然地,可能很多人就會(huì)想到增加服務(wù)器的解決方案,既然用戶激增且這樣的狀態(tài)并不長(zhǎng)久,那么臨時(shí)增加服務(wù)器分流并進(jìn)行負(fù)載均衡不就行了?這自然也是一個(gè)好的解決方案,但我想要介紹的是不需要額外購置服務(wù)器的另一個(gè)方案:水平分割模式。
水平分割模式就是將整個(gè)請(qǐng)求流程切分為必須要相互依賴的多個(gè)階段,每個(gè)階段包含相互獨(dú)立的多種業(yè)務(wù)處理。完成切分后,該模式按階段內(nèi)部并行,各階段間串行的方式運(yùn)作,此時(shí)的請(qǐng)求總耗時(shí)將下降為各階段間耗時(shí)的總和。用上面銀行活動(dòng)的案例來看,是否使用水平分割模式的效果對(duì)比如下(圖中各模塊延遲均為胡謅的):
并行處理的思路并非萬能,比如在數(shù)據(jù)更新的時(shí)候,并行處理就會(huì)出現(xiàn)數(shù)據(jù)不一致的問題(如下圖),這也是為什么DB只允許同時(shí)只有一個(gè)人對(duì)一條數(shù)據(jù)進(jìn)行更新的原因。另外,還有一種情況,當(dāng)負(fù)載不大時(shí),并行也不會(huì)得到很大的性能提升,舉個(gè)例子:銀行窗口排隊(duì),如果大廳中沒人等待,那么就算再怎么增加窗口數(shù)量,排隊(duì)時(shí)間也不會(huì)有什么優(yōu)化的余地(因?yàn)楸揪蜑?)。
降級(jí)模式
除了針對(duì)性的性能優(yōu)化模式外,不論任何問題都有一種萬金油式的解決方案:降級(jí)模式。降級(jí)模式即發(fā)生性能惡化時(shí)系統(tǒng)進(jìn)行自我閹割以保證其運(yùn)轉(zhuǎn),常用的降級(jí)策略為:流量降級(jí)(主動(dòng)拒絕訪問)、效果降級(jí)(使用低質(zhì)量低延遲服務(wù)保證可用性)和功能性降級(jí)(取消部分服務(wù)保證系統(tǒng)可用性),用一個(gè)情景來說明其區(qū)別:計(jì)劃公司全體員工去巴厘島度假,但預(yù)算不夠。流量降級(jí)策略下,只有一半員工去度假了,其余員工僅放假;效果降級(jí)策略下,全體員工去北戴河玩;功能性降級(jí)策略則是全體員工都去巴厘島,但不提供機(jī)票。
其他模式
除上面說到的模式外,在實(shí)際場(chǎng)景中還有反貪心模式、緩存思維模式、綁定模式等等。其中,反貪心模式指局部最優(yōu)并非導(dǎo)向結(jié)果最優(yōu),比如在頻繁請(qǐng)求的情境下,可能全盤掃描這個(gè)看起來并非最優(yōu)解的方案在整體上會(huì)比循環(huán)+索引的方案更好;緩存思維模式指頻繁訪問的數(shù)據(jù)盡可能放在離用戶側(cè)較近的位置上,LRU算法、邊緣計(jì)算等場(chǎng)景都是應(yīng)用緩存思維模式的解決方案;綁定模式即在優(yōu)先考慮局部性的時(shí)候,將任務(wù)與處理器綁定會(huì)更迅速(減少頻繁上下文切換消耗),此模式典型應(yīng)用就是多核CPU采用的NUMA(Non-Uniform Memory Access)技術(shù)。
小結(jié)
性能問題根據(jù)場(chǎng)景不同而千變?nèi)f化,不同場(chǎng)景下其對(duì)應(yīng)的性能優(yōu)化模式不同,付出的代價(jià)也不同,其歸根結(jié)底還是“看碟下菜”四字。