记一次线上优化实战

前言:

是这样的,这周三我在测试一个接口的时候,发现竟然超时了。我们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条了,那怎么办?

求各位老哥给一个建议。

 

posted @ 2019-01-23 11:27  程序员博博  阅读(1306)  评论(2编辑  收藏  举报