一線架構師帶你玩性能優化

ValValenti 8年前發布 | 12K 次閱讀 SQL 性能優化 軟件架構

摘要:系統優化一個方面是系統化的對IT系統或交易鏈上的每個環節進行分析并優化,另一個是對單一系統進行瓶頸點分析和調優。優化的目標無非是:提高系統的響應速度、吞吐量、降低各層耦合,以應對靈活對邊的市場。

1.什么是系統優化

系統優化一個方面是系統化的對IT系統或交易鏈上的每個環節進行分析并優化,另一個是對單一系統進行瓶頸點分析和調優。但優化的目標大致相同,無非是提高系統的響應速度、吞吐量、降低各層耦合,以應對靈活對邊的市場。

系統優化的3個層次:IT架構治理層、系統層、基礎設施層。

  • IT系統治理層:優化的目的不只是性能優化,還會有為適應業務架構變化而帶來的應用架構優化(如:應用分層、服務治理等)。

  • 系統層:優化的目的包括業務流程優化、數據流程優化(如:提高系統負載、減少系統開銷等)

  • 基礎設施層:優化的目的主要是提高IAAS平臺的能力(如:建立彈性集群具備橫向擴展能力,支持資源快速上下線和轉移等)。

2.系統優化的方法論和思路

什么是方法論,我個人的理解就是聽起來很牛,做過的人認為是廢話,但可以指明行動方向或持續改進的東西。

2.1  常用方法論

(1)不訪問不必要的數據——減少交易線上不必要的環節,減少故障點和維護點。

(2)就近加載/緩存為王——減少不必要的訪問。

(3)故障隔離——不要因為一個系統瓶頸壓垮整個交易平臺。

(4)具備良好的擴展能力——合理的利用資源、提高處理效率和避免單點故障。

(5)對交易鏈進行優化提高吞吐量——異步/減少串行、合理拆分(垂直/水平拆分)、規則前置。

(6)性能和功能同等重要——交易鏈上5個性能變為設計階段90%后為則整體性能為設計時的59%。

2.2  優化的一般思路

2.3  優化的原則

  • 在應用系統的設計、開發過程用中,應始終把性能放在考慮的范圍內。

  • 確定清晰明確的性能目標是關鍵。

  • 性能調優是伴隨整個項目周期的,最好進行分階段設定目標開展,在達到預期性能目標之后即可對本階段工作進行總結和知識轉移進入下一階段調優工作。

  • 必須保證調優后的程序運行正確。

  • 性能更大程度是取決于良好的設計,調優技巧只是一個輔助手段。

  • 調優過程是疊代漸進的過程,每次調優的結果要反饋到后續的代碼開發中去。

  • 性能調優不能以犧牲代碼的可讀性和維護性為代價。

3.性能調優

3.1  常見性能問題

3.1.1常見客戶端性能問題

  • 加載慢:第一次啟動慢或者重新加載慢;

  • 無響應:事件出發后頁面假死;

  • 受網絡帶寬影響嚴重:因為需要下載大量資源文件,在一些在網絡環境不好的地區頁面;

  • JS內存溢出:頻繁對對象的屬性進行操作造成內存大量占用最終溢出。

3.1.2常見的J2EE 系統性能問題

  • 內存泄漏:在運行過程中內存不斷被占用而不能被回收,內存使用率隨時間或負載的增加呈線性增長,系統處理效率隨著時間或并發的增加而下降,直至將分配給JVM 的內存用盡而宕機,或重啟后系統短時間內可恢復正常。

  • 資源泄露:在將資源打開后未關閉或未成功關閉的問題。這些資源包括數據源連接,文件流等。當這些資源經常被打開而未能成功關閉,就會導致資源泄漏。數據連接泄漏就是常見的資源泄漏問題。

  • 過載:系統過度使用,超出系統所能承受的負荷。

  • 內部資源瓶頸:資源過度使用或分配不足引起資源瓶頸。

  • 線程阻塞、線程死鎖:線程退回到無法完成的同步點造成通信阻塞。

  • 應用系統響應慢:由于應用本身或SQL不合理的問題,導致響應時間長。

  • 應用系統不穩定,時快時慢的現象發生。

  • 應用系統各種各樣異常情況發生:有些是中間件服務器拋出的異常、有些是數據端拋出的異常。

3.1.3常見的數據庫問題

  • 死鎖:因為請求保持或者執行效率低不能及時釋放導或因為循環等待致表死鎖;

  • IO繁忙:因為不良SQL或業務邏輯設計不合理導致大量IO等待;

  • CPU使用率居高不下:高并發或緩存穿透導致數據庫CPU居高不下或忽高忽低。

3.2  調優的具體工作

天下武功為快不破,首要的就是提高系統的響應時間(響應時間 = 服務處理時間 + 排隊時間),如經典的響應時間曲線所示,我們要做的就是通過程序優化減少服務響應時間,通過提高系統的吞吐量減少系統的排隊時間。

響應時間曲線(摘自《Oracle性能預測》)

縱軸是響應時間。響應時間是服務時間和排隊時間的總和。橫軸是到達率。隨著每單位時間進入系統事務數的遞增,曲線隨之向右滑動。隨著到達率的繼續增加,在某一時候,排隊時間將陡然上升。當這種情況發生時,響應時間也將陡然上升,性能下降,而用戶感到非常沮喪。

下面通過以往項目中的案例來分析性能優化的具體工作。

3.2.1交易線優化

交易線是從服務的消費者為出發點,看交易在各個層面應該完成的功能,以及功能點之間的關系。功能點之間的關系用有向路徑來表示:

交易線優化的原則:

  • 最短路徑:減少不必要的環節,避免故障點;

  • 交易完整性:通過沖正或補償交易等確保交易線各環節的事物一致性;

  • 故障隔離和快速定位:屏蔽異常情況對正常交易的影像,通過交易碼或錯誤碼能快速等位問題;

  • 流量控制原則:可以通過對服務通道進行流量控制,并結合優先級設置優先處理級別高的業務;

  • 超時控制漏斗原則:盡量保持交易線上前端系統超時設置應該大于后端系統。

【案例】隨著架構的演變,過去一站是構建的豎井式系統,逐步發展為現在的以服務為單元可靈活構建的獨立單元:

在服務治理的過程中原來的核心業務系統被打碎為各種獨立的業務組件,一些中間層平臺型系統基于這些業務組件和流程服務逐漸構建了業務服務,并成為前端應用的快速構建提供業務支撐。在這個過程中服務識別和構建是基礎,交易線的規范是保障,通過交易線規范可以確定服務治理有所為,有所不為,這是因為隨軟件版本迭代,很少有個人能把系統的全部細節都考慮清楚,所以要以規則治理,而不是人治。

要開發一個訂單查詢功能A,服務整合平臺的B和C兩個服務都可以完成相同功能,只是B在C的基礎上增加了一些額外不需要的校驗,按照最短路徑原則這個時候A應該直接調用C服務。

當服務提供者D處理能力不足時,應該及時通知服務消費者C或者按照優先級丟棄部分訪問通道的請求,前端消費者接收后端流量控制錯誤碼并及時通知用戶。這樣可以避免在系統達到容量限制后,所有用戶級別都被拒絕服務。流量控制的目的之一是保證各系統健康穩定運行。一般使用計數器按照交易類型來檢測交易的并發數,不同交易類型,使用不同計數器。當交易請求到達時,計數器加1,當請求響應或者超時,計數器減1。

3.2.2客戶端優化

客戶端優化的首要目標是加快頁面展現速度,其次是減少對服務端的調用。

常見解決辦法:

  • 分析瓶頸點,有針對性優化;

  • 緩存為王,通過在客戶端緩存靜態數據提升頁面響應時間;

  • 通過GZIP壓縮減少客戶端網絡下載流量;

  • 使用壓縮工具對js進行壓縮,減少js文件大小;

  • 刪除、合并腳本、樣式表及圖片減少get請求;

  • 無阻塞加載JS

  • 預加載(圖片、css樣式、js腳本);

  • 按需加載js腳本;

  • 優化js處理方法提升頁面處理速度。

WEB請求時序圖:

【案例】下面是某企業內部應用系統客戶端HTTP請求監控記錄:

從上圖中可以看到共計發送25次請求(21次命中緩存、4次與服務端交互)。

從統計信息可以看到:總計請求耗時5.645秒,進行4次網絡交互,接收5.9KB數據。發送110.25KB數據,GZIP壓縮節省了:8KB數據。

后來該頁面通過優化后端請求、合并和壓縮JS/JSP文件等將頁面響應時間優化到2秒左右。

PS:前端優化最好了解瀏覽器原理、HTTP原理

3.2.3服務端優化

【案例】記一次資源泄露,具體表現為RESULT-SET未關閉:

RESULT-SET未關閉統計

根據堆棧跟蹤日志查看應用程序發現程序代碼存在只關閉connection未關閉Statement和ResultSet的問題。

針對關閉connection是否會自動關閉Statement和ResultSet的問題,以及Statement和ResultSet所占用資源是否會自動釋放問題,JDBC處理規范或JDK規范中做了如下描述:

JDBC處理規范

JDBC. 3.0 Specification——13.1.3 Closing Statement Objects

An application calls the method Statement.close toindicate that it has finished processing a statement. All Statement objectswill be closed when the connection that created them is closed. However, it isgood coding practice for applications to close statements as soon as they havefinished processing them. This allows any external resources that the statementis using to be released immediately.

Closing a Statement object will close and invalidateany instances of ResultSet produced by that Statement object. The resourcesheld by the ResultSet object may not be released until garbage collection runsagain, so it is a good practice to explicitly close ResultSet objects when theyare no longer needed.

These comments about closing Statement objects applyto PreparedStatement and CallableStatement objects as well.

JDBC. 4.0 Specification——13.1.4 Closing Statement Objects

An application calls the method Statement.close toindicate that it has finished processing a statement. All Statement objectswill be closed when the connection that created them is closed. However, it isgood coding practice for applications to close statements as soon as they havefinished processing them. This allows any external resources that the statementis using to be released immediately.

Closing a Statement object will close and invalidateany instances of ResultSet produced by that Statement object. The resourcesheld by the ResultSet object may not be released until garbage collection runsagain, so it is a good practice to explicitly close ResultSet objects when theyare no longer needed.

Once a Statement has been closed, any attempt toaccess any of its methods with the exception of the isClosed or close methodswill result in a SQLException being thrown.

These comments about closing Statement objects applyto PreparedStatement and CallableStatement objects as well.

規范說明:connection.close 自動關閉 Statement.close 自動導致 ResultSet 對象無效(注意只是 ResultSet 對象無效,ResultSet 所占用的資源可能還沒有釋放)。所以還是應該顯式執行connection、Statement、ResultSet的close方法。特別是在使用connection pool的時候,connection.close 并不會導致物理連接的關閉,不執行ResultSet的close可能會導致更多的資源泄露。

JDK處理規范:

JDK1.4

Note: A ResultSet object is automatically closed by theStatement object that generated it when that Statement object is closed,re-executed, or is used to retrieve the next result from a sequence of multipleresults. A ResultSet object is also automatically closed when it is garbagecollected.

Note: A Statement object is automatically closed when it isgarbage collected. When a Statement object is closed, its current ResultSetobject, if one exists, is also closed.

Note: A Connection object is automatically closed when it is garbagecollected. Certain fatal errors also close a Connection object.

JDK1.5

Releases this ResultSet object's database and JDBC resources immediatelyinstead of waiting for this to happen when it is automatically closed.

Note: A ResultSetobject is automatically closed by the Statement object that generated it whenthat Statement object is closed, re-executed, or is used to retrieve the nextresult from a sequence of multiple results. A ResultSet object is alsoautomatically closed when it is garbage collected.

規范說明:

1.垃圾回收機制可以自動關閉它們;

2.Statement關閉會導致ResultSet關閉;

3.Connection關閉不一定會導致Statement關閉。

現在應用系統都使用數據庫連接池,Connection關閉并不是物理關閉,只是歸還連接池,所以Statement和ResultSet有可能被持有,并且實際占用相關的數據庫的游標資源,在這種情況下,只要長期運行就有可能報“游標超出數據庫允許的最大值”的錯誤,導致程序無法正常訪問數據庫。

針對該類問題建議:

(1)    顯式關閉數據庫資源,尤其是使用Connection Pool的時候;

(2)    最優經驗是按照ResultSet,Statement,Connection的順序執行close;

(3)    為了避免由于java代碼有問題導致內存泄露,需要在rs.close()和stmt.close()后面一定要加上rs = null和stmt = null,并做好異常處理;

(4)     如果一定要傳遞ResultSet,應該使用RowSet,RowSet可以不依賴于Connection和Statement。

3.2.4JVM優化

針對JVM的參數調整是需要謹慎處理的。常見的JVM參數:

heap參數設置

-server –Xmx1G –Xms1G -Xmn512M-XX:PermSize=512M -XX:MaxPermSize=512M -XX:+UseCompressedOops

-server:選擇"server" VM,一定要作為第一個參數,與之相對的參數是-client,"client" VM,增加-server參數會影響jvm的其他參數默認值。HotSpot包括一個解釋器和兩個編譯器(client 和 server,二選一的),解釋與編譯混合執行模式,默認啟動解釋執行。server啟動慢,占用內存多,執行效率高,適用于服務器端應用,JDK1.6以后在具有64位能力的jdk環境下將默認啟用該模式; client啟動快,占用內存小,執行效率沒有server快,默認情況下不進行動態編譯,通常用于客戶端應用程序或者PC應用開發和調試。

PS:據報道Hotspot的某些版本Servermode被報告有穩定性問題,因此jvm采用server mode還是client mode 需要通過長時間系統監測來評測。

垃圾回收參數設置

-XX:+DisableExplicitGC-XX:+UseParNewGC-XX:+UseConcMarkSweepGC-XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled

-XX:+DisableExplicitGC禁止System.gc(),免得程序員誤調用gc方法影響性能;

PS:根據歷史經驗一般垃圾回收時間占比小于2%則認為對性能影響不大。

日志類參數

-XX:+PrintClassHistogram -XX:+PrintGCDetails-XX:+PrintGCTimeStamps-Xloggc:log/gc.log

-XX:+ShowMessageBoxOnError-XX:+HeapDumpOnOutOfMemoryError-XX:+HeapDumpOnCtrlBreak

調試的時候設置一些日志參數,如-XX:+PrintClassHistogram -XX:+PrintGCDetails-XX:+PrintGCTimeStamps -Xloggc:log/gc.log,這樣可以從gc.log里查看gc頻繁程度,根據此來評估對性能的影響。

調試的時候設置異常宕機時產生heap dump文件,-XX:+ShowMessageBoxOnError-XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpOnCtrlBreak,這樣可以查看宕機時系統執行哪些操作。

性能監控類參數設置

-Djava.rmi.server.hostname=Server IP-Dcom.sun.management.jmxremote.port=7091-Dcom.sun.management.jmxremote.ssl=false-Dcom.sun.management.jmxremote.authenticate=false

增加以上參數既可以通過visualVM或jconsole監控遠程JVM的執行情況。

JVM參數調整

調整heap參數和垃圾回收參數,需要通過壓力測試和監控記錄綜合分析最有方案:

Id

參數組合

TransResponse Time

Throughput

Passed Transactions

Heap參數

GC參數

1

         

2

         

3

         

4

         

5

         

【案例】應用服務器運行一段Object實例數量達百萬/千萬級別,使用IBMHeapAnalyzer分析內存溢出時生成heapdump文件,發現89.1%的空間被基礎對象占用(為從數據庫加載大量記錄導致):

使用jprofiler監控后發現,大量未釋放的VchBaseVo對象:

查看工程代碼,發現使用Hibernate的list()方法去查詢,hibernatelist()方法優先查詢緩存數據,如獲取不到則從數據庫中進行獲取,從數據庫獲取到后Hibernate將會相應的填充一級、二級緩存,所以在應用服務器級別內存中出現百萬級的對象占用內存問題,此為hibernate緩存的一個有效解決方案,但是在此處確實帶來了性能問題,需要調用clear()  釋放一級緩存占用的內存資源。

3.2.5數據庫優化

【案例】某企業內部核心業務系統數據庫出現業務高峰CPU使用率居高不下,存在大數據量查詢、多表連接造成查詢性能下降、表索引建立不合理等問題,最終通過以下辦法將業務高峰期CPU使用率控制在30%內:

在SQL*PLUS下執行下面語句:

SQL> set line 1000  --設置每行顯示1000個字符

SQL> set autotrace traceonly  --顯示執行計劃和統計信息,但是不顯示查詢輸出

執行效率低下SQL語句:

select variablein0_.TOKENVARIABLEMAP_ as  TOKENVAR7_1_

from JBPM_VARIABLEINSTANCE variablein0_

 

where variablein0_.TOKENVARIABLEMAP_ =  '4888804'

查看優化前的執行計劃:

執行計劃

----------------------------------------------------------

Plan hash value:  3971367966

-------------------------------------------------------------------------------------------

| Id | Operation  | Name | Rows | Bytes | Cost (%CPU)| Time|

-------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |                       |    12 |    612 | 12408   (2)| 00:02:29 |

|*  1 |  TABLE ACCESS FULL| JBPM_VARIABLEINSTANCE  |    12 |   612 | 12408   (2)| 00:02:29 |

-------------------------------------------------------------------------------------------

Predicate  Information (identified by operation id):

---------------------------------------------------

1 -  filter("VARIABLEIN0_"."TOKENVARIABLEMAP_"=4888804)

 

統計信息

----------------------------------------------------------

1   recursive calls

1   db block gets

48995   consistent gets

48982   physical reads

0   redo size

1531   bytes sent via SQL*Net to client

248   bytes received via SQL*Net from client

2   SQL*Net roundtrips to/from client

0   sorts (memory)

0   sorts (disk)

9   rows processed

從執行計劃看該語句缺少索引導致全表掃描。消耗總一致性讀占用為:48995,平均每行一致性讀:48995/9=5444,物理讀為:48982,不滿足正常性能需要。創建索引優化后的執行計劃:

統計信息

----------------------------------------------------------

1  recursive calls

0  db block gets

6  consistent gets

4  physical reads

0  redo size

1530  bytes sent via SQL*Net to  client

248  bytes received via SQL*Net  from client

2  SQL*Net roundtrips to/from  client

0  sorts (memory)

0  sorts (disk)

9  rows processed

從執行計劃看該語句消耗總一致性讀占用為:6,平均每行一致性讀:6/9=0.67,物理讀為:4,為比較高效的SQL。

一般認為平均每行一致性讀超過100的為執行效率比較低的SQL,10以內為執行效率比較高的SQL。

根據以往優化實踐, 引起SQL效率低下的問題 主要集中在如下幾個方面:

(1)訪問路徑,主要集中在由于索引缺失或者數據遷移導致索引失效引起的SQL執行時無法使用索引掃描,而被迫使用全表掃描訪問路徑。此時的解決方法是建立缺失的索引或者重建索引。

(2)過度使用子查詢,在某些情形下我們會連接多個大表,而此時由于業務邏輯的需要我們經常會使用到某些子查詢,由于語句的邏輯太過復雜,致使oracle無法自動將子查詢語句轉換為多表連接操作,由此帶來的結果是導致oracle選擇錯誤的執行路徑,帶來語句執行性能的急劇下降。因此,我們需要盡可能使用連接查詢代替子查詢,這樣可以幫助oracle查詢優化器根據數據分區情況、索引設計情況,選擇合理的連接順序、連接技術以及表訪問技術,即選擇最高效的執行計劃。

(3)使用綁定變量的好處是可以避免硬解析,好處在此不多談,但帶來的壞處是有可能選擇錯誤的執行計劃,而這有可能引起性能的急劇下降。目前oracle 10g中已經引入綁定變量分級機制來著手處理這個問題, 11g通過創建新的子游標而維護一個新的執行計劃。在11g下我們可以大膽地使用綁定變量。

3.2.6負載均衡優化

負載均衡負責訪問流量分發并提高系統橫向擴展能力,避免系統單點故障。下面是某個項目組負載均衡問題分析和優化思路:

負載均衡算法:

  1. 隨機(Random):即從pool地址里隨機選擇一臺,好處:算法簡單、性能高,請求耗時差別不大時能基本保持后端是均衡的;缺點:如果請求耗時差別較大那么后端機器容易不均衡。

  2. Round-Robin:根據pool地址列表順序選擇,好處:算法簡單、性能高,缺點:和隨機一樣如果請求耗時差別較大那么后端機器容易不均衡。

  3. 按權重:可以給pool中的主機分配權重,之后按照權重分配請求,好處:可以利舊特別是運行多年生產環境積累了不同配置的主機時需要此算法,但隨著虛擬化該問題已經在IAAS層解決了。

  4. Hash:即對請求信息做hash后分派到pool中的機器上(一般對靜態資源的加載使用),好處:增加緩存命中率;缺點:因為需要讀取請求信息并做hash,所以需要消耗更多的CPU資源。

  5. 按照響應時間:按照響應時間來分配,好處:可以將請求分配給性能好的主機;缺點:如果請求耗時差別較大那么后端機器容易不均衡。

  6. 按照最小連接數:根據主機連接數多少來分配,好處:均衡請求資源;缺點:新增服務器或重啟某一臺會因為瞬間請求量過大而出現性能問題。

會話保持:

  1. 無會話保持:每次請求當認為新的請求重新按照負載均衡算法分配給后端主機。好處:簡單、性能高;缺點:需要后端服務做無狀態處理;

  2. 基于接入ip保持:同一個IP第一次按照負載均衡算法分配后,第二次請求還是分配給上次的主機,好處:回話保持比較穩定;缺點:導致部分網絡內用戶都連入一臺服務器;

  3. 基于cookie保持:第一次請求負載均衡器在HTTP請求頭部insert cookie,第二次請求根據請求的HTTP頭中的cookie分配給上次的主機。好處:相對穩定、可以靈活切換;缺點:偶爾因為清除cookie導致回話丟失。

健康檢查:

  1. 基于TCP端口:監聽端口是否啟用,如果未監聽到則將該主機沖pool中剔除,好處:簡單、缺點:有可能容器啟動、應用未啟動就有請求分發過來

  2. 基于Http get/TCP請求:定期向服務器發送請求并判斷的返回串與約定的是否一致,如果不一致則將該主機沖pool中剔除,好處:可以精準確定應用是否正常啟動,可以動態控制服務是否在線,缺點:需要編寫腳本。

 

 

來自:http://mp.weixin.qq.com/s?__biz=MzI3MzEzMDI1OQ==&mid=2651815238&idx=1&sn=8b549dd1c6689732c892f60d00a33b70&chksm=f0dc2b3ac7aba22cbad3a7f0fc8365d2f377ca75e744907ba7fb5a296406ff4c35f888efecf0&scene=0

 

 本文由用戶 ValValenti 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!