Redis系列 | 分类树查询功能如何从2s优化到0.1s

大家好,今天我们继续来分享一个在项目开发过程中遇到的实际问题,这里也来梳理并总结一下我们是如何对它进行持续优化的,希望能对大家有所帮助。

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

但就是这样一个看似简单的分类树查询功能,我们却优化了数次。这其中到底经历了什么呢?

背 景

我们的一个老项目使用了SpringBoot推荐的模板引擎:Thymeleaf,进行动态渲染。它是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它提供了一个用于整合SpringMVC的可选模块,在应用开发中,我们可以使用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity\FreeMarker等。前端开发写好Thymeleaf的模板文件,调用后端接口获取数据,进行动态绑定,就能把想要的内容展示给用户。由于当时很早这也个是从0-1的新项目,为了开快速开发功能,第一版接口是直接从数据库中查询分类数据,组装成分类树,然后返回给前端。通过这种方式,简化了数据流程,快速把整个页面功能调通了。

第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/1788.html

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

相关文章

国产chatgpt:基于chatGLM微调nlp分类任务

文章目录 一、源码网址1. 硬件设施&#xff1a;2. INT4 量化示例 二、重要的开源社区功能&#xff1a;网址&#xff1a;使用方法&#xff1a;利用方法&#xff1a;对 NLP 工作者的作用&#xff1a;对大模型工程师的用处&#xff1a; 三、重要的开源库四、提示词工程五、进行分类…

线性代数笔记整理

文章目录 1 行列式2 矩阵&#xff08;本质是数表&#xff09;3 方程组的解4 向量5 矩阵的特征值和特征向量6 相似矩阵和相似对角化7 合同对角化8 二次型及其标准型 1 行列式 2 矩阵&#xff08;本质是数表&#xff09; 3 方程组的解 4 向量 5 矩阵的特征值和特征向量 6 相似矩阵…

从0到1精通自动化测试,pytest自动化测试框架,skip跳过用例(八)

一、前言 pytest.mark.skip可以标记无法在某些平台上运行的测试功能&#xff0c;或者希望自己失败的测试功能 skip意味着只有在满足某些条件时才希望测试通过&#xff0c;否则pytest应该跳过运行测试。 常见示例是在非Windows平台上跳过仅限Windows的测试&#xff0c;或跳过测…

pytorch搭建AlexNet网络实现花分类

pytorch搭建AlexNet网络实现花分类 一、AlexNet网络概述分析 二、数据集准备下载划分训练集和测试集 三、代码model.pytrain.pypredict.py 一、AlexNet网络 概述 使用Dropout的方式在网络正向传播过程中随机失活一部分神经元&#xff0c;以减少过拟合 分析 对其中的卷积层、…

MyCat01——如何实现MySQL中的主从复制

1 问题 数据对于我们来说是一项最重要的资产&#xff0c;因为数据丢失带来的损失&#xff0c;对于一家公司来说&#xff0c;有时也是毁灭性的。 那么如何确保数据安全&#xff0c;不因断电或系统故障带来数据丢失呢&#xff1f; 当用户增加&#xff0c;对数据库的访问量也随…

【Soft-prompt Tuning for Large Language Models to Evaluate Bias 论文略读】

Soft-prompt Tuning for Large Language Models to Evaluate Bias 论文略读 INFORMATIONAbstract1 Introduction2 Related work3 Methodology3.1 Experimental setup 4 Results5 Discussion & Conclusion总结A Fairness metricsB Hyperparmeter DetailsC DatasetsD Prompt …

【Java】JVM学习(七)

JVM调优 堆空间如何设置 在分代模型中&#xff0c;各分区的大小对GC的性能影响很大。如何将各分区调整到合适的大小&#xff0c;分析活跃数据的大小是很好的切入点。 活跃数据的大小&#xff1a;应用程序稳定运行时长期存活对象在堆中占用的空间大小&#xff0c;也就是Full …

拧螺丝需求:递归算法的极致应用

前言 在一个平平无奇的下午&#xff0c;接到一个需求&#xff0c;需要给公司的中台系统做一个json报文重组的功能。 因为公司的某些业务需要外部数据的支持&#xff0c;所以会采购一些其它公司的数据&#xff0c;而且为了保证业务的连续性&#xff0c;同一种数据会采购多方的数…

Qt QSqlQueryModel详解

背景知识&#xff1a; Qt SQL的API分为不同层&#xff1a; 驱动层 驱动层 对于QT是基于C来实现的框架&#xff0c;该层主要包括QSqlDriver、QSqlDriverCreator、QSqlDriverCreatorbase、QSqlDriverPlugin and QSqlResult。这一层提供了特定数据库和SQL API层之间的底层桥梁…

Servlet(下篇)

哥几个来学 Servlet 啦 ~~ 这个是 Servlet&#xff08;上篇&#xff09;的链接&#xff0c; (2条消息) Servlet &#xff08;上篇&#xff09;_小枫 ~的博客-CSDN博客https://blog.csdn.net/m0_64247824/article/details/131229873主要讲了 Servlet的定义、Servlet的部署方式、…

C语言-基础语法学习-3 二级指针

目录 二级指针二级指针的定义和声明二级指针的初始化二级指针的使用二级指针和函数参数二级指针和动态内存分配数组指针二维数组二维数组的初始化二维数组与指针二维数组的遍历 二级指针 当涉及到多级指针时&#xff0c;C语言的灵活性和强大的指针功能可以得到充分的发挥。二级…

原生HTML+CSS+JS制作自己的导航主页

如果你想使用原生HTML、CSS和JS制作自己的导航主页&#xff0c;你可以按照以下步骤进行操作&#xff1a; 先看效果图&#xff1a; 创建HTML文件&#xff1a;首先&#xff0c;创建一个新的HTML文件&#xff0c;并在文件中添加基本的HTML结构。你可以使用<!DOCTYPE html>…