坚持,你才会成功

坚持,你才会成功

前言:

是这样的,这周三我在测试一个接口的时候,发现竟然超时了。我们RPC框架用的DUBBO,我超时设置的时间为 timeout=3s。

按照道理,一个方法超过3s,对用户是非常不友好的,用户会立马会感觉是反应十分的慢。

所以进行排查 + 优化

排查一阶段:

因为这个方法中,有很多个小方法,大概如下:

记一次线上优化实战-LMLPHP

因为不知道哪个小方法执行的特比慢,所以首先想到了,计算每个小方法的时间。

于是乎写写写,写出了下面的代码

 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只会拦截这个方法,而不会拦截里面的小方法,我十分的疑惑,然后掉入了一个更大的坑中去了。

真的是一个大坑,我查了很多资料,没有特别好的解决办法。最后只能自己注入自己,然后调用即可,但正式不建议用,太影响业务逻辑了,而且主要是!!!代码太丑了!!!会被后人骂死的。

最后经过一个函数一个函数的排查。排查到了一条查询数据库语句特别特别的慢,竟然达到了3-5s,这尼玛能忍受。

排查二阶段:

我们首先看这条SQL语句

记一次线上优化实战-LMLPHP

数据库大概200w+的数据,根据条件查询大概花费了5s+,基本稳定在3s左右。条件shopId有索引,初步判断是查询出来的数据量太大了,

每个JSON字段,太占用内存了。但Mybatis逆向工程默认全部查询出来,这一点是非常不友好的,即使我只用到一两个字段

我们再看下面的这条SQL语句:

记一次线上优化实战-LMLPHP

毫秒级别,不用说了吧。而且我只需要这个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条了,那怎么办?

求各位老哥给一个建议。

01-23 17:06