[轉載] 性能調優攻略

原文: http://coolshell.cn/articles/7490.htmlphp

做爲架構工程師, 性能調整是平時常常須要作的工做了, 這篇文章對性能調優方面作了一個很好的綜述, 不知道怎麼入手的同窗們, 趕忙看一下吧html

 

關於性能優化這是一個比較大的話題,在《由12306.cn談談網站性能技術》中我從業務和設計上說過一些可用的技術以及那些技術的優缺點,今天,想從一些技術細節上談談性能優化,主要是一些代碼級別的技術和方法。本文的東西是個人一些經驗和知識,並不必定全對,但願你們指正和補充node

在開始這篇文章以前,你們能夠移步去看一下酷殼之前發表的《代碼優化概要》,這篇文章基本上告訴你——要進行優化,先得找到性能瓶頸! 可是在講如何定位系統性能瓶勁以前,請讓我講一下系統性能的定義和測試,由於沒有這兩件事,後面的定位和優化無從談起。mysql

1、系統性能定義

讓咱們先來講說如何什麼是系統性能。這個定義很是關鍵,若是咱們不清楚什麼是系統性能,那麼咱們將沒法定位之。我見過不少朋友會以爲這很容易,可是仔細一問,其實他們並無一個比較系統的方法,因此,在這裏我想告訴你們如何系統地來定位性能。 整體來講,系統性能就是兩個事:linux

  1. Throughput ,吞吐量。也就是每秒鐘能夠處理的請求數,任務數。
  2. Latency, 系統延遲。也就是系統在處理一個請求或一個任務時的延遲。

通常來講,一個系統的性能受到這兩個條件的約束,缺一不可。好比,個人系統能夠頂得住一百萬的併發,可是系統的延遲是2分鐘以上,那麼,這個一百萬的負載毫無心義。系統延遲很短,可是吞吐量很低,一樣沒有意義。因此,一個好的系統的性能測試必然受到這兩個條件的同時做用。 有經驗的朋友必定知道,這兩個東西的一些關係:ios

  • Throughput越大,Latency會越差。由於請求量過大,系統太繁忙,因此響應速度天然會低。
  • Latency越好,能支持的Throughput就會越高。由於Latency短說明處理速度快,因而就能夠處理更多的請求。

2、系統性能測試

通過上述的說明,咱們知道要測試系統的性能,須要咱們收集系統的Throughput和Latency這兩個值。算法

 

  • 首先,須要定義Latency這個值,好比說,對於網站系統響應時間必需是5秒之內(對於某些實時系統可能須要定義的更短,好比5ms之內,這個更根據不一樣的業務來定義)
  • 其次,開發性能測試工具,一個工具用來製造高強度的Throughput,另外一個工具用來測量Latency。對於第一個工具,你能夠參考一下「十個免費的Web壓力測試工具」,關於如何測量Latency,你能夠在代碼中測量,可是這樣會影響程序的執行,並且只能測試到程序內部的Latency,真正的Latency是整個系統都算上,包括操做系統和網絡的延時,你可使用Wireshark來抓網絡包來測量。這兩個工具具體怎麼作,這個還請你們本身思考去了。
  • 最後,開始性能測試。你須要不斷地提高測試的Throughput,而後觀察系統的負載狀況,若是系統頂得住,那就觀察Latency的值。這樣,你就能夠找到系統的最大負載,而且你能夠知道系統的響應延時是多少。

再多說一些,sql

  • 關於Latency,若是吞吐量不多,這個值估計會很是穩定,當吞吐量愈來愈大時,系統的Latency會出現很是劇烈的抖動,因此,咱們在測量Latency的時候,咱們須要注意到Latency的分佈,也就是說,有百分之幾的在咱們容許的範圍,有百分之幾的超出了,有百分之幾的徹底不可接受。也許,平均下來的Latency達標了,可是其中僅有50%的達到了咱們可接受的範圍。那也沒有意義。
  • 關於性能測試,咱們還須要定義一個時間段。好比:在某個吞吐量上持續15分鐘。由於當負載到達的時候,系統會變得不穩定,當過了一兩分鐘後,系統纔會穩定。另外,也有多是,你的系統在這個負載下前幾分鐘還表現正常,而後就不穩定了,甚至垮了。因此,須要這麼一段時間。這個值,咱們叫作峯值極限。
  • 性能測試還須要作Soak Test,也就是在某個吞吐量下,系統能夠持續跑一週甚至更長。這個值,咱們叫作系統的正常運行的負載極限。

性能測試有不少很復要的東西,好比:burst test等。 這裏不能一一詳述,這裏只說了一些和性能調優相關的東西。總之,性能測試是一細活和累活。shell

3、定位性能瓶頸

有了上面的鋪墊,咱們就能夠測試到到系統的性能了,再調優以前,咱們先來講說如何找到性能的瓶頸。我見過不少朋友會以爲這很容易,可是仔細一問,其實他們並無一個比較系統的方法。數據庫

3.1)查看操做系統負載

首先,當咱們系統有問題的時候,咱們不要急於去調查咱們代碼,這個毫無心義。咱們首要須要看的是操做系統的報告。看看操做系統的CPU利用率,看看內存使用率,看看操做系統的IO,還有網絡的IO,網絡連接數,等等。Windows下的perfmon是一個很不錯的工具,Linux下也有不少相關的命令和工具,好比:SystemTapLatencyTOP,vmstat, sar, iostat, top, tcpdump等等 。經過觀察這些數據,咱們就能夠知道咱們的軟件的性能基本上出在哪裏。好比:

1)先看CPU利用率,若是CPU利用率不高,可是系統的Throughput和Latency上不去了,這說明咱們的程序並無忙於計算,而是忙於別的一些事,好比IO。(另外,CPU的利用率還要看內核態的和用戶態的,內核態的一上去了,整個系統的性能就下來了。而對於多核CPU來講,CPU 0 是至關關鍵的,若是CPU 0的負載高,那麼會影響其它核的性能,由於CPU各核間是須要有調度的,這靠CPU0完成)

2)而後,咱們能夠看一下IO大不大,IO和CPU通常是反着來的,CPU利用率高則IO不大,IO大則CPU就小。關於IO,咱們要看三個事,一個是磁盤文件IO,一個是驅動程序的IO(如:網卡),一個是內存換頁率。這三個事都會影響系統性能。

3)而後,查看一下網絡帶寬使用狀況,在Linux下,你可使用iftop, iptraf, ntop, tcpdump這些命令來查看。或是用Wireshark來查看。

4)若是CPU不高,IO不高,內存使用不高,網絡帶寬使用不高。可是系統的性能上不去。這說明你的程序有問題,好比,你的程序被阻塞了。多是由於等那個鎖,多是由於等某個資源,或者是在切換上下文。

經過了解操做系統的性能,咱們才知道性能的問題,好比:帶寬不夠,內存不夠,TCP緩衝區不夠,等等,不少時候,不須要調整程序的,只須要調整一下硬件或操做系統的配置就能夠了

3.2)使用Profiler測試

接下來,咱們須要使用性能檢測工具,也就是使用某個Profiler來差看一下咱們程序的運行性能。如:Java的JProfiler/TPTP/CodePro Profiler,GNU的gprof,IBM的PurifyPlus,Intel的VTune,AMD的CodeAnalyst,還有Linux下的OProfile/perf,後面兩個可讓你對你的代碼優化到CPU的微指令級別,若是你關心CPU的L1/L2的緩存調優,那麼你須要考慮一下使用VTune。 使用這些Profiler工具,可讓你程序中各個模塊函數甚至指令的不少東西,如:運行的時間 ,調用的次數CPU的利用率,等等。這些東西對咱們來講很是有用。

咱們重點觀察運行時間最多,調用次數最多的那些函數和指令。這裏注意一下,對於調用次數多可是時間很短的函數,你可能只須要輕微優化一下,你的性能就上去了(好比:某函數一秒種被調用100萬次,你想一想若是你讓這個函數提升0.01毫秒的時間 ,這會給你帶來多大的性能)

使用Profiler有個問題咱們須要注意一下,由於Profiler會讓你的程序運行的性能變低,像PurifyPlus這樣的工具會在你的代碼中插入不少代碼,會致使你的程序運行效率變低,從而沒發測試出在高吞吐量下的系統的性能,對此,通常有兩個方法來定位系統瓶頸:

1)在你的代碼中本身作統計,使用微秒級的計時器和函數調用計算器,每隔10秒把統計log到文件中。

2)分段註釋你的代碼塊,讓一些函數空轉,作Hard Code的Mock,而後再測試一下系統的Throughput和Latency是否有質的變化,若是有,那麼被註釋的函數就是性能瓶頸,再在這個函數體內註釋代碼,直到找到最耗性能的語句。

最後再說一點,對於性能測試,不一樣的Throughput會出現不一樣的測試結果,不一樣的測試數據也會有不一樣的測試結果。因此,用於性能測試的數據很是重要,性能測試中,咱們須要觀測試不一樣Throughput的結果

4、常見的系統瓶頸

下面這些東西是我所經歷過的一些問題,也許並不全,也許並不對,你們能夠補充指正,我純屬拋磚引玉。關於系統架構方面的性能調優,你們可移步看一下《由12306.cn談談網站性能技術》,關於Web方面的一些性能調優的東西,你們能夠看看《Web開發中須要瞭解的東西》一文中的性能一章。我在這裏就再也不說設計和架構上的東西了。

通常來講,性能優化也就是下面的幾個策略:

  • 用空間換時間。各類cache如CPU L1/L2/RAM到硬盤,都是用空間來換時間的策略。這樣策略基本上是把計算的過程一步一步的保存或緩存下來,這樣就不用每次用的時候都要再計算一遍,好比數據緩衝,CDN,等。這樣的策略還表現爲冗餘數據,好比數據鏡象,負載均衡什麼的。
  • 用時間換空間。有時候,少許的空間可能性能會更好,好比網絡傳輸,若是有一些壓縮數據的算法(如前些天說的「Huffman 編碼壓縮算法」 和 「rsync 的核心算法」),這樣的算法其實很耗時,可是由於瓶頸在網絡傳輸,因此用時間來換空間反而能省時間。
  • 簡化代碼。最高效的程序就是不執行任何代碼的程序,因此,代碼越少性能就越高。關於代碼級優化的技術大學裏的教科書有不少示例了。如:減小循環的層數,減小遞歸,在循環中少聲明變量,少作分配和釋放內存的操做,儘可能把循環體內的表達式抽到循環外,條件表達的中的多個條件判斷的次序,儘可能在程序啓動時把一些東西準備好,注意函數調用的開銷(棧上開銷),注意面嚮對象語言中臨時對象的開銷,當心使用異常(不要用異常來檢查一些可接受可忽略並常常發生的錯誤),…… 等等,等等,這連東西須要咱們很是瞭解編程語言和經常使用的庫。
  • 並行處理。若是CPU只有一個核,你要玩多進程,多線程,對於計算密集型的軟件會反而更慢(由於操做系統調度和切換開銷很大),CPU的核多了才能真正體現出多進程多線程的優點。並行處理須要咱們的程序有Scalability,不能水平或垂直擴展的程序沒法進行並行處理。從架構上來講,這表再爲——是否能夠作到不改代碼只是加加機器就能夠完成性能提高?

總之,根據2:8原則來講,20%的代碼耗了你80%的性能,找到那20%的代碼,你就能夠優化那80%的性能。 下面的一些東西都是個人一些經驗,我只例舉了一些最有價值的性能調優的的方法,供你參考,也歡迎補充。

4.1)算法調優。算法很是重要,好的算法會有更好的性能。舉幾個我經歷過的項目的例子,你們能夠感受一下。

  • 一個是過濾算法,系統須要對收到的請求作過濾,咱們把能夠被filter in/out的東西配置在了一個文件中,原有的過濾算法是遍歷過濾配置,後來,咱們找到了一種方法能夠對這個過濾配置進行排序,這樣就能夠用二分折半的方法來過濾,系統性能增長了50%。
  • 一個是哈希算法。計算哈希算法的函數並不高效,一方面是計算太費時,另外一方面是碰撞過高,碰撞高了就跟單向鏈表一個性能(可參看Hash Collision DoS 問題)。咱們知道,算法都是和須要處理的數據頗有關係的,就算是被你們所嘲笑的「冒泡排序」在某些狀況下(大多數數據是排好序的)其效率會高於全部的排序算法。哈希算法也同樣,廣爲人知的哈希算法都是用英文字典作測試,可是咱們的業務在數據有其特殊性,因此,對於還須要根據本身的數據來挑選適合的哈希算法。對於我之前的一個項目,公司內某牛人給我發來了一個哈希算法,結果讓咱們的系統性能上升了150%。(關於各類哈希算法,你必定要看看StackExchange上的這篇關於各類hash算法的文章 )
  • 分而治之和預處理。之前有一個程序爲了生成月報表,每次都須要計算很長的時間,有時候須要花將近一成天的時間。因而咱們把咱們找到了一種方法能夠把這個算法發成增量式的,也就是說我天天都把當天的數據計算好了後和前一天的報表合併,這樣能夠大大的節省計算時間,天天的數據計算量只須要20分鐘,可是若是我要算整個月的,系統則須要10個小時以上(SQL語句在大數據量面前性能成級數性降低)。這種分而治之的思路在大數據面前對性能有很幫助,就像merge排序同樣。SQL語句和數據庫的性能優化也是這一策略,如:使用嵌套式的Select而不是笛卡爾積的Select,使用視圖,等等。

4.2)代碼調優。從個人經驗上來講,代碼上的調優有下面這幾點:

  • 字符串操做。這是最費系統性能的事了,不管是strcpy, strcat仍是strlen,最須要注意的是字符串子串匹配。因此,能用整型最好用整型。舉幾個例子,第一個例子是N年前作銀行的時候,個人同事喜歡把日期存成字符串(如:2012-05-29 08:30:02),我勒個去,一個select  where between語句至關耗時。另外一個例子是,我之前有個同事把一些狀態碼用字符串來處理,他的理由是,這樣能夠在界面上直接顯示,後來性能調優的時候,我把這些狀態碼全改爲整型,而後用位操做查狀態,由於有一個每秒鐘被調用了150K次的函數裏面有三處須要檢查狀態,通過改善之後,整個系統的性能上升了30%左右。還有一個例子是,我之前從事的某個產品編程規範中有一條是要在每一個函數中把函數名定義出來,如:const char fname[]=」functionName()」, 這是爲了好打日誌,可是爲何不聲明成 static類型的呢?
  • 多線程調優。有人說,thread is evil,這個對於系統性能在某些時候是個問題。由於多線程瓶頸就在於互斥和同步的鎖上,以及線程上下文切換的成本,怎麼樣的少用鎖或不用鎖是根本(好比:多版本併發控制(MVCC)在分佈式系統中的應用 中說的樂觀鎖能夠解決性能問題),此外,還有讀寫鎖也能夠解決大多數是讀操做的併發的性能問題。這裏多說一點在C++中,咱們可能會使用線程安全的智能指針AutoPtr或是別的一些容器,只要是線程安全的,其無論三七二十一都要上鎖,上鎖是個成本很高的操做,使用AutoPtr會讓咱們的系統性能降低得很快,若是你能夠保證不會有線程併發問題,那麼你應該不要用AutoPtr。我記得我上次咱們同事去掉智能指針的引用計數,讓系統性能提高了50%以上。對於Java對象的引用計數,若是我猜的沒錯的話,處處都是鎖,因此,Java的性能問題一直是個問題。另外,線程不是越多越好,線程間的調度和上下文切換也是很誇張的事,儘量的在一個線程裏幹,儘量的不要同步線程。這會讓你有不少的性能。
  • 內存分配。不要小看程序的內存分配。malloc/realloc/calloc這樣的系統調很是耗時,尤爲是當內存出現碎片的時候。我之前的公司出過這樣一個問題——在用戶的站點上,咱們的程序有一天不響應了,用GDB跟進去一看,系統hang在了malloc操做上,20秒都沒有返回,重啓一些系統就行了。這就是內存碎片的問題。這就是爲何不少人抱怨STL有嚴重的內存碎片的問題,由於太多的小內存的分配釋放了。有不少人會覺得用內存池能夠解決這個問題,可是實際上他們只是從新發明了Runtime-C或操做系統的內存管理機制,徹底於事無補。固然解決內存碎片的問題仍是經過內存池,具體來講是一系列不一樣尺寸的內存池(這個留給你們本身去思考)。固然,少進行動態內存分配是最好的。說到內存池就須要說一下池化技術。好比線程池,鏈接池等。池化技術對於一些短做業來講(如http服務) 至關至關的有效。這項技術能夠減小連接創建,線程建立的開銷,從而提升性能。
  • 異步操做。咱們知道Unix下的文件操做是有block和non-block的方式的,像有些系統調用也是block式的,如:Socket下的select,Windows下的WaitforObject之類的,若是咱們的程序是同步操做,那麼會很是影響性能,咱們能夠改爲異步的,可是改爲異步的方式會讓你的程序變複雜。異步方式通常要經過隊列,要注間隊列的性能問題,另外,異步下的狀態通知一般是個問題,好比消息事件通知方式,有callback方式,等,這些方式一樣可能會影響你的性能。可是一般來講,異步操做會讓性能的吞吐率有很大提高(Throughput),可是會犧牲系統的響應時間(latency)。這須要業務上支持。
  • 語言和代碼庫。咱們要熟悉語言以及所使用的函數庫或類庫的性能。好比:STL中的不少容器分配了內存後,那怕你刪除元素,內存也不會回收,其會形成內存泄露的假像,並可能形成內存碎片問題。再如,STL某些容器的size()==0  和 empty()是不同的,由於,size()是O(n)複雜度,empty()是O(1)的複雜度,這個要當心。Java中的JVM調優須要使用的這些參數:-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold,還須要注意JVM的GC,GC的霸氣你們都知道,尤爲是full GC(還整理內存碎片),他就像「恐龍特級克賽號」同樣,他運行的時候,整個世界的時間都中止了。

4.3)網絡調優

關於網絡調優,尤爲是TCP Tuning(你能夠以這兩個關鍵詞在網上找到不少文章),這裏面有不少不少東西能夠說。看看Linux下TCP/IP的那麼多參數就知道了(順便說一下,你也許不喜歡Linux,可是你不可否認Linux給咱們了不少能夠進行內核調優的權力)。強烈建議你們看看《TCP/IP 詳解 卷1:協議》這本書。我在這裏只講一些概念上的東西。

A) TCP調優

咱們知道TCP連接是有不少開銷的,一個是會佔用文件描述符,另外一個是會開緩存,通常來講一個系統能夠支持的TCP連接數是有限的,咱們須要清楚地認識到TCP連接對系統的開銷是很大的。正是由於TCP是耗資源的,因此,不少攻擊都是讓你係統上出現大量的TCP連接,把你的系統資源耗盡。好比著名的SYNC Flood攻擊。

因此,咱們要注意配置KeepAlive參數,這個參數的意思是定義一個時間,若是連接上沒有數據傳輸,系統會在這個時間發一個包,若是沒有收到迴應,那麼TCP就認爲連接斷了,而後就會把連接關閉,這樣能夠回收系統資源開銷。(注:HTTP層上也有KeepAlive參數)對於像HTTP這樣的短連接,設置一個1-2分鐘的keepalive很是重要。這能夠在必定程度上防止DoS攻擊。有下面幾個參數(下面這些參數的值僅供參考):

1
2
3
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_fin_timeout = 30

對於TCP的TIME_WAIT這個狀態,主動關閉的一方進入TIME_WAIT狀態,TIME_WAIT狀態將持續2個MSL(Max Segment Lifetime),默認爲4分鐘,TIME_WAIT狀態下的資源不能回收。有大量的TIME_WAIT連接的狀況通常是在HTTP服務器上。對此,有兩個參數須要注意,

1
2
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_recycle=1

前者表示重用TIME_WAIT,後者表示回收TIME_WAIT的資源。

TCP還有一個重要的概念叫RWIN(TCP Receive Window Size),這個東西的意思是,我一個TCP連接在沒有向Sender發出ack時能夠接收到的最大的數據包。爲何這個很重要?由於若是Sender沒有收到Receiver發過來ack,Sender就會中止發送數據並會等一段時間,若是超時,那麼就會重傳。這就是爲何TCP連接是可靠連接的緣由。重傳還不是最嚴重的,若是有丟包發生的話,TCP的帶寬使用率會立刻受到影響(會盲目減半),再丟包,再減半,而後若是不丟包了,就逐步恢復。相關參數以下:

1
2
3
4
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

通常來講,理論上的RWIN應該設置成:吞吐量  * 迴路時間。Sender端的buffer應該和RWIN有同樣的大小,由於Sender端發送完數據後要等Receiver端確認,若是網絡延時很大,buffer太小了,確認的次數就會多,因而性能就不高,對網絡的利用率也就不高了。也就是說,對於延遲大的網絡,咱們須要大的buffer,這樣能夠少一點ack,多一些數據,對於響應快一點的網絡,能夠少一些buffer。由於,若是有丟包(沒有收到ack),buffer過大可能會有問題,由於這會讓TCP重傳全部的數據,反而影響網絡性能。(固然,網絡差的狀況下,就別玩什麼高性能了) 因此,高性能的網絡重要的是要讓網絡丟包率很是很是地小(基本上是用在LAN裏),若是網絡基本是可信的,這樣用大一點的buffer會有更好的網絡傳輸性能(來來回回太多太影響性能了)。

另外,咱們想想,若是網絡質量很是好,基本不丟包,而業務上咱們不怕偶爾丟幾個包,若是是這樣的話,那麼,咱們爲何不用速度更快的UDP呢?你想過這個問題了嗎?

B)UDP調優

說到UDP的調優,有一些事我想重點說同樣,那就是MTU——最大傳輸單元(其實這對TCP也同樣,由於這是鏈路層上的東西)。所謂最大傳輸單元,你能夠想像成是公路上的公交車,假設一個公交車能夠最多坐70人,帶寬就像是公路的車道數同樣,若是一條路上最多能夠容下100輛公交車,那意味着我最多能夠運送7000人,可是若是公交車坐不滿,好比平均每輛車只有20人,那麼我只運送了2000人,因而我公路資源(帶寬資源)就被浪費了。 因此,咱們對於一個UDP的包,咱們要儘可能地讓他大到MTU的最大尺寸再往網絡上傳,這樣能夠最大化帶寬利用率。對於這個MTU,以太網是1500字節,光纖是4352字節,802.11無線網是7981。可是,當咱們用TCP/UDP發包的時候,咱們的有效負載Payload要低於這個值,由於IP協議會加上20個字節,UDP會加上8個字節(TCP加的更多),因此,通常來講,你的一個UDP包的最大應該是1500-8-20=1472,這是你的數據的大小。固然,若是你用光纖的話, 這個值就能夠更大一些。(順便說一下,對於某些NB的千光以態網網卡來講,在網卡上,網卡硬件若是發現你的包的大小超過了MTU,其會幫你作fragment,到了目標端又會幫你作重組,這就不須要你在程序中處理了)

再多說一下,使用Socket編程的時候,你可使用setsockopt() 設置 SO_SNDBUF/SO_RCVBUF 的大小,TTL和KeepAlive這些關鍵的設置,固然,還有不少,具體你能夠查看一下Socket的手冊。

最後說一點,UDP還有一個最大的好處是multi-cast多播,這個技術對於你須要在內網裏通知多臺結點時很是方便和高效。並且,多播這種技術對於機會的水平擴展(須要增長機器來偵聽多播信息)也頗有利。

C)網卡調優

對於網卡,咱們也是能夠調優的,這對於千兆以及網網卡很是必要,在Linux下,咱們能夠用ifconfig查看網上的統計信息,若是咱們看到overrun上有數據,咱們就可能須要調整一下txqueuelen的尺寸(通常默認爲1000),咱們能夠調大一些,如:ifconfig eth0 txqueuelen 5000。Linux下還有一個命令叫:ethtool能夠用於設置網卡的緩衝區大小。在Windows下,咱們能夠在網卡適配器中的高級選項卡中調整相關的參數(如:Receive Buffers, Transmit Buffer等,不一樣的網卡有不一樣的參數)。把Buffer調大對於須要大數據量的網絡傳輸很是有效。

D)其它網絡性能

關於多路複用技術,也就是用一個線程來管理全部的TCP連接,有三個系統調用要重點注意:一個是select,這個系統調用只支持上限1024個連接,第二個是poll,其能夠突破1024的限制,可是select和poll本質上是使用的輪詢機制,輪詢機制在連接多的時候性能不好,因主是O(n)的算法,因此,epoll出現了,epoll是操做系統內核支持的,僅當在連接活躍時,操做系統纔會callback,這是由操做系統通知觸發的,但其只有Linux Kernel 2.6之後才支持(準確說是2.5.44中引入的),固然,若是全部的連接都是活躍的,過多的使用epoll_ctl可能會比輪詢的方式還影響性能,不過影響的不大。

另外,關於一些和DNS Lookup的系統調用要當心,好比:gethostbyaddr/gethostbyname,這個函數可能會至關的費時,由於其要到網絡上去找域名,由於DNS的遞歸查詢,會致使嚴重超時,而又不能經過設置什麼參數來設置time out,對此你能夠經過配置hosts文件來加快速度,或是本身在內存中管理對應表,在程序啓動時查好,而不要在運行時每次都查。另外,在多線程下面,gethostbyname會一個更嚴重的問題,就是若是有一個線程的gethostbyname發生阻塞,其它線程都會在gethostbyname處發生阻塞,這個比較變態,要當心。(你能夠試試GNU的gethostbyname_r(),這個的性能要好一些) 這種到網上找信息的東西不少,好比,若是你的Linux使用了NIS,或是NFS,某些用戶或文件相關的系統調用就很慢,因此要當心。

4.4)系統調優

A)I/O模型

前面說到過select/poll/epoll這三個系統調用,咱們都知道,Unix/Linux下把全部的設備都當成文件來進行I/O,因此,那三個操做更應該算是I/O相關的系統調用。說到  I/O模型,這對於咱們的I/O性能至關重要,咱們知道,Unix/Linux經典的I/O方式是(關於Linux下的I/O模型,你們能夠讀一下這篇文章《使用異步I/O大大提升性能》):

第一種,同步阻塞式I/O,這個不說了。

第二種,同步無阻塞方式。其經過fctnl設置 O_NONBLOCK 來完成。

第三種,對於select/poll/epoll這三個是I/O不阻塞,可是在事件上阻塞,算是:I/O異步,事件同步的調用。

第四種,AIO方式。這種I/O 模型是一種處理與 I/O 並行的模型。I/O請求會當即返回,說明請求已經成功發起了。在後臺完成I/O操做時,嚮應用程序發起通知,通知有兩種方式:一種是產生一個信號,另外一種是執行一個基於線程的回調函數來完成此次 I/O 處理過程。

第四種由於沒有任何的阻塞,不管是I/O上,仍是事件通知上,因此,其可讓你充分地利用CPU,比起第二種同步無阻塞好處就是,第二種要你一遍一遍地去輪詢。Nginx之所因此高效,是其使用了epoll和AIO的方式來進行I/O的。

再說一下Windows下的I/O模型,

a)一個是WriteFile系統調用,這個系統調用能夠是同步阻塞的,也能夠是同步無阻塞的,關於看文件是否是以Overlapped打開的。關於同步無阻塞,須要設置其最後一個參數Overlapped,微軟叫Overlapped I/O,你須要WaitForSingleObject才能知道有沒有寫完成。這個系統調用的性能可想而知。

b)另外一個叫WriteFileEx的系統調用,其能夠實現異步I/O,並可讓你傳入一個callback函數,等I/O結束後回調之, 可是這個回調的過程Windows是把callback函數放到了APC(Asynchronous Procedure Calls)的隊列中,而後,只用當應用程序當前線程成爲可被通知狀態(Alterable)時,纔會被回調。只有當你的線程使用了這幾個函數時WaitForSingleObjectExWaitForMultipleObjectsExMsgWaitForMultipleObjectsExSignalObjectAndWait 和 SleepEx,線程纔會成爲Alterable狀態。可見,這個模型,仍是有wait,因此性能也不高。

c)而後是IOCP – IO Completion Port,IOCP會把I/O的結果放在一個隊列中,可是,偵聽這個隊列的不是主線程,而是專門來幹這個事的一個或多個線程去幹(老的平臺要你本身建立線程,新的平臺是你能夠建立一個線程池)。IOCP是一個線程池模型。這個和Linux下的AIO模型比較類似,可是實現方式和使用方式徹底不同。

固然,真正提升I/O性能方式是把和外設的I/O的次數降到最低,最好沒有,因此,對於讀來講,內存cache一般能夠從質上提高性能,由於內存比外設快太多了。對於寫來講,cache住要寫的數據,少寫幾回,可是cache帶來的問題就是實時性的問題,也就是latency會變大,咱們須要在寫的次數上和相應上作權衡。

B)多核CPU調優

關於CPU的多核技術,咱們知道,CPU0是很關鍵的,若是0號CPU被用得過狠的話,別的CPU性能也會降低,由於CPU0是有調整功能的,因此,咱們不能任由操做系統負載均衡,由於咱們本身更瞭解本身的程序,因此,咱們能夠手動地爲其分配CPU核,而不會過多地佔用CPU0,或是讓咱們關鍵進程和一堆別的進程擠在一塊兒。

  • 對於Windows來講,咱們能夠經過「任務管理器」中的「進程」而中右鍵菜單中的「設置相關性……」(Set Affinity…)來設置並限制這個進程能被運行在哪些核上。
  • 對於Linux來講,可使用taskset命令來設置(你能夠經過安裝schedutils來安裝這個命令:apt-get install schedutils)

多核CPU還有一個技術叫NUMA技術(Non-Uniform Memory Access)。傳統的多核運算是使用SMP(Symmetric Multi-Processor )模式,多個處理器共享一個集中的存儲器和I/O總線。因而就會出現一致存儲器訪問的問題,一致性一般意味着性能問題。NUMA模式下,處理器被劃分紅多個node, 每一個node有本身的本地存儲器空間。關於NUMA的一些技術細節,你能夠查看一下這篇文章《Linux 的 NUMA 技術》,在Linux下,對NUMA調優的命令是:numactl 。以下面的命令:(指定命令「myprogram arg1 arg2」運行在node 0 上,其內存分配在node 0 和 1上)

1
numactl --cpubind=0 --membind=0,1 myprogram arg1 arg2

固然,上面這個命令並很差,由於內存跨越了兩個node,這很是很差。最好的方式是隻讓程序訪問和本身運行同樣的node,如:

1
$ numactl --membind 1 --cpunodebind 1 --localalloc myapplication

C)文件系統調優

關於文件系統,由於文件系統也是有cache的,因此,爲了讓文件系統有最大的性能。首要的事情就是分配足夠大的內存,這個很是關鍵,在Linux下可使用free命令來查看 free/used/buffers/cached,理想來講,buffers和cached應該有40%左右。而後是一個快速的硬盤控制器,SCSI會好不少。最快的是Intel SSD 固態硬盤,速度超快,可是寫次數有限。

接下來,咱們就能夠調優文件系統配置了,對於Linux的Ext3/4來講,幾乎在全部狀況下都有所幫助的一個參數是關閉文件系統訪問時間,在/etc/fstab下看看你的文件系統 有沒有noatime參數(通常來講應該有),還有一個是dealloc,它可讓系統在最後時刻決定寫入文件發生時使用哪一個塊,可優化這個寫入程序。還要注間一下三種日誌模式:data=journal、data=ordered和data=writeback。默認設置data=ordered提供性能和防禦之間的最佳平衡。

固然,對於這些來講,ext4的默認設置基本上是最佳優化了。

這裏介紹一個Linux下的查看I/O的命令—— iotop,可讓你看到各進程的磁盤讀寫的負載狀況。

其它還有一些關於NFS、XFS的調優,你們能夠上google搜索一些相關優化的文章看看。關於各文件系統,你們能夠看一下這篇文章——《Linux日誌文件系統及性能分析

4.5)數據庫調優

數據庫調優並非個人強項,我就僅用我很是有限的知識說上一些吧。注意,下面的這些東西並不必定正確,由於在不一樣的業務場景,不一樣的數據庫設計下可能會獲得徹底相反的結論,因此,我僅在這裏作一些通常性的說明,具體問題還要具體分析。

A)數據庫引擎調優

我對數據庫引擎不是熟,可是有幾個事情我以爲是必定要去了解的。

  • 數據庫的鎖的方式。這個很是很是地重要。併發狀況下,鎖是很是很是影響性能的。各類隔離級別,行鎖,表鎖,頁鎖,讀寫鎖,事務鎖,以及各類寫優先仍是讀優先機制。性能最高的是不要鎖,因此,分庫分表,冗餘數據,減小一致性事務處理,能夠有效地提升性能。NoSQL就是犧牲了一致性和事務處理,並冗餘數據,從而達到了分佈式和高性能。
  • 數據庫的存儲機制。不但要搞清楚各類類型字段是怎麼存儲的,更重要的是數據庫的數據存儲方式,是怎麼分區的,是怎麼管理的,好比Oracle的數據文件,表空間,段,等等。瞭解清楚這個機制能夠減輕不少的I/O負載。好比:MySQL下使用show engines;能夠看到各類存儲引擎的支持。不一樣的存儲引擎有不一樣的側重點,針對不一樣的業務或數據庫設計會讓你有不一樣的性能。
  • 數據庫的分佈式策略。最簡單的就是複製或鏡像,須要瞭解分佈式的一致性算法,或是主主同步,主從同步。經過了解這種技術的機理能夠作到數據庫級別的水平擴展。

B)SQL語句優化

關於SQL語句的優化,首先也是要使用工具,好比:MySQL SQL Query AnalyzerOracle SQL Performance Analyzer,或是微軟SQL Query Analyzer,基本上來講,全部的RMDB都會有這樣的工具,來讓你查看你的應用中的SQL的性能問題。 還可使用explain來看看SQL語句最終Execution Plan會是什麼樣的。

還有一點很重要,數據庫的各類操做須要大量的內存,因此服務器的內存要夠,優其應對那些多表查詢的SQL語句,那是至關的耗內存。

下面我根據我有限的數據庫SQL的知識說幾個會有性能問題的SQL:

  • 全表檢索。好比:select * from user where lastname = 「xxxx」,這樣的SQL語句基本上是全表查找,線性複雜度O(n),記錄數越多,性能也越差(如:100條記錄的查找要50ms,一百萬條記錄須要5分鐘)。對於這種狀況,咱們能夠有兩種方法提升性能:一種方法是分表,把記錄數降下來,另外一種方法是建索引(爲lastname建索引)。索引就像是key-value的數據結構同樣,key就是where後面的字段,value就是物理行號,對索引的搜索複雜度是基本上是O(log(n)) ——用B-Tree實現索引(如:100條記錄的查找要50ms,一百萬條記錄須要100ms)。
  • 索引。對於索引字段,最好不要在字段上作計算、類型轉換、函數、空值判斷、字段鏈接操做,這些操做都會破壞索引本來的性能。固然,索引通常都出如今Where或是Order by字句中,因此對Where和Order by子句中的子段最好不要進行計算操做,或是加上什麼NOT之類的,或是使用什麼函數。
  • 多表查詢。關係型數據庫最多的操做就是多表查詢,多表查詢主要有三個關鍵字,EXISTS,IN和JOIN(關於各類join,能夠參看圖解SQL的Join一文)。基原本說,現代的數據引擎對SQL語句優化得都挺好的,JOIN和IN/EXISTS在結果上有些不一樣,但性能基本上都差很少。有人說,EXISTS的性能要好於IN,IN的性能要好於JOIN,我各人以爲,這個還要看你的數據、schema和SQL語句的複雜度,對於通常的簡單的狀況來講,都差很少,因此千萬不要使用過多的嵌套,千萬不要讓你的SQL太複雜,寧肯使用幾個簡單的SQL也不要使用一個巨大無比的嵌套N級的SQL。還有人說,若是兩個表的數據量差很少,Exists的性能可能會高於In,In可能會高於Join,若是這兩個表一大一小,那麼子查詢中,Exists用大表,In則用小表。這個,我沒有驗證過,放在這裏讓你們討論吧。另,有一篇關於SQL Server的文章你們能夠看看《IN vs JOIN vs EXISTS
  • JOIN操做。有人說,Join表的順序會影響性能,只要Join的結果集是同樣,性能和join的次序無關。由於後臺的數據庫引擎會幫咱們優化的。Join有三種實現算法,嵌套循環,排序歸併,和Hash式的Join。(MySQL只支持第一種)
    • 嵌套循環,就好像是咱們常見的多重嵌套循環。注意,前面的索引說過,數據庫的索引查找算法用的是B-Tree,這是O(log(n))的算法,因此,整個算法復法度應該是O(log(n)) * O(log(m)) 這樣的。
    • Hash式的Join,主要解決嵌套循環的O(log(n))的複雜,使用一個臨時的hash表來標記。
    • 排序歸併,意思是兩個表按照查詢字段排好序,而後再合併。固然,索引字段通常是排好序的。

仍是那句話,具體要看什麼樣的數據,什麼樣的SQL語句,你才知道用哪一種方法是最好的。

  • 部分結果集。咱們知道MySQL裏的Limit關鍵字,Oracle裏的rownum,SQL Server裏的Top都是在限制前幾條的返回結果。這給了咱們數據庫引擎不少能夠調優的空間。通常來講,返回top n的記錄數據須要咱們使用order by,注意在這裏咱們須要爲order by的字段創建索引。有了被建索引的order by後,會讓咱們的select語句的性能不會被記錄數的所影響。使用這個技術,通常來講咱們前臺會以分頁方式來顯現數據,Mysql用的是OFFSET,SQL Server用的是FETCH NEXT,這種Fetch的方式其實並很差是線性複雜度,因此,若是咱們可以知道order by字段的第二頁的起始值,咱們就能夠在where語句裏直接使用>=的表達式來select,這種技術叫seek,而不是fetch,seek的性能比fetch要高不少。
  • 字符串。正如我前面所說的,字符串操做對性能上有很是大的惡夢,因此,能用數據的狀況就用數字,好比:時間,工號,等。
  • 全文檢索。千萬不要用Like之類的東西來作全文檢索,若是要玩全文檢索,能夠嘗試使用Sphinx
  • 其它
    • 不要select *,而是明確指出各個字段,若是有多個表,必定要在字段名前加上表名,不要讓引擎去算。
    • 不要用Having,由於其要遍歷全部的記錄。性能差得不能再差。
    • 儘量地使用UNION ALL  取代  UNION。
    • 索引過多,insert和delete就會越慢。而update若是update多數索引,也會慢,可是若是隻update一個,則只會影響一個索引表。
    • 等等。

關於SQL語句的優化,網上有不少文章, 不一樣的數據庫引擎有不一樣的優化技巧,正如本站之前轉發的《MySQL性能優化的最佳20+條經驗

先寫這麼多吧,歡迎你們指正補充。

注:這篇文章的確是個大雜燴。其實其中的說到的不少技術在網上都有不少不少的技術文章,google一下就能找到一堆有不少細節的文章,因此我也就不寫了。這篇性能調優的文章寫做的動機是以前看到 @淘寶褚霸 強推的highscalability.com上的這篇文章:Big List Of 20 Common Bottlenecks,以爲這篇文章泛泛而談,以爲本身能寫得比它好,因此就產生了動機。

轉載時請註明做者和出處,請勿用於商業用途

(轉載本站文章請註明做者和出處 酷 殼 – CoolShell.cn ,請勿用於任何商業用途)

相關文章
相關標籤/搜索