系統(tǒng)性能調(diào)優(yōu)技術(shù)實戰(zhàn)
本文目錄如下:
1、概述 1、1 系統(tǒng)性能定義 1、2 目的意義 2、性能優(yōu)化技術(shù) 2、1 前端優(yōu)化 2、2 后端優(yōu)化 3、總結(jié)
1、概述
最近看了很多關(guān)于系統(tǒng)性能調(diào)優(yōu)的文章,發(fā)現(xiàn)很多文章都是介紹某一方面的,例如專門數(shù)據(jù)庫方面的優(yōu)化、前端頁面的優(yōu)化等等都不是很全面,這里結(jié)合我在工作中的一些實踐對系統(tǒng)性能調(diào)優(yōu)技術(shù)來一個綜合性的分享。
1、1 系統(tǒng)性能定義
如上圖,性能就是吞吐量加延遲,這兩個相互矛盾又相互協(xié)調(diào)構(gòu)成了一個系統(tǒng)性能的定義:
- Throughput ,吞吐量。也就是每秒鐘可以處理的請求數(shù),任務(wù)數(shù)。
- Latency, 系統(tǒng)延遲。也就是系統(tǒng)在處理一個請求或一個任務(wù)時的延遲。
一般來說,一個系統(tǒng)的性能受到這兩個條件的約束,缺一不可。比如,我的系統(tǒng)可以頂?shù)米∫话偃f的并發(fā),但是系統(tǒng)的延遲是2分鐘以上,那么,這個一百萬的負載毫無意義。系統(tǒng)延遲很短,但是吞吐量很低,同樣沒有意義。所以,一個好的系統(tǒng)的性能測試必然受到這兩個條件的同時作用。 有經(jīng)驗的朋友一定知道,這兩個東西的一些關(guān)系:
- Throughput越大,Latency會越差。因為請求量過大,系統(tǒng)太繁忙,所以響應(yīng)速度自然會低。
- Latency越好,能支持的Throughput就會越高。因為Latency短說明處理速度快,于是就可以處理更多的請求。
1、2 目的意義
本文的目的是通過講解系統(tǒng)性能讓大家在后續(xù)的工作中能夠帶著產(chǎn)品化的思路去優(yōu)化自己的代碼包括前后臺、數(shù)據(jù)庫等,自測過程中我們可以利用壓力性能測試pylot、Fiddler、單元測試等工具去發(fā)現(xiàn)系統(tǒng)的問題從而去優(yōu)化提高系統(tǒng)的質(zhì)量,這樣通過團隊的配合和努力來提高增強用戶的體驗從而提高我們公司的競爭力!#p#
2、性能優(yōu)化技術(shù)
以下性能優(yōu)化技術(shù)需要我們在自己工作過程中不斷積累和總結(jié),在工作中配合一些專業(yè)的測試工具去發(fā)現(xiàn)性能的瓶頸,這里把性能優(yōu)化技術(shù)分為兩塊分別是前端和后端的優(yōu)化。
2、1 前端優(yōu)化
2.1.1 負載均衡
通過DNS的負載均衡器(一般在路由器上根據(jù)路由的負載重定向)可以把用戶的訪問均勻地分散在多個Web服務(wù)器上。這樣可以減少Web服務(wù)器的請求負載。因為http的請求都是短作業(yè),所以,可以通過很簡單的負載均衡器來完成這一功能。***是有CDN網(wǎng)絡(luò)讓用戶連接與其最近的服務(wù)器(CDN通常伴隨著分布式存儲)。
CDN的全稱是Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò)。其基本思路是盡可能避開互聯(lián)網(wǎng)上有可能影響數(shù)據(jù)傳輸速度和穩(wěn)定性的瓶頸和環(huán)節(jié),使內(nèi)容傳輸?shù)母?、更穩(wěn)定。
CDN的通俗理解就是網(wǎng)站加速,可以解決跨運營商,跨地區(qū),服務(wù)器負載能力過低,帶寬過少等帶來的網(wǎng)站打開速度慢等問題。
CDN的特點和優(yōu)勢:
1、本地Cache加速 提高了企業(yè)站點(尤其含有大量圖片和靜態(tài)頁面站點)的訪問速度,并大大提高以上性質(zhì)站點的穩(wěn)定性
2、鏡像服務(wù) 消除了不同運營商之間互聯(lián)的瓶頸造成的影響,實現(xiàn)了跨運營商的網(wǎng)絡(luò)加速,保證不同網(wǎng)絡(luò)中的用戶都能得到良好的訪問質(zhì)量。
3、遠程加速 遠程訪問用戶根據(jù)DNS負載均衡技術(shù)智能自動選擇Cache服務(wù)器,選擇最快的Cache服務(wù)器,加快遠程訪問的速度
4、帶寬優(yōu)化 自動生成服務(wù)器的遠程Mirror(鏡像)cache服務(wù)器,遠程用戶訪問時從cache服務(wù)器上讀取數(shù)據(jù),減少遠程訪問的帶寬、分擔(dān)網(wǎng)絡(luò)流量、減輕原站點WEB服務(wù)器負載等功能。
2.1.2 減少請求和2.1.3縮減網(wǎng)頁
減少請求數(shù)
(1)系統(tǒng)某個頁面的加載往往伴隨著多個請求的發(fā)生,請求越多吞吐量越大,延遲就會變大,這里就要考慮優(yōu)化請求數(shù)了,我們可以使用Fiddler等工具查看某個網(wǎng)頁的請求數(shù),如下圖,如果我們的一個網(wǎng)頁引用了很多樣式和js,例如一個頁面引用了10個css和10個js,那么我們應(yīng)該考慮把某些樣式和js合并起來;
(2)Css Sprites:有很多圖片我們其實可以用一張圖片來代替的,一般需要跟美工或UI設(shè)計器配合一起來做的,美工或UI設(shè)計師去設(shè)計出來之后告訴我們圖片中具體元素的位置或者封裝在css中,研發(fā)這邊直接調(diào)用即可。
異步
系統(tǒng)某個頁面中如果有一個請求的響應(yīng)超過0.5秒以上或者請求的響應(yīng)量大于300KB的話我們應(yīng)該考慮進行異步請求,還有就是一些服務(wù)的調(diào)用這些盡量不要用同步,一阻塞整個網(wǎng)站的體驗會非常差;
CSS/JS壓縮
可以借助一些開源的壓縮工具,像開源的yuicompressor,發(fā)布或發(fā)包時把js和css都壓縮一下,這樣js和css文件就會非常小了;
GZIP壓縮
使用GZIP壓縮可以降低服務(wù)器發(fā)送的字節(jié)數(shù),能讓客戶感覺到網(wǎng)頁的速度更 快也減少了對帶寬的使用情況;
IIS里面也可以設(shè)置GZIP壓縮,可以壓縮應(yīng)用程序文件和靜態(tài)文件,具體百度。
精簡代碼
***效的程序就是不執(zhí)行任何代碼的程序,所以,代碼越少性能就越高。關(guān)于代碼級優(yōu)化的技術(shù)大學(xué)里的教科書有很多示例了。如:減少循環(huán)的層數(shù),減少遞歸,在循環(huán)中少聲明變量,少做分配和釋放內(nèi)存的操作,盡量把循環(huán)體內(nèi)的表達式抽到循環(huán)外,條件表達的中的多個條件判斷的次序,盡量在程序啟動時把一些東西準(zhǔn)備好,注意函數(shù)調(diào)用的開銷(棧上開銷),注意面向?qū)ο笳Z言中臨時對象的開銷,小心使用異常。
開源框架
現(xiàn)在開源的好東西太多了,關(guān)鍵是你要有一雙慧眼,向大家推薦開源中國社區(qū)、github、codeplex,我發(fā)現(xiàn)現(xiàn)在比較厲害的開發(fā)者就是一個很牛逼的模仿者,消化掉成為自己的其實就是一種創(chuàng)新;
2.1.4 優(yōu)化查詢
(1)SQL語句的優(yōu)化
關(guān)于SQL語句的優(yōu)化,首先也是要使用工具,比如:MySQL SQL Query Analyzer,Oracle SQL Performance Analyzer,或是微軟SQL Query Analyzer,基本上來說,所有的RMDB都會有這樣的工具,來讓你查看你的應(yīng)用中的SQL的性能問題。 還可以使用explain來看看SQL語句最終Execution Plan會是什么樣的。
還有一點很重要,數(shù)據(jù)庫的各種操作需要大量的內(nèi)存,所以服務(wù)器的內(nèi)存要夠,優(yōu)其應(yīng)對那些多表查詢的SQL語句,那是相當(dāng)?shù)暮膬?nèi)存。
下面我根據(jù)我有限的數(shù)據(jù)庫SQL的知識說幾個會有性能問題的SQL:
全表檢索。比如:select * from user where lastname = “xxxx”,這樣的SQL語句基本上是全表查找,線性復(fù)雜度O(n),記錄數(shù)越多,性能也越差(如:100條記錄的查找要50ms,一百萬條記錄需要5分鐘)。對于這種情況,我們可以有兩種方法提高性能:一種方法是分表,把記錄數(shù)降下來,另一種方法是建索引(為lastname建索引)。索引就像是key-value的數(shù)據(jù)結(jié)構(gòu)一樣,key就是where后面的字段,value就是物理行號,對索引的搜索復(fù)雜度是基本上是O(log(n)) ——用B-Tree實現(xiàn)索引(如:100條記錄的查找要50ms,一百萬條記錄需要100ms)。
索引。對于索引字段,***不要在字段上做計算、類型轉(zhuǎn)換、函數(shù)、空值判斷、字段連接操作,這些操作都會破壞索引原本的性能。當(dāng)然,索引一般都出現(xiàn)在Where或是Order by字句中,所以對Where和Order by子句中的子段***不要進行計算操作,或是加上什么NOT之類的,或是使用什么函數(shù)。
多表查詢。關(guān)系型數(shù)據(jù)庫最多的操作就是多表查詢,多表查詢主要有三個關(guān)鍵字,EXISTS,IN和JOIN(關(guān)于各種join,可以參看圖解SQL的Join一文)。基本來說,現(xiàn)代的數(shù)據(jù)引擎對SQL語句優(yōu)化得都挺好的,JOIN和IN/EXISTS在結(jié)果上有些不同,但性能基本上都差不多。有人說,EXISTS的性能要好于IN,IN的性能要好于JOIN,我各人覺得,這個還要看你的數(shù)據(jù)、schema和SQL語句的復(fù)雜度,對于一般的簡單的情況來說,都差不多,所以千萬不要使用過多的嵌套,千萬不要讓你的SQL太復(fù)雜,寧可使用幾個簡單的SQL也不要使用一個巨大無比的嵌套N級的SQL。還有人說,如果兩個表的數(shù)據(jù)量差不多,Exists的性能可能會高于In,In可能會高于Join,如果這兩個表一大一小,那么子查詢中,Exists用大表,In則用小表。這個,我沒有驗證過,放在這里讓大家討論吧。另,有一篇關(guān)于SQL Server的文章大家可以看看《IN vs JOIN vs EXISTS》。
JOIN操作。有人說,Join表的順序會影響性能,只要Join的結(jié)果集是一樣,性能和join的次序無關(guān)。因為后臺的數(shù)據(jù)庫引擎會幫我們優(yōu)化的。Join有三種實現(xiàn)算法,嵌套循環(huán),排序歸并,和Hash式的Join。(MySQL只支持***種)。
- 嵌套循環(huán),就好像是我們常見的多重嵌套循環(huán)。注意,前面的索引說過,數(shù)據(jù)庫的索引查找算法用的是B-Tree,這是O(log(n))的算法,所以,整個算法復(fù)法度應(yīng)該是O(log(n)) * O(log(m)) 這樣的。
- Hash式的Join,主要解決嵌套循環(huán)的O(log(n))的復(fù)雜,使用一個臨時的hash表來標(biāo)記。
- 排序歸并,意思是兩個表按照查詢字段排好序,然后再合并。當(dāng)然,索引字段一般是排好序的。
還是那句話,具體要看什么樣的數(shù)據(jù),什么樣的SQL語句,你才知道用哪種方法是***的。
部分結(jié)果集。我們知道MySQL里的Limit關(guān)鍵字,Oracle里的rownum,SQL Server里的Top都是在限制前幾條的返回結(jié)果。這給了我們數(shù)據(jù)庫引擎很多可以調(diào)優(yōu)的空間。一般來說,返回top n的記錄數(shù)據(jù)需要我們使用order by,注意在這里我們需要為order by的字段建立索引。有了被建索引的order by后,會讓我們的select語句的性能不會被記錄數(shù)的所影響。使用這個技術(shù),一般來說我們前臺會以分頁方式來顯現(xiàn)數(shù)據(jù),Mysql用的是OFFSET,SQL Server用的是FETCH NEXT,這種Fetch的方式其實并不好是線性復(fù)雜度,所以,如果我們能夠知道order by字段的第二頁的起始值,我們就可以在where語句里直接使用>=的表達式來select,這種技術(shù)叫seek,而不是fetch,seek的性能比fetch要高很多。
字符串。正如我前面所說的,字符串操作對性能上有非常大的惡夢,所以,能用數(shù)據(jù)的情況就用數(shù)字,比如:時間,工號,等。
全文檢索。千萬不要用Like之類的東西來做全文檢索,如果要玩全文檢索,可以嘗試使用Sphinx。
其它。
- 不要select *,而是明確指出各個字段,如果有多個表,一定要在字段名前加上表名,不要讓引擎去算。
- 不要用Having,因為其要遍歷所有的記錄。性能差得不能再差。
- 盡可能地使用UNION ALL 取代 UNION。
- 索引過多,insert和delete就會越慢。而update如果update多數(shù)索引,也會慢,但是如果只update一個,則只會影響一個索引表。
?。?)DBCC DBREINDEX重建索引#p#
優(yōu)化實戰(zhàn)
2.1.5 靜態(tài)化
靜態(tài)化一些不常變的頁面和數(shù)據(jù),并gzip一下。使用nginx的sendfile功能可以讓這些靜態(tài)文件直接在內(nèi)核心態(tài)交換,可以極大增加性能。
一般我們可以做一個靜態(tài)文件管理功能,可以把我們網(wǎng)站的一些欄目直接通過請求/響應(yīng)的方式在服務(wù)器上直接生成靜態(tài)文件,當(dāng)然這里可以設(shè)置一個時間頻率,用戶直接訪問靜態(tài)頁面訪問效率肯定非常高!
2.1.6 緩存
通常,應(yīng)用程序可以將那些頻繁訪問的數(shù)據(jù),以及那些需要大量處理時間來創(chuàng)建的數(shù)據(jù)存儲在內(nèi)存中,從而提高性能;它包括應(yīng)用程序緩存和頁輸出緩存;
一般我們大部分用的是應(yīng)用程序緩存
緩存的應(yīng)用場景主要有:
OutputCache
我們可以用Fiddler找出一些內(nèi)容幾乎不會改變的頁面,給它們設(shè)置OutputCache指令即可;
對于設(shè)置過OutputCache的頁面來說,瀏覽器在收到這類頁面的響應(yīng)后,會將頁面響應(yīng)內(nèi)容緩存起來。 只要在指定的緩存時間之內(nèi),且用戶沒有強制刷新的操作,那么就根本不會再次請求服務(wù)端, 而對于來自其它的瀏覽器發(fā)起的請求,如果緩存頁已生成,那么就可以直接從緩存中響應(yīng)請求,加快響應(yīng)速度。 因此,OutputCache指令對于性能優(yōu)化來說,是很有意義的(除非所有頁面頁面都在頻繁更新)。
應(yīng)用程序緩存
應(yīng)用程序緩存提供了一種編程方式,可通過鍵/值對將任意數(shù)據(jù)存儲在內(nèi)存中,這里提供一個asp.net對緩存有效封裝的例子,見緩存機制理解及C#開發(fā)使用。
緩存可以用來緩存動態(tài)頁面,也可以用來緩存查詢的數(shù)據(jù)。緩存通常有那么幾個問題:
1)緩存的更新。也叫緩存和數(shù)據(jù)庫的同步。有這么幾種方法,一是緩存time out,讓緩存失效,重查,二是,由后端通知更新,一量后端發(fā)生變化,通知前端更新。前者實現(xiàn)起來比較簡單,但實時性不高,后者實現(xiàn)起來比較復(fù)雜 ,但實時性高。
2)緩存的換頁。內(nèi)存可能不夠,所以,需要把一些不活躍的數(shù)據(jù)換出內(nèi)存,這個和操作系統(tǒng)的內(nèi)存換頁和交換內(nèi)存很相似。FIFO、LRU、LFU都是比較經(jīng)典的換頁算法。
3)緩存的重建和持久化。緩存在內(nèi)存,系統(tǒng)總要維護,所以,緩存就會丟失,如果緩存沒了,就需要重建,如果數(shù)據(jù)量很大,緩存重建的過程會很慢,這會影響生產(chǎn)環(huán)境,所以,緩存的持久化也是需要考慮的。