Java接口全鏈路優化:如何下降接口RT時長

背景:因爲之前的應用多且雜,因此最近對公司的應用進行優化改造,須要全部接口RT達到xxx值如下。java

1、監控

那麼問題來了~如今應用都是放養式的,幾乎沒有什麼監控工具,不可能根據log一個接口一個接口去撈日誌,那怎麼知道哪些接口rt長,須要優化呢。 因此第一步咱們作的事情就是上監控。git

監控工具:pinpoint。程序員

選擇pinpoint有幾個方面的考量:github

1.對應用代碼0侵入,這個固然是咱們程序員最關心的,誰都不喜歡由於附加功能在本身的應用大動干戈,萬一影響原有業務就不值得了。。算法

2.應用依賴關係,調用關係一目瞭然,經過一個traceId將全部流程串起來,方便排查問題。這一點也是很是重要,在分佈式系統中,調用關係請求太複雜,若是沒有traceId標識,你是很難找到這個請求的上層調用關係的。sql

3.接口各類監控圖表,固然也包括RT圖表,便於快速定位須要優化的鏈路。數據庫

pinpoint的源碼及教程可參考https://github.com/naver/pinpoint。編程

咱們能夠大體看一下pinpoint的監控頁面,以下圖緩存

從圖中咱們能得出:

調用鏈路一列看出該請求實際執行了2次SQL。併發

執行時間一列看出第一次耗時19ms,第二次722ms。

traceId是這次請求的惟一標識,若是咱們在應用須要記錄該值,也可經過對應API獲取。

固然這個示例只是一次很簡單的請求,他還能監控dubbo調用、http請求、Redis、db等等操做,因此只要應用配置了它,那麼該請求執行的調用細節就一目瞭然,盡在咱們掌握之中,而不是放養任其發展。

2、優化

既然目標已經被咱們揪出來了,那咱們下一步固然就是把它解決掉,優化它。我從優化過程當中得出一些很廣泛,實用的優化經驗,特別是對於初學者,能夠多多參考。由於對於剛開始編程的來講這些就是習慣,正常的邏輯,但並不表明它就是最優的。

1.循環SQL操做

這類狀況對於新手來講很容易犯,好比就建立交易訂單。咱們主子單關係以下:

那麼建立訂單須要生成1個主單,N個子單,這時不少這麼寫(代碼僅表示執行流程):

insert(主單);
for(子單:子單列表){
    insert(子單);
}
複製代碼

因此就出現循環SQL操做,由於SQL操做比較耗時,循環的話就會大大拉大整個接口的rt時長,這種狀況咱們應該一次性把全部子單insert,而不是一次一次地操做,優化後代碼相似爲這樣:

insert(主單);
batchInsert(子單列表);
複製代碼

Mybatits是支持循環標籤的,因此在sqlMap文件裏改造一下SQL就能夠了。另外批量update也是能夠的,執行批量操做須要在數據庫連接加上參數allowMultiQueries=true

2.數據庫索引

索引固然是必需要建的,否則得查到何時。不過建歸建,可是咱們還要正確的運用它。 咱們能夠經過explain命令檢測SQL是否使用索引。 EXPLAIN SELECT * FROM article WHERE id=10;

key和rows反應了使用的索引以及預計須要掃描的行數。 還有一些咱們須要注意的:

在咱們優化Query語句中的ORDER BY的時候,儘量利用已有的索引來避免實際的排序計算,能夠很大幅度的提高 ORDER BY操做的性能。在有些 Query 的優化過程當中,即便爲了不實際的排序操做而調整索引字段的順序,甚至是增長索引字段也是值得的 由於MySQL中,order by的實現有兩種類型:

1).一種是經過有序索引而直接取得有序的數據,這樣不用進行任何排序操做便可獲得知足客戶端 要求的有序數據返回給客戶端;

2).一種則須要經過 MySQL 的排序算法將存儲引擎中返回的數據進行排序而後再將排序後的數 據返回給客戶端

咱們建的索引爲 user_id, define_id, seq聯合索引,能夠看出第一張圖排序define_id直接走索引,第二張圖走不了索引須要額外的排序操做Using filesort.group by 其實也是進行了order by操做 而後進行分組,因此group by也是相似的優化方式。

DISTINCT儘可能少用,DISTINCT其實也是進行了一次group by操做,而後每一組取的第一條記錄。

java應用的類型必定和數據庫索引列的類型匹配,例如java類型爲long,數據庫類型爲varchar,這樣去查詢是用不了索引的,可是不會報錯。 下圖是兩種方式的對比:

3.count計數千萬不能用

計數也是咱們常常用到的,好比優惠券領取數量,統計活動參加人數。這些有時是和業務強耦合的。好比秒殺只能賣出多少件等等。這類場景咱們千萬不能使用count來計數。咱們來分析一下爲何不使用count:

1).count會查詢全部知足條件的記錄,若是表很是大,這將可能致使全表掃描,這後果你們都知道的吧。 2).若是使用count來控制,那麼在業務邏輯執行期間,確定要加鎖,不然剛纔count的結果就白操做了,這將也會阻止全部對該活動的請求。

這種場景咱們通常都是在須要控制的記錄上加個計數字段,好比控制最大領取值num=10個。那在業務邏輯裏面可將對總數控制轉爲經過SQL的方式, update xxx set num=num-1 where id=xx and num>0; 這是原子操做,可保證必定不會超領。根據返回結果判斷是否執行成功(返回結果爲影響的行數)。

4.鎖

單機鎖和分佈式鎖,鎖其實咱們可以避免就不要使用它,由於加鎖就表明只能串行執行,併發數降爲1,這確定影響性能。

若是是相似庫存扣減的場景,可參考第3條。經過數據庫的原子操做來避免。
若是是更新等操做,可經過樂觀鎖來避免長時間的阻塞。
若是非要使用鎖,不論是單機鎖仍是分佈式鎖,咱們必定要評估該鎖的影響範圍,是針對單個用戶userId仍是全部的用戶userId,單個用戶是能夠採用的,由於單個用戶併發度極低,可是若是是全部用戶的操做加鎖,那必定要好好評估,這個操做會致使全部用戶的相似操做阻塞。

5.適當冗餘

在分佈式系統中,冗餘應該是很是常見的狀況。咱們這個時候就不要追求數據庫範式的標準了,由於按照數據庫第幾範式來設計,全部字段都不冗餘,可是這給咱們查詢帶來很大麻煩。可能咱們查一個訂單,須要關聯查詢,查子單信息,查商品信息,查支付信息等等,查詢這麼多,想一想咱們接口的rt能快嗎,qps能高嗎。將商品名稱等基本信息冗餘可減小對其實模塊的查詢,何嘗不是一種好的方式。

6.Redis緩存

其實緩存在咱們系統廣泛都用到了,因此對這部分優化很少。仍是總結一些經驗。 緩存的刷新策略選擇:失效刷新仍是定時刷新。 由於監控到不少接口RT老是有規律的變慢,這是由於都是在緩存失效的時候,須要從db及其餘模塊組裝數據,而後推到緩存,這時全部請求都走不了緩存,在流量大的時候也有可能成爲致命的因素。若是是這種狀況,例如首頁推薦商品、推薦帖子等等訪問量大且相同的場景能夠經過定時刷新的方式。 Keys*命令線上嚴禁使用:Redis是單線程,該命令的執行將會致使全部後續請求阻塞,影響整個系統性能。

7.搜索引擎

可能看到這個比較疑惑,搜索引擎怎麼還能優化RT,他比DB固然慢,可是在某些場景他能夠比DB更快。例如一個社區論壇,須要對文章進行篩選排序,總共十來個字段,並且自由組合,這時DB就無能爲力,由於條件太多,各類排序操做,關聯操做,數據庫沒辦法建索引。咱們能夠經過將A、B表中的字段構建成完整的信息,推送到搜索引擎,查詢的時候直接根據條件搜索。這樣既能保證rt,又能避免DB被複雜查詢拖垮。

相關文章
相關標籤/搜索