优化--分类树,我从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问题被解决了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Python实战】Python采集情感音频

成年人的世界真不容易啊 总是悲伤大于欢喜 爱情因为懵懂而快乐 却走进了复杂和困惑的婚姻 前言 我最近喜欢去听情感类的节目&#xff0c;比如说&#xff0c;婚姻类&#xff0c;我可能老了吧。我就想着怎么把音乐下载下来了&#xff0c;保存到手机上&#xff0c;方便我们业余时…

Jnpf低代码开发平台

一、写在前面 低代码开发平台&#xff0c;一个号称能在几分钟的时间里开发出一套公司内部都可使用的应用系统开发工具。 很多人或许都隐隐听说过低代码&#xff0c;因为低代码不仅远名国外&#xff0c;国内的腾讯、阿里、华为、网易、百度等科技巨头也纷纷入局&#xff0c;足以…

URL到页面: 探索网页加载的神秘过程

当我们从浏览器的地址栏输入 URL, 按下回车, 再到最后出现需要的网页界面, 这中间究竟发生了什么, 接下来就一步步进行解析. 主要是如下过程: 输入网址DNS 解析客户端发送 HTTP 请求建立 TCP 连接服务器处理请求, 计算响应, 返回响应浏览器渲染页面关闭连接 本篇中只是概述整…

docker 操作手册

名词解释 images&#xff1a;封装了应用程序的镜像 tag&#xff1a;镜像的标记&#xff0c;一个镜像可以创建多个标记 container&#xff1a;装载镜像并运行 常用命令 查看容器 docker ps -a //查看全部镜像 启动容器 docker start mysql //启动mysql容器 停止容器 doc…

Maven(三):Maven的组成详解

文章目录 坐标和依赖坐标详解依赖配置依赖范围传递性依赖依赖调节可选依赖优化排除依赖归类依赖优化依赖 仓库本地仓库远程仓库仓库镜像常用搜索地址 生命周期与插件三套生命周期clean生命周期default生命周期site生命周期 插件 聚合与继承更加灵活的构建常见问题使用jdk来运行…

TuyaOS 开发固件OTA上传固件指南

文章目录 一、产品创建二、TuyaOS设备开发三、固件上传 通过TuyaOS接入涂鸦云的产品全部默认支持固件OTA功能&#xff0c;TuyaOS设备实现固件OTA需要&#xff1a; 自定义产品创建TuyaOS嵌入式开发固件上传固件OTA配置与发布 等步骤实现产品OTA。本文重点讲述TuyaOS开发模式下&…

基于数据驱动 U-Net 模型的大气污染物扩散快速预测,提升计算速度近6000倍

项目背景 当前&#xff0c;常见的大气污染预测模型大多是基于物理机理构建的&#xff0c;比如空气质量预测模型 Calpuff、AERMOD、CMAQ 等。然而&#xff0c;这些模型运算较为复杂&#xff0c;对于输入数据的要求非常高&#xff0c;运算耗时也比较长&#xff0c;适合用于常规固…

如何在 ZBrush 和 UE5 中创建精灵吟游诗人(P1)

小伙伴们大家好&#xff0c;今天 云渲染小编给大家带来的是CG艺术家Hugo Sena关于“精灵吟游诗人”项目背后的工作流程&#xff0c;讨论了角色身体、服装和竖琴的工作流程&#xff0c;并解释了如何在虚幻引擎 5 中设置灯光。篇幅较长&#xff0c;分为上下两篇&#xff0c;大家接…

为摸鱼助力:一份Vue3的生成式ElementPlus表单组件

目录 一、实现背景 二、简介 三、组织架构设计 四、实现方式 五、代码示例 六、示例代码效果预览 七、项目预览地址 & 项目源码地址 目前项目还有诸多待完善的地方&#xff0c;大家有好的想法、建议、意见等欢迎再次评论&#xff0c;或于github提交Issues 一、实现…

【吃透网络安全】2023软考网络管理员考点网络安全(三)计算机系统安全评估

涉及知识点 计算机系统安全评估准则&#xff0c;计算机系统安全评估历史&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 后面还有更多续篇希望大家能给个赞哈&#xff0c;这边提供个快捷入口&#xff01; 第一节…

解决vue依赖报错SockJSServer.js出现Cannot read property ‘headers‘ of null

前言 在做新的需求需要变更vue的项目代码时突然出现报错 TypeError: Cannot read property ‘headers’ of null at Server.socket.on (***/node_modules/webpack-dev-server/lib/servers/SockJSServer.js:68:32) 不清楚为什么突然出现了这个问题&#xff0c;之前在这个vue项目…

【安全】使用docker安装Nessus

目录 一、准备docker环境服务器&#xff08;略&#xff09; 二、安装 2.1 搜索镜像 2.2 拉取镜像 2.3 启动镜像 三、离线更新插件 3.1 获取challenge 3.2 官方注册获取激活码 3.3 使用challenge码和激活码获取插件下载地址 3.4 下载的插件以及许可协议复制到容器内 四…

数据结构第六章 图 6.4 图的应用 错题整理

4.A A. 不是简单路径的话&#xff0c;有环&#xff0c;去环路径会更短 B. 适合的 弗洛伊德算法才不适合 C. 本来就是 D 2X2矩阵拓展到3X3矩阵 再扩大 若是子集 即加入新顶点后&#xff0c;最短路径都没有变&#xff0c;错 5.B 本题用弗洛伊德更合适 但这道题只需全部代入求最…

PowerShell系列(七)PowerShell当中的Provider介绍

往期回顾PowerShell系列&#xff08;一&#xff09;&#xff1a;PowerShell介绍和cmd命令行的区别 PowerShell系列&#xff08;二&#xff09;&#xff1a;PowerShell和Python之间的差异介绍 PowerShell系列&#xff08;三&#xff09;&#xff1a;PowerShell发展历程梳理 P…

@DateTimeFormat与@JsonFormat不完全解析

目录 前言测试代码DateTimeFormat不加任何注解的情况普通请求JSON请求 JsonFormat普通请求JSON请求 其他方式&#xff08;InitBinder&#xff09;结论源码地址 前言 一直以来对DateTimeFormat与JsonFormat 比较模糊&#xff0c;容易搞忘&#xff0c;今天就做个笔记&#xff0c…

UE4自定义资产类型编辑器实现

在虚幻引擎中&#xff0c;资产是具有持久属性的对象&#xff0c;可以在编辑器中进行操作。 Unreal 附带多种资源类型&#xff0c;从 UStaticMesh 到 UMetasoundSources 等等。 自定义资源类型是实现专门对象的好方法&#xff0c;这些对象需要专门构建的编辑器来进行高效操作。 …

SQL语言的四大组成部分——DCL(数据控制语言)

1️⃣前言 SQL语言中的DCL&#xff08;Data Control Language&#xff09;是一组用于控制数据库用户访问权限的语言&#xff0c;主要包括GRANT、REVOKE、DENY等关键字。 文章目录 1️⃣前言2️⃣DCL语言3️⃣GRANT关键字4️⃣REVOKE关键字5️⃣DENY关键字6️⃣总结附&#xff1…

【kubernetes】部署网络组件Calico与CoreDNS

前言:二进制部署kubernetes集群在企业应用中扮演着非常重要的角色。无论是集群升级,还是证书设置有效期都非常方便,也是从事云原生相关工作从入门到精通不得不迈过的坎。通过本系列文章,你将从虚拟机配置开始,到使用二进制方式从零到一搭建起安全稳定的高可用kubernetes集…

基于YOLO V8的车牌识别

赵春江 2023年6月 1、前言 十年前就想实现车牌识别这项任务&#xff0c;虽然当时这项技术就已较成熟&#xff08;与现在的实现方法不同&#xff09;&#xff0c;但那时的我还具备这个能力。弹指一瞬间&#xff0c;没想到十年间人工智能技术已经发展到一个新的高度&#xff0c…

Nacos架构与原理 - 健康检查机制

文章目录 注册中心的健康检查机制Nacos 健康检查机制临时实例健康检查机制永久实例健康检查机制集群模式下的健康检查机制 注册中心的健康检查机制 想象发生地质灾害,被掩埋在废墟下,搜救队需定位才能施救。两种方法: 大喊求救,告知位置与健康状况,让搜救队知晓搜救队使用专业…