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


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

文章插图
前言分类树查询功能,在各个业务系统中可以说随处可见,特别是在电商系统中 。
分类树,我从2s优化到0.1s

文章插图
但就是这样一个简单的分类树查询功能,我们却优化了5次 。
到底是怎么回事呢?
背景我们的网站使用了SpringBoot推荐的模板引擎:Thymeleaf,进行动态渲染 。
它是一个XML/Xhtml/HTML5模板引擎,可用于Web与非Web环境中的应用开发 。
它提供了一个用于整合SpringMVC的可选模块,在应用开发中,我们可以使用Thymeleaf来完全代替JSP或其他模板引擎,如VelocityFreeMarker等 。
前端开发写好Thymeleaf的模板文件,调用后端接口获取数据,进行动态绑定,就能把想要的内容展示给用户 。
由于当时这个是从0-1的新项目,为了开快速开发功能,我们第一版接口,直接从数据库中查询分类数据,组装成分类树,然后返回给前端 。
通过这种方式,简化了数据流程,快速把整个页面功能调通了 。
第1次优化我们将该接口部署到dev环境,刚开始没啥问题 。
【分类树,我从2s优化到0.1s】随着开发人员添加的分类越来越多,很快就暴露出性能瓶颈 。
我们不得不做优化了 。
我们第一个想到的是:加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中有数据,则将数据更新到本地缓存中,然后返回数据 。


    推荐阅读