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

1.前言

分类树查询功能,在各个业务系统中可以说随处可见,特别是在电商系统中。

但就是这样一个简单的分类树查询功能,我们却优化了5次。

到底是怎么回事呢?

2.背景

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

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

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

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

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

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

3.优化

第1次优化

我们将该接口部署到dev环境,刚开始没啥问题。

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

我们不得不做优化了。

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

流程图如下:

于是暂时这样优化了一下:

  1. 用户访问接口获取分类树时,先从Redis中查询数据。

  2. 如果Redis中有数据,则直接数据。

  3. 如果Redis中没有数据,则再从数据库中查询数据,拼接成分类树返回。

  4. 将从数据库中查到的分类树的数据,保存到Redis中,设置过期时间5分钟。

  5. 将分类树返回给用户。

我们在Redis中定义一个了key,value是一个分类树的json格式转换成了字符串,使用简单的key/value形式保存数据。

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

第2次优化

我们将这个功能部署到st环境了。

刚开始测试同学没有发现什么问题,但随着后面不断地深入测试,隔一段时间就出现一次首页访问很慢的情况。

于是,我们马上进行了第2次优化。

我们决定使用Job定期异步更新分类树到Redis中,在系统上线之前,会先生成一份数据。

当然为了保险起见,防止Redis在哪条突然挂了,之前分类树同步写入Redis的逻辑还是保留。

于是,流程图改成了这样:

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

其他的流程保持不变。

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

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

第3次优化

测试了一段时间之后,整个网站的功能快要上线了。

为了保险起见,我们需要对网站首页做一次压力测试。

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

我们需要做第3次优化。

该怎么优化呢?

答:加内存缓存。

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

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

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

因此,分类树这种业务场景,是可以使用内存缓存的。

于是,我们使用了Spring推荐的caffine作为内存缓存。

改造后的流程图如下:

  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问题被解决了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/1848.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LIN诊断实现MCU本地OTA升级

一、目标 通过PC端上位机实现MCU本地的OTA升级,本篇文章对实现的目的、需要用到的第三方工具、LIN诊断帧、升级协议、MCU端升级过程以及PC端升级过程做详细说明。 二、目的 最近在做MCU项目时需要将样机寄给客户进行验证,在客户的验证过程中要求参数可调试,如果需要修改软…

proteus三级管(NPN)仿真LED灯串的开关

实验里设置LED灯的导通电压为1.2V 打开仿真开关后可以看到&#xff0c;此时三极管不导通&#xff0c;LED灯亮了&#xff0c;并且电压表和电流探针有数值显示 按下按键&#xff0c;三级管导通&#xff0c;LED灯灭

vue项目运行不起来,可能是版本等不兼容问题

给pakeage.json 运行脚本前加上&#xff1a;set NODE_OPTIONS--openssl-legacy-provider && 即可。

电商--抢购架构总结

文章目录 背景业务流程业务难点技术难点技术方案技术方向具体落地客户端流控网关流控容器流控后端接口流控数据库流控 流控总结优化读取加速异步化流程处理系统扩容 压测监控 总结参考文献 背景 这是个在做NFT电商项目时遇到的场景&#xff0c;要求运营可以商家某个系列的NFT商…

【MySQL】不就是多表查询

前言 嗨&#xff01;小伙伴们大家好呀&#xff0c;忙碌的一周就要开始&#xff01;在此之前我们学习的MySQL数据库的各种操作都是在一张表之中&#xff0c;今天我们学习要对多张表进行相关操作&#xff0c;相比较于单一的表来说&#xff0c;多张表操作相对复杂一些&#xff0c;…

【MySQL】库的操作

目录 一、创建数据库 二、字符集和校验规则 1、查看系统默认字符集以及校验规则 2、创建数据库案例 3、校验规则对数据库的影响 3.1、不区分大小写 3.2、区分大小写 3.3、进行查询 3.3.1、不区分大小写的查询以及结果 3.3.2、区分大小写的查询以及结果 3.4、结果排序…

软件测试项目案例去哪找【银行/金融/电商/外卖】

项目经验&#xff08;案例一&#xff09; 项目时间&#xff1a;2016-08 - 2017-07 项目名称&#xff1a;小花钱包&#xff08;Web&#xff09; 项目描述&#xff1a; 项目介绍 这个产品产是互联网金融理财服务平台&#xff0c;既可以发起投标&#xff0c;也可以借款&#xf…

AutoSAR系列讲解(入门篇)4.3-BSW的Communication功能

一、架构与术语解释 BSW中以后每一节我都会放上一张模块图&#xff0c;所以就先上图&#xff1a; 由于汽车上一般都使用CAN总线&#xff0c;图中的bus大家可以就当成CAN来看待&#xff0c;如果使用的是LIN或者其他的&#xff0c;也相应的换成其总线看待就行。后续在实践篇中将会…

腾讯云私有云平台运维面试

文章目录 概述JD 岗位描述一面二面三面HR面 概述 根据会议将面试问题进行总结&#xff0c;很多问题感觉当时没回答好&#xff0c;这是为啥呢&#xff1f;应该还是不熟练吧&#xff0c;或者不善于表达。将次经历分享出来&#xff0c;大家多练练。 JD 岗位描述 私有云平台运维…

使用 JCommander 解析命令行参数

前言 如果你想构建一个支持命令行参数的程序&#xff0c;那么 jcommander 非常适合你&#xff0c;jcommander 是一个只有几十 kb 的 Java 命令行参数解析工具&#xff0c;可以通过注解的方式快速实现命令行参数解析。 这篇教程会通过介绍 jcommadner &#xff0c;快速的创建一…

Rabbit MQ的基本使用

目录 1. MQ是什么&#xff0c;有哪些作用&#xff1f; 2. 主要的MQ框架有哪些&#xff1f; 3. RabbitMQ安装 4. RabbitMQ中的主要概念 5. 消息队列的核心概念 6. 一个简单的生产者和消费者示例 6.1 消息发送者模块 6.1.1 引入必要的依赖 6.1.2 项目配置文件 6.1.3 启动…

【探索 Kubernetes|作业管理篇 系列 14】StatefulSet 存储状态

前言 大家好&#xff0c;我是秋意零。 在上一篇中&#xff0c;我们讲解了 StatefulSet 的拓扑状态&#xff1b;我们发现&#xff0c;它的拓扑状态&#xff0c;就是顺序启动/删除、Pod 名称编号命名、将 Pod 名称设为 Hostname 名称、通过 Service 无头服务的 DNS 记录访问。 …