記一次線上優化實戰


前言:

是這樣的,這周三我在測試一個接口的時候,發現竟然超時了。我們RPC框架用的DUBBO,我超時設置的時間為 timeout=3s。

按照道理,一個方法超過3s,對用戶是非常不友好的,用戶會立馬會感覺是反應十分的慢。

所以進行排查 + 優化

排查一階段:

因為這個方法中,有很多個小方法,大概如下:

因為不知道哪個小方法執行的特比慢,所以首先想到了,計算每個小方法的時間。

於是乎寫寫寫,寫出了下面的代碼

 1 @Aspect
 2 @Component
 3 @Slf4j
 4 public class TimeWatchAspect {
 5 
 6     @Pointcut(value = "execution(* com.tianya..*(..))")
 7     public void log() {
 8 
 9     }
10 
11     @Around(value = "log()")
12     public void test(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
13         Stopwatch stopwatch = Stopwatch.createStarted();
14         log.info("開始計算時間");
15         proceedingJoinPoint.proceed();
16         long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
17         log.info("結束計算時間, 花費時間為:{}", duration);
18     }
19 }

一個簡單的AOP,攔截所有請求,並計算方法。

我以為大事萬吉了。沒想到!!!

該AOP只會攔截這個方法,而不會攔截里面的小方法,我十分的疑惑,然后掉入了一個更大的坑中去了。

這個我稍微說下,為啥這是一個大坑。

Spring的AOP攔截,其實是為這個類生成了代理類。比如類A,為類A生成了一個ProxyA類。然后為每個方法加上AOP的before,AOP的after等等

但一個方法中調用另一個方法其實是:this.x();

那么假設代碼如下:

 1 class A {
 2     public void a() {
 3         System.out.println("我是a方法");
 4         b();
 5     }
 6 
 7     private void b() {
 8         System.out.println("我是b方法");
 9     }
10 }
11 
12 class ProxyA {
13     public void  a() {
14         aopBefore();
15         System.out.println("我是a方法");
16         A.b();
17         aopAfter();
18     }
19 }

看懂了嘛?a方法里面的b方法,調用的還是舊的A類中的b方法,所以AOP不會進行一個攔截。

真的是一個大坑,我查了很多資料,沒有特別好的解決辦法。最后只能自己注入自己,然后調用即可,但正式不建議用,太影響業務邏輯了,而且主要是!!!代碼太丑了!!!會被后人罵死的。

最后經過一個函數一個函數的排查。排查到了一條查詢數據庫語句特別特別的慢,竟然達到了3-5s,這尼瑪能忍受。

排查二階段:

我們首先看這條SQL語句

 

 

數據庫大概200w+的數據,根據條件查詢大概花費了5s+,基本穩定在3s左右。條件shopId有索引,初步判斷是查詢出來的數據量太大了,

每個JSON字段,太占用內存了。但Mybatis逆向工程默認全部查詢出來,這一點是非常不友好的,即使我只用到一兩個字段

 

我們再看下面的這條SQL語句:

 

毫秒級別,不用說了吧。而且我只需要這個id字段即可,不需要其他的字段。既然找到了原因那么就動手解決它吧。

 

解決:

 

自己新建一個Mapper文件,如下即可:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 3 <mapper namespace="com.tianya.items.mapper.ItemsMapperSpeed">
 4 
 5     <!-- 慢sql優化 -->
 6     <resultMap id="ShopMap" type="java.util.HashMap">
 7         <id column="id" jdbcType="INTEGER" property="id" />
 8     </resultMap>
 9     <select id="selectItemIdByShopId" parameterType="com.tianya.items.entity.ItemsExample" resultMap="ShopMap">
10         select id from items
11         <if test="_parameter != null">
12             <include refid="Example_Where_Clause" />
13         </if>
14         <if test="orderByClause != null">
15             order by ${orderByClause}
16         </if>
17         <if test="limit != null">
18             <if test="offset != null">
19                 limit ${offset}, ${limit}
20             </if>
21             <if test="offset == null">
22                 limit ${limit}
23             </if>
24         </if>
25     </select>
26 </mapper>

 

我們看到xml文件的第10行,就是只把id查出來,而不查詢全部。優化開始了

然后建立一個與Mapper映射的類

 

1 public interface ItemsMapperSpeed {
2     /**
3      * 這個屬於慢sql優化 對應ItemsMapperSpeed.xml文件
4      * @yizhen
5      * @return
6      */
7     List<Map<String, Object>> selectItemIdByShopId(ItemsExample example);
8 }

最后直接調用即可

 

結果:

優化前基本穩定在5s+,優化后基本穩定在1-2s+。還是太慢了。原因大概有以下幾個原因:

1:數據庫表設計太多。大概涉及到了5張表左右,其中2張表達到了200w+的級別,3張表達到了20W+的級別

2:其中很多處業務都需要查表,每次查表都是查詢出全部字段,而不是特定的字段,查詢十分的慢

3:業務復雜。后期排查代碼大覺一個方法里面調用了兩三個方法,兩三個方法又調用了兩三個方法。時間復雜度基本在O(N^2)級別,個別方法甚至達到了O(N^3),這點是非常不好的

4:自己代碼寫的太搓了,挫的一批

 

優化思路:

如果這個接口特別的慢,我主要會從以下思路進行優化:

1:SQL層面 ==> SQL語句搓,進行SQL語句的優化。實在不行,加一個緩存,過期一般60s即可,會快特比的多。特別建議ehcache比較好用

2:代碼層面 ==> 類似HashMap, HashSet都可以達到O(1)級別的時間復雜度,可以多考慮用空間換時間的策略。

3:代碼盡量寫工整易懂,導師天天說我代碼寫的搓。也是很不錯的一個優化

 

后記:

問大家一個問題,就是我現在的業務場景。

一個按照條件搜索的需求,涉及的表大概有5-6張,每張表大概達到了百萬的級別,按照條件進行篩選查找。

這種肯定是不能用join的,那么怎么做效率才比較高呢?

如果我不從每張表把數據取出來,最后按照其他條件篩選,那么可能篩選都不符合0條了,那怎么辦?

求各位老哥給一個建議。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM