zoukankan      html  css  js  c++  java
  • 记一次线上优化实战

    前言:

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

    求各位老哥给一个建议。

  • 相关阅读:
    nmp部署(Nginx Mariadb Php-fpm)
    通过能别的主机连接yum库
    基于ftp服务实现yum网络共享
    nginx做代理部署WordPress
    练习题
    php-fpm包的安装与配置
    安装mariadb并修改配置文件
    mysql的简单操作
    telnet IP:ERROR
    加固mysql服务器
  • 原文地址:https://www.cnblogs.com/wenbochang/p/10257416.html
Copyright © 2011-2022 走看看