一份平民化的应用性能优化检查列表(完整篇)--转
原文地址:http://calvin1978.blogcn.com/articles/checklist.html
1.總原則
一些正確但稍顯廢話的原則,但能指導(dǎo)后面每個章節(jié)的優(yōu)化,所以還是要啰嗦一次。
?
2.環(huán)境準備
保證符合自家各種規(guī)范(沒有的話趕緊回家寫一個),尤其線下壓測服務(wù)器的配置要與生產(chǎn)環(huán)境一致。
2.1 操作系統(tǒng)
- 自家規(guī)范調(diào)優(yōu)應(yīng)包含TCP內(nèi)核參數(shù),網(wǎng)卡參數(shù)及多隊列綁定,IO&Swap內(nèi)核參數(shù),ulimit資源限制等。
2.2 JVM與應(yīng)用服務(wù)器
- 使用JDK7.0 u80 或 JDK8 最新版。
- 檢查JVM啟動參數(shù)已按自家規(guī)范調(diào)優(yōu),見《關(guān)鍵業(yè)務(wù)系統(tǒng)的JVM參數(shù)推薦》
- 檢查應(yīng)用服務(wù)器(Tomcat或微服務(wù)容器) 已按自家指南調(diào)優(yōu),如線程數(shù)等。
2.3 周邊依賴系統(tǒng)
- 檢查數(shù)據(jù)庫,緩存,消息系統(tǒng),已按自家指南調(diào)優(yōu)。
2.4 后臺輔助程序
- 檢查日志收集,系統(tǒng)監(jiān)控等,已使用最新版本,最優(yōu)配置。
- 最好其最大消耗已被控制(通過cgroup,taskset等方式)。
2.5 測試程序
- 壓測工具如JMeter,啟動參數(shù)要參考真實應(yīng)用客戶端的參數(shù)優(yōu)化(如JVM參數(shù),Netty參數(shù)等)。
- 測試腳本和客戶端程序經(jīng)過review,不存在影響性能的步驟,不存在System.out.println()等明顯的瓶頸。
2.5 流量模型
- 扇入模型:平時與高峰期的流量估算,各接口的流量比例,響應(yīng)時間要求
- 扇出模型:各接口對遠程服務(wù)、數(shù)據(jù)庫、緩存、消息系統(tǒng)的調(diào)用比例,響應(yīng)時間估算。
?
大家在心里都有這么一個大概的模型,但很少認真寫出來。
行文到此,大家大概可以感受到這份checklist的風(fēng)格,都是大家明白的道理,但可能一時也會忘掉的,這里啰啰嗦嗦的給寫下來。
3.數(shù)據(jù)庫
特別鳴謝,我司DBA。
3.1 拓撲
根據(jù)擴展性原則考慮:
- 垂直拆分:按業(yè)務(wù)將不同的表拆分到不同的庫。
- 水平拆分:水平分庫分表。
- 讀寫分離:在業(yè)務(wù)允許的情況下,在從庫讀取非實時數(shù)據(jù)。
3.2 Schema
自家規(guī)范應(yīng)包含:
- 統(tǒng)一的存儲引擎,主鍵策略。
- 禁用存儲過程,函數(shù),觸發(fā)器,外鍵約束。
- 列類型永遠越短越好,建議:布爾/枚舉:tinyint,日期與時間戳:timestamp或int,char/text/blob: 盡量用符合實際長度的varchar(n),小數(shù)及貨幣:移位轉(zhuǎn)為int 或 decimal,IP地址:int。
- 索引策略:索引字段的順序需要考慮字段值去重之后的個數(shù),較多的放前面,合理創(chuàng)建聯(lián)合索引,避免冗余索引,合理利用覆蓋索引等。
?
3.3 SQL
1. 自家規(guī)范應(yīng)包含:
- 如禁止多于3表join,禁用子查詢
- 禁止where子句中對字段施加函數(shù),如to_date(add_time)>xxxxx
- 避免MySQL進行隱式類型轉(zhuǎn)化,如ISENDED&eq;1 與 ISENDED&eq;`1`
- 不建議使用%前綴模糊查詢,模糊查詢較多時建議使用ElasticSearch
?
根據(jù)盡量少數(shù)據(jù)原則與盡量少交互的原則來設(shè)計SQL:
- 禁止select?*
- 合理的SQL語句,減少交互次數(shù)
根據(jù)擴展性原則,將負載放在更容易伸縮的應(yīng)用服務(wù)實例上:
- 盡量不要做數(shù)學(xué)運算,函數(shù)運算, 或者輸出格式轉(zhuǎn)換等非必要操作
- 避免count(*),計數(shù)統(tǒng)計實時要求較強使用memcache或者redis,非實時統(tǒng)計使用單獨統(tǒng)計表,定時更新。
- 甚至排序都是不鼓勵的,盡量在應(yīng)用側(cè)進行。另外避免多余的排序,使用GROUP BY 時,默認會進行排序,當(dāng)你不需要排序時,可以使用order by null。
2.?聯(lián)系DBA進行MySQL統(tǒng)計的慢查詢的Review,解析SQL查詢計劃時盡量避免extra列出現(xiàn):Using File Sort,Using Temporary
3.4 DAO框架
- 根據(jù)盡量少交互與盡量少數(shù)據(jù)的原則,需使用對SQL完全可控的DAO框架,建議為MyBatis 或 Spring JDBC Template。
- 必須使用prepareStatement,提升性能與防注入。
- 根據(jù)一切皆有超時的原則,配置SQL執(zhí)行的超時。可在連接池里設(shè)置default值,可在MyBatis的Mapper定義里可設(shè)置每個請求的超時,可惜規(guī)范是秒級的。
- JDBC driver 規(guī)范本身不支持異步模式,如果一定要異步,可以像Quasar那樣把請求封裝成Callable交給另外的線程池執(zhí)行,但要注意其額外開銷。
?
3.5 事務(wù)
- 不使用事務(wù),連接池設(shè)置autocommit,使用其他方式來保持數(shù)據(jù)一致性。
- 通過Transaction?Annotation控制事務(wù),事務(wù)跨度盡量短,把非事務(wù)范圍內(nèi)的業(yè)務(wù)邏輯剔除到被標注的函數(shù)之外。
- 只讀事務(wù)可以不加事務(wù)標注。
?
連接池
選型:
- 在分庫分表時,根據(jù)點對點通信優(yōu)先的原則,盡量使用客戶端分片的實現(xiàn)。功能不滿足時才用MyCat中央代理。
- 推薦使用性能最高HikariCP,或者Druid,不推薦c3p0與DBCP。
?
連接池的配置:
- 配置初始值,再聯(lián)系DBA獲得線上數(shù)據(jù)庫支持的連接數(shù),計算最大連接數(shù)。
- 連接有效性檢查,只在連接空閑檢測時執(zhí)行,不在拿出和歸還連接時執(zhí)行,最好是直接使用數(shù)據(jù)的Ping方案,不要配置檢查SQL。
- 根據(jù)總是設(shè)置超時的原則,配置獲取連接超時的時間。
- 配置合理的空閑連接回收間隔和空閑時間。
番外篇:在分庫分表時,可考慮基于HikariCP二次開發(fā),減少總的空閑連接檢查線程數(shù)(比如128個分區(qū),可能有256條線程),重用同一個實例上的庫的連接等。
4.緩存
?
4.1 多級緩存
?
- 根據(jù)緩存原則, 緩存 > 數(shù)據(jù)庫/遠程調(diào)用
- 根據(jù)就近原則, 堆內(nèi)緩存 > 堆外緩存 > 集中式緩存
- 堆內(nèi)緩存受大小限制,并影響GC
- 堆內(nèi)緩存與堆外緩存,分布在每一臺應(yīng)用服務(wù)器上,刷新方式比集中式緩存復(fù)雜
- 堆外緩存與集中式緩存,需要序列化/反序列化對象
- 集中式緩存,有網(wǎng)絡(luò)傳輸?shù)某杀?#xff0c;特別是數(shù)據(jù)超過一個網(wǎng)絡(luò)包的大小。
- 集中式緩存,一次獲取多個鍵時,在有分區(qū)的情況下,需要收發(fā)多個網(wǎng)絡(luò)包。
使用上述條件選擇合適的緩存方案,或同時使用多級緩存,逐層回源。
?
4.2 綜述
- 需要對回源進行并發(fā)控制,當(dāng)key失效時,只有單一線程對該key回源。
- 基于二進制優(yōu)于文本數(shù)據(jù)的原則,JSON的序列化方案較通用與更高的可讀性。而對于較大,結(jié)構(gòu)較復(fù)雜的對象,基于Kyro,PB,Thrift的二進制序列化方案的性能更高,見后面的序列化方案部分。
?
4.3 堆內(nèi)緩存
選型:
- 推薦Guava Cache。
- Ehcache較重,性能也較差。更不要使用存在嚴重bug的Jodd Cache。
GuavaCache:
- 正確設(shè)置并行度等參數(shù)。
- 重載load()參數(shù),實現(xiàn)單一線程回源。
- Guava Cache能后臺定時刷新,在刷新的過程中,依然使用舊數(shù)據(jù)響應(yīng)請求,不會造成卡頓,但需要重載實現(xiàn)reload()函數(shù)。
- Guava Cache同時還支持并發(fā)安全版的WeakHashMap。
?
4.4 堆外緩存
選型:
- 推薦Cassandra的OHC 或者 OpenHFT的Chronical map2。
- OHC夠簡單,其實R大不喜歡Chronical,玩的太深,換個JDK都可能跑不起來。
- Chronical map3的license則較不友好,復(fù)雜度高且要求JDK8。
- 其他的Ehcache的Terracota Offheap 一向不喜歡。
4.5 Memcached
?客戶端:
- 基于點對點通信優(yōu)于網(wǎng)關(guān)的原則,使用客戶端一致性哈希分區(qū)。
- 推薦Spymemcached。 XMemcached 太久沒更新,Folsom知名度不高。
- 注意Spymemcached為單線程單連接架構(gòu)(一個MemcachedClient只有一條IO線程,與每臺Memcached只有一條連接),必要時可多建幾個MemcachedClient隨機選擇,但不要用Commons Pool去封裝它,把Spy原本的設(shè)計一筆抹殺。
- 根據(jù)在合適場景使用并發(fā)的原則,Spymemcached支持異步API。
- 根據(jù)一切皆設(shè)超時的原則,可在連接工廠中設(shè)置最大超時數(shù),默認值兩秒半太長。
?
數(shù)據(jù)結(jié)構(gòu):
- Key必須設(shè)置失效時間。
- Key必須有長度限制。
- Value長度需要控制,以不超過1個網(wǎng)絡(luò)包(MTU,千五字節(jié))為佳。
- Value大小差別較大的緩存類型,建議拆分到不同MC集群,否則會造成低使用率并且產(chǎn)生踢出。
?
4.6 Redis as Cache
?
Redis拓撲:
基于點對點通信優(yōu)于網(wǎng)關(guān)的原則,使用如下兩種拓撲
- 無HA的普通分片:由Jedis客戶端完成分片路由。
- Redis Cluster:同樣由Jedis客戶端封裝分區(qū),跳轉(zhuǎn),重試等邏輯,需要使用最新版的Jedis版本。
?
服務(wù)端:
- Cache節(jié)點與持久化數(shù)據(jù)節(jié)點不要混用。
- Cache節(jié)點是否需要持久化要仔細衡量。
- 由于Redis是單線程,使用taskset進行cpu綁定后可以有效地利用cpu,并在單機上運行多個redis實例。
- 對熱鍵進行監(jiān)控,發(fā)現(xiàn)不合理的熱健要進行分拆等處理。
客戶端:
- Jedis基于Apache Commons Pool進行了多連接的封裝,正確配置總連接數(shù)不超過Redis Server的允許連接數(shù)。
- 性能考慮,空閑連接檢查不要過于頻繁(建議30秒以上),另不要打開testOnBorrow等測試參數(shù)。
- 根據(jù)一切皆有超時的原則,設(shè)定統(tǒng)一的調(diào)用超時,獲取連接的最長等待時間參數(shù),重試次數(shù)
- 根據(jù)在合適的地方異步的原則,Jedis本身沒有異步API,只在PipleLine模式下支持。
數(shù)據(jù)結(jié)構(gòu):
- 必須對Key設(shè)置失效時間。
- Key必須有長度限制。
- Value長度需要控制,不要超過一個網(wǎng)絡(luò)包。另外集合的元素不要超過五千個。
- 除了使用序列化的String,同樣可以考慮用Hash來存儲對象,注意內(nèi)部結(jié)構(gòu)為ZipList與HashTable時,hmget?與hgetall的不同復(fù)雜度。
命令:
- 慎用的命令:LANGE(0, -1), HGETALL, SMEMBER
- 高復(fù)雜度的命令: ZINTERSTORE, SINTERSTORE, ZUNIONSTORE, ZREM
- 盡量使用多參數(shù)的命令:MGET/MSET,HMGET/HMSET, LPUSH/RPUSH, LRANGE
- 盡量使用pipeline
- 根據(jù)減少交互的原則,必要時可使用Redis的Lua腳本
?
5.服務(wù)調(diào)用
5.1 接口設(shè)計
1. 盡量少交互的原則:
支持批量接口,最大的批量,綜合考慮調(diào)用者的需求與 后端存儲的能力。
支持粗粒度接口,在支持原子細粒度接口的同時,支持粗粒度接口/聚合層接口,將多個數(shù)據(jù)源的獲取,多個動作,合并成一個粗粒度接口。
?
2. 盡量少數(shù)據(jù)的原則:
在提供返回所有數(shù)據(jù)的大接口的同時,提供只提供滿足部分調(diào)用者需要的輕量接口。
最好再提供能定制返回字段的接口。
?
3. 二進制數(shù)據(jù)優(yōu)于文本數(shù)據(jù)
同樣是一個簡單通用性,與性能的選擇,特別是大數(shù)據(jù)量時。
?
5.2 RESTful
僅以Apache HttpClient為例,大部分Restful框架都是對Apache HttpClient的封裝。
另外OkHttp也值得看看。
?
- 不要重復(fù)創(chuàng)建ApacheClient實例,使用連接池,正確配置連接池的連接數(shù)。
- 連接池總是有鎖,針對不同的服務(wù),使用不同的Apache HttpClient實例,將鎖分散開來。在高并發(fā)時比使用全局單例的ApacheClient,有很大的性能提升。
- 根據(jù)一切調(diào)用皆有超時的原則,每次調(diào)用均設(shè)置超時時間。RequestConfig里共有Connect Timeout, Socket Timout 和 從Pool中獲取連接的Timeout三種超時。
- 需要異步或并行的場景,使用Apache AsyncHttpClient項目。但要注意AsyncHttpClient項目,檢查調(diào)用超時的周期默認為1秒。
?
5.3 自家RPC框架
?
每家的RPC框架特性不同,但考慮點都類似。
?
?
6.消息異步
?
6.1 選型
- 根據(jù)就近原則,可以先嘗試用JVM內(nèi)的隊列來解決,然后再考慮中央消息系統(tǒng)。
- 可靠性要求極高的選擇RabbitMQ,可支持單條消息確認。
- 海量消息場景,允許極端情況下少量丟失則使用Kafka。
?
6.2 Kafka
- 在同步和異步之間做好權(quán)衡,異步批量發(fā)送可以極大的提高發(fā)送的速度。
- 關(guān)注消費者如下參數(shù):commitInterval(自動提交offset間隔),prefetchSize(指單次從服務(wù)器批量拉取消息的大小),過大和過小都會影響性能,建議保持默認。
?
6.3 RabbitMQ
- 根據(jù)擴展性原則,RabbitMQ本身沒有分片功能,但可以在客戶端自行分片。
- 如非必要情況,應(yīng)該保持默認的同步發(fā)送模式。
- 關(guān)注消費者如下參數(shù):autocommit(自動提交確認,默認false) ,在消息拉取到本地即認為消費成功,而不是真正消費成功后提交。prefetchCount(預(yù)取消息條數(shù),默認64條)
- 生產(chǎn)者在必要時也可以臨時降級不進行confirm。
?
7. 日志
?
7.1 綜述
- Log4j2或logback,不要再使用Log4j。
- 除了應(yīng)用啟停日志,不允許使用超慢的System.out.println() 或 e.printStack();
- 嚴格控制日志量避免過高IO,對海量日志,應(yīng)該有開關(guān)可以動態(tài)關(guān)停。
- 如果可能出現(xiàn)海量異常信息,可仿效JDK的優(yōu)化,用RateLimiter進行限流,丟棄過多的異常日志。
7.2 內(nèi)容
- 嚴格控制日志格式,避免出現(xiàn)消耗較大的輸出如類名,方法名,行號等。
- 業(yè)務(wù)日志不要濫用toJSONString()來打印對象,盡量使用對象自身的toString()函數(shù),因為JSON轉(zhuǎn)換的消耗并不低。
- 在生產(chǎn)環(huán)境必定輸出的日志,不要使用logger.info("hello?{}", name)的模式,而是使用正確估算大小的StringBuilder直接拼裝輸出信息。
?
7.3 異步日志
- 同步日志的堵塞非常嚴重,特別是發(fā)生IO的時候,因此盡量使用異步日志。
- Logback的異步方案存在一定問題,需要正確配置Queue長度,閥值達到多少時丟棄Warn以下的日志,最新版還可以設(shè)置如果隊列已滿,是等待還是直接丟棄日志。
- 如果覺得Logback的異步日志每次插入都要詢問隊列容量太過消耗,可重寫一個直接入列,不成功則直接丟棄的版本。
?
8. 工具類
?
8.1 JSON
- 使用Jackson 或 FastJSON。GSON的性能較前兩者為差,尤其是大對象時。
- 超大對象可以使用Jackson或FastJSON的流式 API進行處理。
- 將不需要序列化的屬性,通過Annotation排除掉。
FastJson:
- 盡量使用最新的版本。
- SerializerFeature.DisableCircularReferenceDetect 關(guān)閉循環(huán)引用檢查。
Jackson:
- 設(shè)置參數(shù),不序列化為空的屬性,等于默認值的屬性。
- 除了jackson-databinding,可試用簡化版沒那么多花樣的jackon-jr。
?
8.2 二進制序列化
?
需要定義IDL的PB與Thrift,不需要定義的Storm等用的Kyro 都可選擇,其他一些比較舊就算了。
8.3 Bean復(fù)制
在VO,BO之間復(fù)制時,使用Orika(生成代碼) 或 Dozer(緩存反射),不要使用需要每次進行反射的Apache BeanUitls,Spring BeanUtils。
?
8.4 日期
JDK的日期類與字符串之間的轉(zhuǎn)換很慢且非線程安全。
繼續(xù)用Java日期不想大動作的,就用CommonsLang的FastDateFormat。
能大動作就用joda time,或者JDK8的新日期API。
?
9.Java代碼優(yōu)化 與 業(yè)務(wù)邏輯優(yōu)化
?
參考《Java調(diào)優(yōu)指南1.8版》,對內(nèi)存使用,并發(fā)與鎖等方面進行優(yōu)化。
?
規(guī)則前置,將消耗較大的操作放后面,如果前面的條件不滿足時可。
另外前面提到的一堆原則,比如盡量緩存,盡量少交互,盡量少數(shù)據(jù),并行,異步等,都可在此使用。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/6932852.html
總結(jié)
以上是生活随笔為你收集整理的一份平民化的应用性能优化检查列表(完整篇)--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微服务实战(七):从单体式架构迁移到微服
- 下一篇: 美团配送资金安全治理之对账体系建设