性能優(yōu)化那些事兒(一)
作者 | 張錦程
性能優(yōu)化是個恒久的話題,它伴隨著業(yè)務(wù)的一次次迭代,產(chǎn)品的一步步演進(jìn),它陪伴企業(yè)一步步走向壯大再走向衰敗,是我們面臨的不可回避的問題。就如同宇宙的遞增定律,一切都走向混亂走向無序,性能的劣化邊隨著企業(yè)的發(fā)展壯大,業(yè)務(wù)的膨脹,人員的流動,復(fù)雜度的提升,一定也最終走向不可收拾的一步。
我們沒法像消除吸血鬼一樣,性能的優(yōu)化沒有銀彈可用,但不代表性能優(yōu)化沒有共性可言,本文針對筆者做過的一些性能優(yōu)化案例,嘗試總結(jié)下解決性能問題的常用手段,以及如何持續(xù)性地避免過快的遞增。
首先我們把性能優(yōu)化分為兩種情況,第一種是在企業(yè)發(fā)展階段的平穩(wěn)期產(chǎn)生的性能瓶頸,第二種是企業(yè)發(fā)展的臨界點(diǎn)產(chǎn)生的性能瓶頸,知道第二曲線原理的同學(xué)們可以嘗試對應(yīng)到第二曲線上去,一種是在曲線內(nèi)的性能優(yōu)化,一種是跨越曲線的性能優(yōu)化。
理論源自查爾斯·漢迪《第二曲線:跨越“S型曲線”的二次增長》
比如著名的C10K問題,和10年淘寶架構(gòu)的演進(jìn)基本都屬于第二種情況,這種情況很難通過業(yè)務(wù)代碼的優(yōu)化或者簡單的架構(gòu)調(diào)整就能解決性能問題,這種情況的性能優(yōu)化一般在算法&理論的突破或者是架構(gòu)哲學(xué)&語言的調(diào)整層面了。我們沒法在一條曲線上完成性能的突破,可以看到曲線后期的收益越來越小,我們必須跳躍到一個新的曲線上去,這就是為什么很多大企業(yè)會注重架構(gòu)的演進(jìn),第一曲線和第二曲線重合的部分就是企業(yè)高層進(jìn)行重要決策的時機(jī),我們再看淘寶的架構(gòu)演進(jìn)很明顯是符合第二曲線原理的。
針對這種如同換血般的性能優(yōu)化,評估的時候需要結(jié)合現(xiàn)有流量和指標(biāo)的分析給出強(qiáng)有理的數(shù)學(xué)模型,來證實(shí)在當(dāng)前架構(gòu)模型上是否能承載未來一段時間的業(yè)務(wù)高速發(fā)展,這種預(yù)判需要有前瞻性,和對市場有準(zhǔn)確的估計。一旦發(fā)現(xiàn)數(shù)學(xué)模型證實(shí)架構(gòu)模型無法承載更多的業(yè)務(wù)增長,那就需要果斷的遷移到第二曲線上去,公司的前瞻性和戰(zhàn)略性在這個階段表現(xiàn)無遺。
我們很多的性能優(yōu)化接觸更多的其實(shí)是第一種情況,我們需要在不打破現(xiàn)有架構(gòu)的情況下,進(jìn)行性能調(diào)優(yōu)。我們繼續(xù)在這個場景下進(jìn)行總結(jié):
環(huán)境優(yōu)化
所謂環(huán)境優(yōu)化就是代碼執(zhí)行環(huán)境的優(yōu)化,就如同你的工作環(huán)境影響你工作效率一樣,程序的運(yùn)行環(huán)境對性能影響也很大。舉個栗子,網(wǎng)卡中斷與CPU親和性,在Linux的網(wǎng)絡(luò)調(diào)優(yōu)方面,如果你發(fā)現(xiàn)網(wǎng)絡(luò)流量上不去,那么有一個方面需要去查一下:網(wǎng)卡處理網(wǎng)絡(luò)請求的中斷是否被綁定到單個CPU(或者說跟處理其它中斷的是同一個CPU)。
這就是個典型的運(yùn)行環(huán)境對性能的影響,你的服務(wù)會應(yīng)為網(wǎng)卡中斷的原因?qū)е滦阅芟陆档暮軈柡Α.?dāng)然環(huán)境的優(yōu)化比較吃經(jīng)驗(yàn),如果沒有經(jīng)驗(yàn)會比較難定位問題,但一些基礎(chǔ)的Linux優(yōu)化常識還是得必備的,需要學(xué)會看各項(xiàng)指標(biāo),有足夠的敏銳力發(fā)現(xiàn)異常的指標(biāo),有足夠的經(jīng)驗(yàn)識別異常指標(biāo)的誘因是什么。
輪子的優(yōu)化和選擇
很多庫提供了非常便利的功能,但有些情況下這些便利的功能對性能不是很友好。準(zhǔn)確來說很多輪子對開發(fā)而言是個黑盒,即使有源碼也鮮有人去一行行研究,往往很多性能問題就暴露在簡單的一句調(diào)用中。先說說簡單的,大家都知道的,HashTable和ConcurrentHashMap,都是并發(fā)安全的組件,但是性能上差別就大了,明顯用ConcurrentHashMap性能就會比HashTable好。
一樣的道理,ArrayBlockingQueue是JDK提供的同步堵塞隊(duì)列,很多場景下會用到這個組件,但是追求極致性能的情況下Disruptor是個更好的選擇。對于大量定時任務(wù)的調(diào)用,Netty的時間輪算法就是更為優(yōu)秀的選擇。
對于輪子的性能選擇可以遵循下面的原則:無鎖設(shè)計普遍優(yōu)于有鎖的設(shè)計,細(xì)粒度鎖優(yōu)于粗粒度鎖,環(huán)形隊(duì)列的設(shè)計普遍優(yōu)于無邊界隊(duì)列的設(shè)計。
萬惡的循環(huán)
一般爛代碼都出現(xiàn)在循環(huán)里,比如幾百次的REST請求,幾千次的SQL請求,手動找起來海底撈針,特別是很深的調(diào)用堆棧很難發(fā)現(xiàn),這里需要利用工具,我們單獨(dú)去說,無論是基于語法樹還是字節(jié)碼的靜態(tài)檢測還是持續(xù)性能集成都能一定程度的預(yù)防這種情況的發(fā)生,經(jīng)驗(yàn)告訴我這里是優(yōu)化成果的大頭,越復(fù)雜的項(xiàng)目這種問題越嚴(yán)重,解決方案是做批處理和小心的使用緩存。這里性能可視化和調(diào)用鏈分析可以幫助你快速定位問題。
鎖
鎖可以說性能優(yōu)化的難點(diǎn),一類鎖會牽扯到業(yè)務(wù),優(yōu)化的重心是如何合理的使用鎖,有沒有行成鎖的使用規(guī)范,鎖的粒度足夠細(xì)么?有可能的集中管理鎖,限制開發(fā)人員直接使用鎖。那另一類鎖一般在中間件那塊,屬于通用組件,和業(yè)務(wù)關(guān)系不大,但如果瓶頸在中間件那就得著手去優(yōu)化了,最好的是實(shí)現(xiàn)無鎖模型。
緩存
緩存是能夠解決一些性能問題的,在某些場合是殺手锏的存在,但緩存需要注意的是時效性和生效范圍,控制好這2點(diǎn)一般緩存會帶來很大的收益。
線程池
線程池過大對性能也是有一定影響的,畢竟JAVA的線程是1:1的內(nèi)核線程,解決方法是設(shè)置合適的線程池大小不要過于龐大,線程上下文切換的開銷可是不小的,或者干脆使用阿里的JDK開啟全局虛擬線程模式(黑科技)。
同步
同步一般會堵塞線程導(dǎo)致需要大量線程池,異步太難寫了,協(xié)程JAVA不支持,有條件的用阿里的JDK吧。
慎用Hibernate
為啥單獨(dú)說Hibernate,可能筆者有條件反射了,一般使用Hibernate的項(xiàng)目多多少少都對其用法有誤解,或者完全沉迷于它帶來的便利性而忽略了這些便利性帶來的性能問題。簡單的N+1問題經(jīng)常在項(xiàng)目上遇到,復(fù)雜的級聯(lián)更新問題導(dǎo)致的性能問題也屢見不鮮,總之大家小心使用Hibernate。
GC
由于頻繁FullGC導(dǎo)致的性能問題也是很常見的,這塊有點(diǎn)大,可以說個幾天幾夜了,這里不細(xì)說。
還有些奇葩的優(yōu)化點(diǎn),比如緩存行失效,一般來說業(yè)務(wù)涉及不到,都是中間件基礎(chǔ)組件才有可能碰到的優(yōu)化策略。
業(yè)務(wù)優(yōu)化,業(yè)務(wù)優(yōu)化往往會取得很喜人的成績,但這是一個取舍的問題,而且涉及到業(yè)務(wù),小心謹(jǐn)慎,一般來說在性能優(yōu)化的專項(xiàng)工作中盡量不去修改業(yè)務(wù)。
上面這些僅僅是一些性能優(yōu)化心得,有不少是經(jīng)驗(yàn)很難總結(jié)全面,但有不少效果顯著的優(yōu)化項(xiàng)可以通過模式去解決,那么如何發(fā)現(xiàn)代碼中的性能問題,快速識別出那些性能不友好的代碼呢?請聽下回分解。
下一篇文章:????《????性能優(yōu)化那些事兒(二)》??