前言

Java技能突击网站:www.susan.net.cn

分类树查询功用,在各个事务体系中能够说随处可见,特别是在电商体系中。

分类树,我从2s优化到0.1s
但便是这样一个简单的分类树查询功用,咱们却优化了5次。

到底是怎么回事呢?

背景

咱们的网站运用了SpringBoot推荐的模板引擎:Thymeleaf,进行动态渲染。

它是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。

它供给了一个用于整合SpringMVC的可选模块,在应用开发中,咱们能够运用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity\FreeMarker等。

前端开发写好Thymeleaf的模板文件,调用后端接口获取数据,进行动态绑定,就能把想要的内容展现给用户。

由于当时这个是从0-1的新项目,为了开快速开发功用,咱们第一版接口,直接从数据库中查询分类数据,组装成分类树,然后回来给前端。

经过这种方法,简化了数据流程,快速把整个页面功用调通了。

第1次优化

咱们将该接口布置到dev环境,刚开始没啥问题。

跟着开发人员添加的分类越来越多,很快就暴露出功能瓶颈。

咱们不得不做优化了。

咱们第一个想到的是:加Redis缓存

流程图如下:

分类树,我从2s优化到0.1s
所以暂时这样优化了一下:

  1. 用户拜访接口获取分类树时,先从Redis中查询数据。
  2. 假如Redis中有数据,则直接数据。
  3. 假如Redis中没有数据,则再从数据库中查询数据,拼接成分类树回来。
  4. 将从数据库中查到的分类树的数据,保存到Redis中,设置过期时刻5分钟。
  5. 将分类树回来给用户。

咱们在Redis中界说一个了key,value是一个分类树的json格式转化成了字符串,运用简单的key/value方式保存数据。

经过这样优化之后,dev环境的联谐和自测顺畅完成了。

第2次优化

咱们将这个功用布置到st环境了。

刚开始测验同学没有发现什么问题,但跟着后边不断地深化测验,隔一段时刻就呈现一次主页拜访很慢的情况。

所以,咱们立刻进行了第2次优化。

咱们决定运用Job定时异步更新分类树到Redis中,在体系上线之前,会先生成一份数据。

当然为了稳妥起见,避免Redis在哪条突然挂了,之前分类树同步写入Redis的逻辑仍是保存。

所以,流程图改成了这样:

分类树,我从2s优化到0.1s
增加了一个job每隔5分钟执行一次,从数据库中查询分类数据,封装成分类树,更新到Redis缓存中。

其他的流程保持不变。

此外,Redis的过期时刻之前设置的5分钟,现在要改成永久。

经过这次优化之后,st环境就没有再呈现过分类树查询的功能问题了。

第3次优化

测验了一段时刻之后,整个网站的功用快要上线了。

为了稳妥起见,咱们需求对网站主页做一次压力测验。

公然测出问题了,网站主页最大的qps是100多,最终发现是每次都从Redis获取分类树导致的网站主页的功能瓶颈。

咱们需求做第3次优化。

该怎么优化呢?

答:加内存缓存。

假如加了内存缓存,就需求考虑数据一致性问题。

内存缓存是保存在服务器节点上的,不同的服务器节点更新的频率可能有点差异,这样可能会导致数据的不一致性。

但分类本身是更新频率比较低的数据,关于用户来说不太敏感,即使在短时刻内,用户看到的分类树有些差异,也不会对用户造成太大的影响。

因而,分类树这种事务场景,是能够运用内存缓存的。

所以,咱们运用了Spring推荐的caffine作为内存缓存。

改造后的流程图如下:

分类树,我从2s优化到0.1s

  1. 用户拜访接口时改成先从本地缓存分类数查询数据。
  2. 假如本地缓存有,则直接回来。
  3. 假如本地缓存没有,则从Redis中查询数据。
  4. 假如Redis中有数据,则将数据更新到本地缓存中,然后回来数据。
  5. 假如Redis中也没有数据(说明Redis挂了),则从数据库中查询数据,更新到Redis中(如果Redis康复了呢),然后更新到本地缓存中,回来回来数据。

需求注意的是,需求改本地缓存设置一个过期时刻,这儿设置的5分钟,不然的话,没办法获取新的数据。

这样优化之后,再次做网站主页的压力测验,qps提升到了500多,满意上线要求。

第4次优化

之后,这个功用顺畅上线了。

运用了很长一段时刻没有呈现问题。

两年后的某一天,有用户反馈说,网站主页有点慢。

咱们排查了一下原因发现,分类树的数据太多了,一次性回来了上万个分类。

本来在体系上线的这两年多的时刻内,运营同学在体系后台增加了许多分类。

咱们需求做第4次优化。

这时要怎么优化呢?

限制分类树的数量?

答:也不太实际,现在这个事务场景便是有这么多分类,不能让用户选择不到他想要的分类吧?

这时咱们想到最快的办法是开启nginxGZip功用。

让数据在传输之前,先压缩一下,然后进行传输,在用户浏览器中,主动解压,将实在的分类树数据展现给用户。

之前调用接口回来的分类树有1MB的巨细,优化之后,接口回来的分类树的巨细是100Kb,一会儿缩小了10倍。

这样简单的优化之后,功能提升了一些。

第5次优化

经过上面优化之后,用户很长一段时刻都没有反馈功能问题。

但有一天公司搭档在排查Redis中大key的时分,揪出了分类树。之前的分类树运用key/value的结构保存数据的。

咱们不得不做第5次优化。

为了优化在Redis中存储数据的巨细,咱们首先需求对数据进行减肥

只保存需求用到的字段。

例如:

@AllArgsConstructor
@Data
public class Category {
    private Long id;
    private String name;
    private Long parentId;
    private Date inDate;
    private Long inUserId;
    private String inUserName;
    private List<Category> children;
}

像这个分类对象中inDate、inUserId和inUserName字段是能够不必保存的。

修正主动称号。

例如:

@AllArgsConstructor
@Data
public class Category {
    /**
     * 分类编号
     */
    @JsonProperty("i")
    private Long id;
    /**
     * 分类层级
     */
    @JsonProperty("l")
    private Integer level;
    /**
     * 分类称号
     */
    @JsonProperty("n")
    private String name;
    /**
     * 父分类编号
     */
    @JsonProperty("p")
    private Long parentId;
    /**
     * 子分类列表
     */
    @JsonProperty("c")
    private List<Category> children;
}

由于在一万多条数据中,每条数据的字段称号是固定的,他们的重复率太高了。

由此,能够在json序列化时,改成一个简短的称号,以便于回来更少的数据巨细。

这还不行,需求对存储的数据做压缩。

之前在Redis中保存的key/value,其中的value是json格式的字符串。

其实RedisTemplate支撑,value保存byte数组

先将json字符串数据用GZip东西类压缩成byte数组,然后保存到Redis中。

再获取数据时,将byte数组转化成json字符串,然后再转化成分类树。

这样优化之后,保存到Redis中的分类树的数据巨细,一会儿减少了10倍,Redis的大key问题被解决了。

最终说一句(求重视,别白嫖我)

假如这篇文章对您有所帮助,或者有所启示的话,帮忙扫描下发二维码重视一下,您的支撑是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

重视大众号:【苏三说技能】,在大众号中回复:面试、代码神器、开发手册、时刻管理有超赞的粉丝福利,别的回复:加群,能够跟许多BAT大厂的长辈沟通和学习。