一份平民化的應用性能優化檢查列表(完整篇)--轉

原文地址:http://calvin1978.blogcn.com/articles/checklist.htmlhtml

1.總原則

一些正確但稍顯廢話的原則,但能指導後面每一個章節的優化,因此仍是要囉嗦一次。java

 

  1. 可擴展性架構,堆機器能不能解決問題是最最優先考慮的問題
  2. 去中心化的點對點通訊,優於經過中心代理的通訊
  3. 池化的長鏈接,優於短鏈接
  4. 二進制數據,優於文本數據
  5. 儘可能減小交互,一次調用的粗粒度聚合接口 優於 屢次調用的細粒度接口
  6. 儘可能減小交互,批量接口優於循環調用
  7. 儘可能只交互必要的數據
  8. 儘可能就近訪問
  9. 儘可能使用緩存
  10. 老是設定超時
  11. 在合適的場景,並行化執行
  12. 在合適的場景,異步化執行

2.環境準備

保證符合自家各類規範(沒有的話趕忙回家寫一個),尤爲線下壓測服務器的配置要與生產環境一致。redis

2.1 操做系統

  • 自家規範調優應包含TCP內核參數,網卡參數及多隊列綁定,IO&Swap內核參數,ulimit資源限制等。

2.2 JVM與應用服務器

  • 使用JDK7.0 u80 或 JDK8 最新版。
  • 檢查JVM啓動參數已按自家規範調優,見《關鍵業務系統的JVM參數推薦》
  • 檢查應用服務器(Tomcat或微服務容器) 已按自家指南調優,如線程數等。

2.3 周邊依賴系統

  • 檢查數據庫,緩存,消息系統,已按自家指南調優。

2.4 後臺輔助程序

  • 檢查日誌收集,系統監控等,已使用最新版本,最優配置。
  • 最好其最大消耗已被控制(經過cgroup,taskset等方式)。

2.5 測試程序

  • 壓測工具如JMeter,啓動參數要參考真實應用客戶端的參數優化(如JVM參數,Netty參數等)。
  • 測試腳本和客戶端程序通過review,不存在影響性能的步驟,不存在System.out.println()等明顯的瓶頸。

2.5 流量模型

  • 扇入模型:平時與高峯期的流量估算,各接口的流量比例,響應時間要求
  • 扇出模型:各接口對遠程服務、數據庫、緩存、消息系統的調用比例,響應時間估算。

 

你們在內心都有這麼一個大概的模型,但不多認真寫出來。數據庫

行文到此,你們大概能夠感覺到這份checklist的風格,都是你們明白的道理,但可能一時也會忘掉的,這裏囉囉嗦嗦的給寫下來。後端


3.數據庫

特別鳴謝,我司DBA。緩存

3.1 拓撲

根據擴展性原則考慮:安全

  • 垂直拆分:按業務將不一樣的表拆分到不一樣的庫。
  • 水平拆分:水平分庫分表。
  • 讀寫分離:在業務容許的狀況下,在從庫讀取非實時數據。

3.2 Schema

自家規範應包含:服務器

  • 統一的存儲引擎,主鍵策略。
  • 禁用存儲過程,函數,觸發器,外鍵約束。
  • 列類型永遠越短越好,建議:布爾/枚舉:tinyint,日期與時間戳:timestamp或int,char/text/blob: 儘可能用符合實際長度的varchar(n),小數及貨幣:移位轉爲int 或 decimal,IP地址:int。
  • 索引策略:索引字段的順序須要考慮字段值去重以後的個數,較多的放前面,合理建立聯合索引,避免冗餘索引,合理利用覆蓋索引等。

 

3.3 SQL

1. 自家規範應包含:網絡

  • 如禁止多於3表join,禁用子查詢
  • 禁止where子句中對字段施加函數,如to_date(add_time)>xxxxx
  • 避免MySQL進行隱式類型轉化,如ISENDED&eq;1 與 ISENDED&eq;`1`
  • 不建議使用%前綴模糊查詢,模糊查詢較多時建議使用ElasticSearch

 

根據儘可能少數據原則與儘可能少交互的原則來設計SQL:數據結構

  • 禁止select *
  • 合理的SQL語句,減小交互次數

根據擴展性原則,將負載放在更容易伸縮的應用服務實例上:

  • 儘可能不要作數學運算,函數運算, 或者輸出格式轉換等非必要操做
  • 避免count(*),計數統計實時要求較強使用memcache或者redis,非實時統計使用單獨統計表,定時更新。
  • 甚至排序都是不鼓勵的,儘可能在應用側進行。另外避免多餘的排序,使用GROUP BY 時,默認會進行排序,當你不須要排序時,可使用order by null。

2. 聯繫DBA進行MySQL統計的慢查詢的Review,解析SQL查詢計劃時儘可能避免extra列出現:Using File Sort,Using Temporary

3.4 DAO框架

  • 根據儘可能少交互與儘可能少數據的原則,需使用對SQL徹底可控的DAO框架,建議爲MyBatis 或 Spring JDBC Template。
  • 必須使用prepareStatement,提高性能與防注入。
  • 根據一切皆有超時的原則,配置SQL執行的超時。可在鏈接池裏設置default值,可在MyBatis的Mapper定義裏可設置每一個請求的超時,惋惜規範是秒級的。
  • JDBC driver 規範自己不支持異步模式,若是必定要異步,能夠像Quasar那樣把請求封裝成Callable交給另外的線程池執行,但要注意其額外開銷。

 

3.5 事務

  • 不使用事務,鏈接池設置autocommit,使用其餘方式來保持數據一致性。
  • 經過Transaction Annotation控制事務,事務跨度儘可能短,把非事務範圍內的業務邏輯剔除到被標註的函數以外。
  • 只讀事務能夠不加事務標註。

 

鏈接池

選型:

  • 在分庫分表時,根據點對點通訊優先的原則,儘可能使用客戶端分片的實現。功能不知足時才用MyCat中央代理。
  • 推薦使用性能最高HikariCP,或者Druid,不推薦c3p0與DBCP。

 

鏈接池的配置:

  • 配置初始值,再聯繫DBA得到線上數據庫支持的鏈接數,計算最大鏈接數。
  • 鏈接有效性檢查,只在鏈接空閒檢測時執行,不在拿出和歸還鏈接時執行,最好是直接使用數據的Ping方案,不要配置檢查SQL。
  • 根據老是設置超時的原則,配置獲取鏈接超時的時間。
  • 配置合理的空閒鏈接回收間隔和空閒時間。

番外篇:在分庫分表時,可考慮基於HikariCP二次開發,減小總的空閒鏈接檢查線程數(好比128個分區,可能有256條線程),重用同一個實例上的庫的鏈接等。


4.緩存

 

4.1 多級緩存

 

  • 根據緩存原則, 緩存 > 數據庫/遠程調用
  • 根據就近原則, 堆內緩存 > 堆外緩存 > 集中式緩存
  • 堆內緩存受大小限制,並影響GC
  • 堆內緩存與堆外緩存,分佈在每一臺應用服務器上,刷新方式比集中式緩存複雜
  • 堆外緩存與集中式緩存,須要序列化/反序列化對象
  • 集中式緩存,有網絡傳輸的成本,特別是數據超過一個網絡包的大小。
  • 集中式緩存,一次獲取多個鍵時,在有分區的狀況下,須要收發多個網絡包。

使用上述條件選擇合適的緩存方案,或同時使用多級緩存,逐層回源。

 

4.2 綜述

  • 須要對回源進行併發控制,當key失效時,只有單一線程對該key回源。
  • 基於二進制優於文本數據的原則,JSON的序列化方案較通用與更高的可讀性。而對於較大,結構較複雜的對象,基於Kyro,PB,Thrift的二進制序列化方案的性能更高,見後面的序列化方案部分。

 

4.3 堆內緩存

選型:

  • 推薦Guava Cache。
  • Ehcache較重,性能也較差。更不要使用存在嚴重bug的Jodd Cache。

GuavaCache:

  • 正確設置並行度等參數。
  • 重載load()參數,實現單一線程回源。
  • Guava Cache能後臺定時刷新,在刷新的過程當中,依然使用舊數據響應請求,不會形成卡頓,但須要重載實現reload()函數。
  • Guava Cache同時還支持併發安全版的WeakHashMap。

 

4.4 堆外緩存

選型:

  • 推薦Cassandra的OHC 或者 OpenHFT的Chronical map2。
  • OHC夠簡單,其實R大不喜歡Chronical,玩的太深,換個JDK均可能跑不起來。
  • Chronical map3的license則較不友好,複雜度高且要求JDK8。
  • 其餘的Ehcache的Terracota Offheap 一貫不喜歡。

4.5 Memcached

​客戶端:

  • 基於點對點通訊優於網關的原則,使用客戶端一致性哈希分區。
  • 推薦Spymemcached。 XMemcached 過久沒更新,Folsom知名度不高。
  • 注意Spymemcached爲單線程單鏈接架構(一個MemcachedClient只有一條IO線程,與每臺Memcached只有一條鏈接),必要時可多建幾個MemcachedClient隨機選擇,但不要用Commons Pool去封裝它,把Spy本來的設計一筆抹殺。
  • 根據在合適場景使用併發的原則,Spymemcached支持異步API。
  • 根據一切皆設超時的原則,可在鏈接工廠中設置最大超時數,默認值兩秒半太長。

 

數據結構:

  • Key必須設置失效時間。
  • Key必須有長度限制。
  • Value長度須要控制,以不超過1個網絡包(MTU,千五字節)爲佳。
  • Value大小差異較大的緩存類型,建議拆分到不一樣MC集羣,不然會形成低使用率而且產生踢出。

 

4.6 Redis as Cache

 

Redis拓撲:

基於點對點通訊優於網關的原則,使用以下兩種拓撲

  • 無HA的普通分片:由Jedis客戶端完成分片路由。
  • Redis Cluster:一樣由Jedis客戶端封裝分區,跳轉,重試等邏輯,須要使用最新版的Jedis版本。

 

服務端:

  • Cache節點與持久化數據節點不要混用。
  • Cache節點是否須要持久化要仔細衡量。
  • 因爲Redis是單線程,使用taskset進行cpu綁定後能夠有效地利用cpu,並在單機上運行多個redis實例。
  • 對熱鍵進行監控,發現不合理的熱健要進行分拆等處理。

客戶端:

  • Jedis基於Apache Commons Pool進行了多鏈接的封裝,正確配置總鏈接數不超過Redis Server的容許鏈接數。
  • 性能考慮,空閒鏈接檢查不要過於頻繁(建議30秒以上),另不要打開testOnBorrow等測試參數。
  • 根據一切皆有超時的原則,設定統一的調用超時,獲取鏈接的最長等待時間參數,重試次數
  • 根據在合適的地方異步的原則,Jedis自己沒有異步API,只在PipleLine模式下支持。

數據結構:

  • 必須對Key設置失效時間。
  • Key必須有長度限制。
  • Value長度須要控制,不要超過一個網絡包。另外集合的元素不要超過五千個。
  • 除了使用序列化的String,一樣能夠考慮用Hash來存儲對象,注意內部結構爲ZipList與HashTable時,hmget 與hgetall的不一樣複雜度。

命令:

  • 慎用的命令:LANGE(0, -1), HGETALL, SMEMBER
  • 高複雜度的命令: ZINTERSTORE, SINTERSTORE, ZUNIONSTORE, ZREM
  • 儘可能使用多參數的命令:MGET/MSET,HMGET/HMSET, LPUSH/RPUSH, LRANGE
  • 儘可能使用pipeline
  • 根據減小交互的原則,必要時可以使用Redis的Lua腳本

 


5.服務調用

5.1 接口設計

1. 儘可能少交互的原則:

支持批量接口,最大的批量,綜合考慮調用者的需求與 後端存儲的能力。

支持粗粒度接口,在支持原子細粒度接口的同時,支持粗粒度接口/聚合層接口,將多個數據源的獲取,多個動做,合併成一個粗粒度接口。

 

2. 儘可能少數據的原則:

在提供返回全部數據的大接口的同時,提供只提供知足部分調用者須要的輕量接口。

最好再提供能定製返回字段的接口。

 

3. 二進制數據優於文本數據

一樣是一個簡單通用性,與性能的選擇,特別是大數據量時。

 

5.2 RESTful

僅以Apache HttpClient爲例,大部分Restful框架都是對Apache HttpClient的封裝。

另外OkHttp也值得看看。

 

  • 不要重複建立ApacheClient實例,使用鏈接池,正確配置鏈接池的鏈接數。
  • 鏈接池老是有鎖,針對不一樣的服務,使用不一樣的Apache HttpClient實例,將鎖分散開來。在高併發時比使用全局單例的ApacheClient,有很大的性能提高。
  • 根據一切調用皆有超時的原則,每次調用均設置超時時間。RequestConfig裏共有Connect Timeout, Socket Timout 和 從Pool中獲取鏈接的Timeout三種超時。
  • 須要異步或並行的場景,使用Apache AsyncHttpClient項目。但要注意AsyncHttpClient項目,檢查調用超時的週期默認爲1秒。

 

5.3 自家RPC框架

 

每家的RPC框架特性不一樣,但考慮點都相似。

 

 


6.消息異步

 

6.1 選型

  • 根據就近原則,能夠先嚐試用JVM內的隊列來解決,而後再考慮中央消息系統。
  • 可靠性要求極高的選擇RabbitMQ,可支持單條消息確認。
  • 海量消息場景,容許極端狀況下少許丟失則使用Kafka。

 

6.2 Kafka

  • 在同步和異步之間作好權衡,異步批量發送能夠極大的提升發送的速度。
  • 關注消費者以下參數:commitInterval(自動提交offset間隔),prefetchSize(指單次從服務器批量拉取消息的大小),過大和太小都會影響性能,建議保持默認。

 

6.3 RabbitMQ

  • 根據擴展性原則,RabbitMQ自己沒有分片功能,但能夠在客戶端自行分片。
  • 如非必要狀況,應該保持默認的同步發送模式。
  • 關注消費者以下參數:autocommit(自動提交確認,默認false) ,在消息拉取到本地即認爲消費成功,而不是真正消費成功後提交。prefetchCount(預取消息條數,默認64條)
  • 生產者在必要時也能夠臨時降級不進行confirm。

 


7. 日誌

 

7.1 綜述

  • Log4j2或logback,不要再使用Log4j。
  • 除了應用啓停日誌,不容許使用超慢的System.out.println() 或 e.printStack();
  • 嚴格控制日誌量避免太高IO,對海量日誌,應該有開關能夠動態關停。
  • 若是可能出現海量異常信息,可仿效JDK的優化,用RateLimiter進行限流,丟棄過多的異常日誌。

7.2 內容

  • 嚴格控制日誌格式,避免出現消耗較大的輸出如類名,方法名,行號等。
  • 業務日誌不要濫用toJSONString()來打印對象,儘可能使用對象自身的toString()函數,由於JSON轉換的消耗並不低。
  • 在生產環境一定輸出的日誌,不要使用logger.info("hello {}", name)的模式,而是使用正確估算大小的StringBuilder直接拼裝輸出信息。

 

7.3 異步日誌

  • 同步日誌的堵塞很是嚴重,特別是發生IO的時候,所以儘可能使用異步日誌。
  • Logback的異步方案存在必定問題,須要正確配置Queue長度,閥值達到多少時丟棄Warn如下的日誌,最新版還能夠設置若是隊列已滿,是等待仍是直接丟棄日誌。
  • 若是以爲Logback的異步日誌每次插入都要詢問隊列容量太過消耗,可重寫一個直接入列,不成功則直接丟棄的版本。

 


8. 工具類

 

8.1 JSON

  • 使用Jackson 或 FastJSON。GSON的性能較前二者爲差,尤爲是大對象時。
  • 超大對象可使用Jackson或FastJSON的流式 API進行處理。
  • 將不須要序列化的屬性,經過Annotation排除掉。

FastJson:

  • 儘可能使用最新的版本。
  • SerializerFeature.DisableCircularReferenceDetect 關閉循環引用檢查。

Jackson:

  • 設置參數,不序列化爲空的屬性,等於默認值的屬性。
  • 除了jackson-databinding,可試用簡化版沒那麼多花樣的jackon-jr。

 

8.2 二進制序列化

 

須要定義IDL的PB與Thrift,不須要定義的Storm等用的Kyro 均可選擇,其餘一些比較舊就算了。

8.3 Bean複製

在VO,BO之間複製時,使用Orika(生成代碼) 或 Dozer(緩存反射),不要使用須要每次進行反射的Apache BeanUitls,Spring BeanUtils。

 

8.4 日期

JDK的日期類與字符串之間的轉換很慢且非線程安全。

繼續用Java日期不想大動做的,就用CommonsLang的FastDateFormat。

能大動做就用joda time,或者JDK8的新日期API。

 


9.Java代碼優化 與 業務邏輯優化

 

參考《Java調優指南1.8版》,對內存使用,併發與鎖等方面進行優化。

 

規則前置,將消耗較大的操做放後面,若是前面的條件不知足時可。

另外前面提到的一堆原則,好比儘可能緩存,儘可能少交互,儘可能少數據,並行,異步等,均可在此使用。

相關文章
相關標籤/搜索