Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

记一次线上优化实战

wenbochang 2019-01-23 11:27:00 阅读数:209 评论数:0 点赞数:0 收藏数:0

前言:

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

求各位老哥给一个建议。

 

版权声明
本文为[wenbochang]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/wenbochang/p/10257416.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领