原文: http://coolshell.cn/articles/7490.htmlphp
做爲架構工程師, 性能調整是平時常常須要作的工做了, 這篇文章對性能調優方面作了一個很好的綜述, 不知道怎麼入手的同窗們, 趕忙看一下吧html
關於性能優化這是一個比較大的話題,在《由12306.cn談談網站性能技術》中我從業務和設計上說過一些可用的技術以及那些技術的優缺點,今天,想從一些技術細節上談談性能優化,主要是一些代碼級別的技術和方法。本文的東西是個人一些經驗和知識,並不必定全對,但願你們指正和補充。node
在開始這篇文章以前,你們能夠移步去看一下酷殼之前發表的《代碼優化概要》,這篇文章基本上告訴你——要進行優化,先得找到性能瓶頸! 可是在講如何定位系統性能瓶勁以前,請讓我講一下系統性能的定義和測試,由於沒有這兩件事,後面的定位和優化無從談起。mysql
讓咱們先來講說如何什麼是系統性能。這個定義很是關鍵,若是咱們不清楚什麼是系統性能,那麼咱們將沒法定位之。我見過不少朋友會以爲這很容易,可是仔細一問,其實他們並無一個比較系統的方法,因此,在這裏我想告訴你們如何系統地來定位性能。 整體來講,系統性能就是兩個事:linux
通常來講,一個系統的性能受到這兩個條件的約束,缺一不可。好比,個人系統能夠頂得住一百萬的併發,可是系統的延遲是2分鐘以上,那麼,這個一百萬的負載毫無心義。系統延遲很短,可是吞吐量很低,一樣沒有意義。因此,一個好的系統的性能測試必然受到這兩個條件的同時做用。 有經驗的朋友必定知道,這兩個東西的一些關係:ios
通過上述的說明,咱們知道要測試系統的性能,須要咱們收集系統的Throughput和Latency這兩個值。算法
再多說一些,sql
性能測試有不少很復要的東西,好比:burst test等。 這裏不能一一詳述,這裏只說了一些和性能調優相關的東西。總之,性能測試是一細活和累活。shell
有了上面的鋪墊,咱們就能夠測試到到系統的性能了,再調優以前,咱們先來講說如何找到性能的瓶頸。我見過不少朋友會以爲這很容易,可是仔細一問,其實他們並無一個比較系統的方法。數據庫
首先,當咱們系統有問題的時候,咱們不要急於去調查咱們代碼,這個毫無心義。咱們首要須要看的是操做系統的報告。看看操做系統的CPU利用率,看看內存使用率,看看操做系統的IO,還有網絡的IO,網絡連接數,等等。Windows下的perfmon是一個很不錯的工具,Linux下也有不少相關的命令和工具,好比:SystemTap,LatencyTOP,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緩衝區不夠,等等,不少時候,不須要調整程序的,只須要調整一下硬件或操做系統的配置就能夠了。
接下來,咱們須要使用性能檢測工具,也就是使用某個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的結果。
下面這些東西是我所經歷過的一些問題,也許並不全,也許並不對,你們能夠補充指正,我純屬拋磚引玉。關於系統架構方面的性能調優,你們可移步看一下《由12306.cn談談網站性能技術》,關於Web方面的一些性能調優的東西,你們能夠看看《Web開發中須要瞭解的東西》一文中的性能一章。我在這裏就再也不說設計和架構上的東西了。
通常來講,性能優化也就是下面的幾個策略:
總之,根據2:8原則來講,20%的代碼耗了你80%的性能,找到那20%的代碼,你就能夠優化那80%的性能。 下面的一些東西都是個人一些經驗,我只例舉了一些最有價值的性能調優的的方法,供你參考,也歡迎補充。
4.1)算法調優。算法很是重要,好的算法會有更好的性能。舉幾個我經歷過的項目的例子,你們能夠感受一下。
4.2)代碼調優。從個人經驗上來講,代碼上的調優有下面這幾點:
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)時,纔會被回調。只有當你的線程使用了這幾個函數時WaitForSingleObjectEx, WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx, SignalObjectAndWait 和 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,或是讓咱們關鍵進程和一堆別的進程擠在一塊兒。
多核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)數據庫引擎調優
我對數據庫引擎不是熟,可是有幾個事情我以爲是必定要去了解的。
B)SQL語句優化
關於SQL語句的優化,首先也是要使用工具,好比:MySQL SQL Query Analyzer,Oracle SQL Performance Analyzer,或是微軟SQL Query Analyzer,基本上來講,全部的RMDB都會有這樣的工具,來讓你查看你的應用中的SQL的性能問題。 還可使用explain來看看SQL語句最終Execution Plan會是什麼樣的。
還有一點很重要,數據庫的各類操做須要大量的內存,因此服務器的內存要夠,優其應對那些多表查詢的SQL語句,那是至關的耗內存。
下面我根據我有限的數據庫SQL的知識說幾個會有性能問題的SQL:
仍是那句話,具體要看什麼樣的數據,什麼樣的SQL語句,你才知道用哪一種方法是最好的。
關於SQL語句的優化,網上有不少文章, 不一樣的數據庫引擎有不一樣的優化技巧,正如本站之前轉發的《MySQL性能優化的最佳20+條經驗》
先寫這麼多吧,歡迎你們指正補充。
注:這篇文章的確是個大雜燴。其實其中的說到的不少技術在網上都有不少不少的技術文章,google一下就能找到一堆有不少細節的文章,因此我也就不寫了。這篇性能調優的文章寫做的動機是以前看到 @淘寶褚霸 強推的highscalability.com上的這篇文章:Big List Of 20 Common Bottlenecks,以爲這篇文章泛泛而談,以爲本身能寫得比它好,因此就產生了動機。
(轉載時請註明做者和出處,請勿用於商業用途)
(轉載本站文章請註明做者和出處 酷 殼 – CoolShell.cn ,請勿用於任何商業用途)