服務端基於性能監控的優化思路
一、性能日誌定義
服務端主動列印時間超過指定閾值的介面的線程棧楨調用痕迹及耗時信息。使用性能日誌的目的是為了提前暴露系統性能問題,並為後續的性能優化提供重要指導;因此這塊需要RD主動去關注日誌,並主動去做優化,而不是在系統出現問題時才去看性能日誌。性能日誌反映的是彼時線上資源狀況下介面的耗時情況,具體問題需要具體分析,需要結合統計介面超時次數來分析。
二、性能日誌長什麼樣第一行為最外層的介面信息,即根介面的耗時情況。如上圖表示的是介面PaidupBizImpl.pageSelectByPaidupSearchDto(PaidupSearchDto ) 某次調用耗時2287ms,性能日誌閾值為2000ms(即根介面耗時超過2000ms的調用會列印性能日誌)。從第二行開始為調用棧楨的耗時信息列表,有層級關係。其中+---4 [2,249ms;2,245ms;98%;98%] - [mysql]- com.lianjia.finance.dao.PaidupMapper.selectShowDtoBySearchDto()表示該行棧楨selectShowDtoBySearchDto()開始執行時刻為4ms(相對根介面開始執行時間),結束時間為2,249ms,耗時2,245ms,耗時占父棧楨耗時的98%,占根介面總耗時的98%。可見該行棧楨即為根介面的性能瓶頸。而該條棧楨是個mysql查詢,即該問題是個mysql慢查問題。後續可以結合參數與sql做explain分析,給出具體的解決方案。三、實現原理(演示代碼)原理很簡單,使用了spring aop(aspectj) +mybatis interceptor技術,將調用鏈信息緩存到threadlocal內,根介面開始時埋初始化數據,根介面結束時埋結束數據,並判斷耗時是否超過耗時,超過則列印調用鏈信息,最後需要clear threadlocal。目前切了三個面,一個是各業務系統的com.lianjia.xx.service..,一個是dubbo 介面的filter里(監控consumer調用介面耗時信息),另一個則是mybatis的Mapper調用(監控資料庫讀寫耗時信息)。這三塊可通過簡單配置,實現日誌監控,對業務代碼侵入非常小。另如果想手動埋點,可參照com.lianjia.commons.log.performance.PerformanceAop中的作法,核心類為com.lianjia.commons.log.performance.Profiler。四、性能日誌使用(限鏈家網內部使用)1、pom依賴<dependency>n <groupId>com.lianjia.commons</groupId>n <artifactId>commons-log</artifactId>n <version>1.0.45</version>n</dependency>n
2、spring配置
a) spring-core.xml
<!-- 性能日誌-->n<bean id="performanceAop" class="com.lianjia.commons.log.performance.PerformanceAop">n <property name="threshold" value="${performance.log.threshold}"/>n</bean>n<aop:config>n <aop:aspect ref="performanceAop">n <aop:around method="doAround" pointcut="within( com.lianjia.finance.service..*) and not within(com.lianjia.finance.service.job..*) "/>n </aop:aspect>n</aop:config>n
其中${performance.log.threshold}為超時閾值,pointcut為各業務系統自定義的切面。
b) spring-mybatis.xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">n <property name="dataSource" ref="dataSource" />n <property name="mapperLocations" value="classpath:finance_mybatis/*.xml" />n <property name="plugins">n <array>n <bean class="com.github.pagehelper.PageHelper">n <property name="properties">n <value>n dialect=mysqln reasonable=truen </value>n </property>n </bean>n <bean class="com.lianjia.commons.log.performance.MybatisInterceptor"/>n </array>n </property>n</bean>n
增加了com.lianjia.commons.log.performance.MybatisInterceptor。
3、log4j2配置
<!-- 性能日誌--><RollingRandomAccessFile name="perfFile" fileName="/home/work/var/cp-finance/logs/performance.log" filePattern="/home/work/var/cp-finance/logs/%d{yyyyMMdd}/performance.log">n <Filters>n <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>n </Filters>n <PatternLayout>n <Pattern>%d{yyyyMMdd-HH:mm:ss.SSS} [%t] %-5level [$${cp:token}] - %msg%n</Pattern>n </PatternLayout>n <Policies>n <TimeBasedTriggeringPolicy />n </Policies>n</RollingRandomAccessFile>nn<!--性能日誌 --><Logger name="log.commons.performance" level="INFO" additivity="false">n <appenderRef ref="perfFile" />n</Logger>n
五、常見優化思路(先演示線上問題分析)
性能優化是服務治理里很重要的內容,優化是根據當時的服務壓力情況及資源分配情況而做的相應調整,是一個循序漸進的過程。雖然性能日誌為優化提供了重要的線索,出現性能問題的場景卻是很繁雜,一種優化方案在某場合可能會提升性能,而另一種場合確成了性能瓶頸,因此具體問題要具體分析,結合上下文來分析,最終找到解決方案。另建議初級開發人員多做這類的分析及優化,過程中能快速提升內功。下面整理了一些常見的場景下的性能優化思路。
1、使用批量介面
開發中經常遇到for循環中作網路調用,形如:
for(int i=0;i<list.size;i++){
dubbo.consumerMethod(..);}for(int i=0;i<list.size;i++){
mysql.mapperUpdateMethod(..);}for(int i=0;i<list.size;i++){
redis.set(..);}
網站io都ms級別的,假設一次網路調用需要2ms,1000次就是 2*1000=2s(這還沒算服務端的開銷)。因此RD在寫出這樣的代碼時一定要警惕,循環次數會不會超過50,超過了就不能這麼寫。走批量介面。redis有multi set ,dubbo調用得要求服務端提供批量介面,mysql也支持批量操作。
2、mysql/mongodb 等B+樹 慢sql查詢
a) 熟用explainb) 聯合索引、最左前綴匹配c) 深分頁問題 d) 讀寫分離、分庫分表(cobar mycat ) 、數據遷移3、使用緩存
讀多寫少,高並發,緩存命中率高且滿足業務,可使用緩存技術(建議不要使用本地cache)。緩存設計時,粒度不宜過大,多個介面能復用數據,活用緩存策略,努力提升命中率。 緩存代碼的位置也有講究,一般有三種方案:
(1)緩存代碼加在client.jar(咱們這邊叫api.jar)中,優點:對provider保護最好,緩存命中的請求不會打到provider機器上,不僅減少網路開銷,也減輕了provider的壓力;缺點:provider開發人員需要維護client代碼,且升級時會有溝通成本。有的場景加緩存是起不到作用的,反而會增加一次網路開銷,我們需要關注緩存命中率,太低或存在緩存穿透都是需要調整的。
4、同步改非同步(1)獨立模塊非同步化 (2)系統間交互使用MQ替代同步調用 5、減少鎖競爭,鎖的粒度盡量小;線程不宜開太多,避免頻繁的上下文切換6、復用資源對象,避免重複創建、銷毀的開銷。
7、其它
推薦閱讀: