性能優化 = 改改代碼?

這裏是Z哥的我的公衆號html

每週五11:45 按時送達前端

固然了,也會時不時加個餐~linux

個人第「124」篇原創敬上程序員

你們好,我是Z哥。算法


很久沒寫技術文章了,最近正好有進行一些思考,順手寫出來分享給你們。數據庫

上了必定規模的系統,特別是To C的系統,性能優化或多或少都會被逼着去作一下。不然,系統便沒法支撐業務的發展,技術成了拖後腿,不是引領業務了。緩存


一旦線上出現了性能問題,就會很棘手。由於它和業務功能上的Bug不一樣,後者的分析和解決思路更清晰,只要日誌記錄到位,沿着一條已知的業務邏輯線,很容易就能找到問題根源。性能優化


而性能問題就會複雜的多,致使的因素有不少,甚至會是多種因素共同做用下的結果。好比,代碼質量低下、業務發展太快、架構設計不合理等等。服務器


並且通常狀況下,性能問題處理起來比較耗時,涉及到的分析鏈路可能會很長,特別是本身小組以外的上下游系統,不少人不肯意幹,或者說有心無力。最多采用一些臨時性的補救手段,碰碰運氣。好比,擴容增長機器、重啓大招、……。微信



有些臨時性的補救措施,有時候不但不能解決問題,還會埋下新的隱患。


好比,從表象上看到某個程序由於給的資源不足致使產生性能問題。臨時增長更多資源給它,可能從表面上看,問題是解決了。可是實則多是由於程序內部對資源的使用上存在不合理的地方,增長資源只是延緩問題發做的時間,並且還可能會侵佔其它程序的運行資源。


爲了不陷入如此的窘境,咱們應當儘可能提早進行性能優化,未雨綢繆。甚至最好是將它做爲一個週期性的工做來進行。


接下去就來分享一下我對作性能優化的思路。



/01 明確優化目的/

不少人優化優化着慢慢變成了爲了優化而優化,目的丟了,或者甚至一開始就沒考慮過。如此會陷入到無心義的性能黑洞中,沒法自拔,只是不斷追求更好看的性能指標。


優化的目的能夠是加強用戶體驗,好比消除一些有明顯卡頓的頁面和操做。還能夠是節省服務器帶寬流量、減小服務器壓力這些。不管如何,你須要有一個目的。



/02 定標準,作到什麼程度/

優化這事是永無止境的,爲了不陷入到前面說的無心義的性能黑洞中,咱們最好可以根據實際的業務狀況定義出一個相對客觀的標準,表明優化到什麼程度。


我本身慣用的標準是確保比預期高50%,若是條件容許則爭取到100%。


好比,根據當下的性能指標與業務量對比,發現最大併發數可能會超過當前的2倍,那麼此時優化到爭取優化提高3倍,至少保證能提高2.5倍,是一個比較合理的標準。


以前專門寫過一篇關於容量預估的文章《作「容量預估」可沒有true和false》,能夠在文末跳轉過去看下,這裏就不展開了。



/03 找到瓶頸點/

不少人作優化的時候,逮着代碼就開始改。的確,只要有必定的知識積累,很容易就能從代碼中發現,寫法A不如寫法B這樣的代碼。


但其實大部分狀況下,「流程上的優化遠勝於語法級別的優化」。好比將每個字符串拼接改爲用StringBuilder來實現,大多數狀況下帶來的成果其實很小,甚至在某些狀況下還不如不改。


因此,咱們最好仍是可以藉助一些客觀數據,以得到更多的運行環境相關的信息,來找到整個「木桶」上最短的一塊「板」。如整個系統的整體架構、服務器的信息等,便於定位到底性能的瓶頸點在哪。


「流程上的優化遠勝於語法級別的優化」中的「流程」除了業務流程以外,還包括技術層面的流程,好比數據在網絡中的流轉過程。



/04 着手優化/

最後纔是着手優化。


作優化的時候須要避免兩個常見的誤區。


第一,不要過分追求應用的單機性能,若是單機表現良好,還應該從總體的角度去思考。


第二,要過分追求單一維度上的極致優化,好比過分追求 CPU 的性能而忽略了內存方面的瓶頸。



正確的思路通常符合下面兩個方向。


第一,空間換性能。一個節點頂不住就多複製一個節點出來,獨一份的數據致使資源競爭得厲害,就多複製一份數據出來。


第二,距離換性能。數據從服務端通過層層處理返回到客戶端以爲慢的話,那麼能不能直接保存在客戶端,或者至少是離客戶端儘量近的地方。



好了,思路清楚了,具體在作的時候我建議你根據下面小標題的順序進行。無論是主動地性能優化,仍是被動地排查性能問題都同樣。



/01 應用程序層面/

無論你願不肯意認可,現實中的大部分性能問題皆是應用程序自身部分的代碼致使的。


咱們老是不太願意認可本身的錯誤,我見過太多程序員老是習慣性的將問題先歸結於硬件問題,網絡問題等等,而後最終排查下來的根源每每仍是在coding的應用程序上。


因此,咱們更應該先從應用程序自己入手進行分析。並且,應用程序所處的位置更「上游」,可操做性更強,讓咱們能夠有更多的手段進行優化



01  緩存

首先,最多見的即是「緩存」,這是用空間換性能的經典。


數據必然是存儲在非易失性的數據庫中的,可是一些會被高頻訪問的數據,將它從數據庫中複製一份,存儲在易失性的內存上作緩存,能夠大大提升被訪問的性能。這個道理你們都懂,就很少說了。


可是值得提醒的一點是,緩存數據的數據結構設計很重要,沒有一種數據結構是萬能的。須要更多的權衡,由於數據結構設計的越簡單、單一,緩存數據的二次運算就越多;反之,全部都存儲「結果數據」的話,須要冗餘的數據量又過大(緩存數據更新還麻煩)。


還得提醒一點,若是緩存的數據量不小,還得考慮增長一個緩存淘汰算法,不然緩存命中率不堪入目,白白浪費大量內存資源。


以前的《分佈式系統系列》中有幾篇緩存相關的聊了不少細節,能夠在文末跳過去查閱。



02  異步

舉個現實生活中的例子,若是你在手機上點了一杯奶茶,去店裏拿的時候發現前面還有20個號,你會在這乾等半小時麼?


我想大部分人都不會吧,寧願去別的地方溜溜。異步就是經過避免「乾等着」來提高性能的手段。


作異步主要是如下兩種方式,


  • 經過線程進行異步。這主要用於涉及到I/O的地方,像磁盤I/O和網絡I/O。一旦產生I/O其實就意味着背後的操做是由另一個程序在進行,此時CPU就不用空着了,讓它忙別的去吧。
複製代碼
  • 經過中間件異步,好比MQ。這用於更大的場景裏,好比在某些流程中、上下游系統的銜接中,若是有些結果並不須要實時收到,那麼經過MQ進行異步就能夠大大提升性能。畢竟MQ的性能更接近NoSQL,性能天然比關係型數據庫高的多。更況且,還將一些業務邏輯的預算給滯後了,當下的性能會更好。

03  多線程&分佈式

這兩點都是「分治」思想的體現。一個快遞員送1000個包裹比較慢,那麼讓10個快遞員同時各送100個天然就快了。


可是切勿分的太狠,畢竟,多起一個線程至關於多一個放養的娃,放出去太多的話,管理成本很高,可能反而會更慢。這就是線程切換的成本,分佈式系統中也存在相似的管理成本。


不過,一個小建議送給你。不到無可奈何,能經過「單機多線程」應付的,就不要引入分佈式了。由於,網絡這個東西實在太不靠譜了,你得爲它作大量的額外工做。



04  延後運算

這個和緩存的思路相反,將一些運算儘量的延後到用的時候。適用的場景也和緩存相反,適用於一些低頻的、運算耗時的數據上。


延遲加載、插件化等等就是該思想的體現。



05  批量,合併

若是你須要在短期內頻繁的傳遞多個數據給同一個目的地,那麼儘可能考慮將他們打包到一塊兒,一次性傳輸,特別是涉及到I/O的場景。


若是手頭的系統仍是一個單點系統,這招的性價比就很是高。在避開分佈式系統的複雜性的前提下,得到性能提高。


數據庫的bulk操做,前端的sprite圖,都是該思想的體現。



應用程序層面的其它優化方式還有不少。好比,用長連接代替頻繁打開關閉的短連接、壓縮、重用等等。這些相對比較簡單和好理解,就很少說了。


應用程序層面的事情作到位了以後,咱們再來考慮組件層面的優化。



/02 組件層面/

組件是指那些非業務性的東西,好比一些中間件、數據庫、運行時的環境(JVM、WebServer)等。


數據庫的調優,總的來講分爲如下三部分:


  • SQL語句。
  • 索引。
  • 鏈接池。

其它的一些,好比JVM的調優最主要的就是對「GC」相關的配置調優。WebServer的調優主要是針對「鏈接」相關的調優。這些細節就不贅述了,資料多到看不過來。



/03 系統層面/

系統層面的一些調優工做,涉及到運維工程師的一些工做,我不是很擅長就不誤人子弟了。可是咱們能夠藉助系統層面的一些技術指標來觀測並判斷咱們的程序是否正常。好比,CPU、線程、網絡、磁盤、內存。



01  CPU

判斷CPU是否正常,大多數狀況下關注這三個指標就夠了,CPU利用率、CPU平均負載、CPU上下文切換。CPU利用率你們基本上都知道,就很少說了,那就說說後面兩個。


關注CPU平均負載的時候,特別須要注意趨勢的變化。若是 1 分鐘/5 分鐘/15 分鐘的三個值相差不大,那說明系統負載很平穩,則不用關注,若是這三個值逐漸下降,說明負載在漸漸升高,須要排查具體的緣由。


CPU上下文切換。上下文切換的次數越多,就意味着更多的CPU時間消耗在寄存器、內核棧以及虛擬內存等數據的保存和恢復上,真正進行你所指望的運算工做的時間就越少,系統的總體性能天然就會降低。致使這個狀況的緣由主要有兩點,


  1. 程序內的磁盤I/O、網絡I/O比較多。
複製代碼
  1. 程序內啓動的線程過多。

02  線程

線程方面除了關注線程數以外,還須要關注一下處於「掛起」狀態的線程數量有多少。


掛起狀態的線程數過多,意味着程序裏鎖競爭激烈,須要考慮經過其它的方案來縮小鎖的粒度、級別,甚至是避免用鎖。



03  網絡

一般在硬件層面內網帶寬會遠大於外網的帶寬,因此,外網帶寬被吃滿的狀況更加常見,特別是多圖、多流媒體類型的可對外訪問系統。關於流量大小相關的問題通常你們都能想到,就很少說了。


可是,Z哥提醒你要特別關注端口的使用和每一個端口上的鏈接狀態狀況。比較常見的問題是,鏈接用完有沒有及時釋放,致使端口被佔滿,後續新的網絡請求沒法創建鏈接通道。(能夠經過netstat、ss獲取網絡相關的信息。)


04  磁盤

除非是規模很是大的系統,不然通常狀況下,從磁盤的指標上看不出啥問題。


平時看的時候,除了看看利用率、吞吐量和請求數量以外,有兩個容易被忽略的點能夠多關注下。


第一點,若是I/O利用率很高,可是吞吐量很小,則意味着存在較多的磁盤隨機讀寫,最好把隨機讀寫優化成順序讀寫。(能夠經過 strace 或者 blktrace 觀察 I/O 是否連續判斷是不是順序的讀寫行爲)


其次,若是I/O等待隊列的長度比較大,則該磁盤存在 I/O 性能問題。通常來講,若是隊列長度持續超過2就能夠這麼認爲。



05  內存

關注內存的時候除了內存消耗以外,有一個Swap換入和換出的內存大小須要特別注意一下。由於Swap須要讀寫磁盤,因此性能不是很高。若是GC的時候遍歷到的對象恰巧被Swap 出去了,便會有磁盤I/O產生,性能天然會降低。因此這個指標不該該過高。


大多數內存問題,都和對象常駐內存不及時釋放有關,有不少工具能夠觀察對象的內存分配狀況。如,jmap、VisualVM、heap dump等。



若是你的程序部署在linux系統上的話,不得不錯過Brendan Gregg的大神整理的精華。下面就引用一張圖,給你們感覺一下,具體能夠去 http://www.brendangregg.com/linuxperf.html 自行查閱更多相關的內容。



最後,雖然性能優化是一件你們都知道的好事,可是再好的事作起來都有成本。因此,如非必要,不要過早、過分進行性能優化哦。



好了,總結一下。


這篇呢,Z哥和你聊了一下很是讓程序員們頭疼的程序性能問題。想要避免受這個問題困擾的前提是事前作好性能優化工做。


作性能優化不能走一步算一步。事先須要作三件事「明確優化目的」、「定標準」、「找到瓶頸點」


具體作優化的時候建議從應用程序層面開始,再到組件層面,最後纔是系統層面,從上往下,層層深刻。順帶分享了每一個層面的經常使用一些方法和思路。


但願對你有所啓發。



在一個大系統中,數據就像水,整個系統就像是一個漏斗,漏斗的每一層表明每一個子程序。上層的子程序對性能的損耗越低,能流下去的水就越多,直到最後一層「數據庫」處,也能夠理解爲是存儲。


因此,趕忙行動起來,開啓保衛數據庫之戰吧。



推薦閱讀:

做者:Zachary

出處:zacharyfan.com/archives/10…

若是你喜歡這篇文章,能夠點一下右下角的「愛心」,支持個人創做~

▶ 關於做者:張帆(Zachary,我的微信號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。本文首發於公衆號:「跨界架構師」(ID:Zachary_ZF)。

若是你是初級程序員,想提高但不知道如何下手。又或者作程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注個人公衆號「跨界架構師」,回覆「技術」,送你一份我長期收集和整理的思惟導圖。

若是你是運營,面對不斷變化的市場一籌莫展。又或者想了解主流的運營策略,以豐富本身的「倉庫」。歡迎關注個人公衆號「跨界架構師」,回覆「運營」,送你一份我長期收集和整理的思惟導圖。

按期發表原創內容:架構設計丨分佈式系統丨產品丨運營丨一些深度思考。

相關文章
相關標籤/搜索