谷粒商城 - 树形菜单递归流查询、三级分类数据查询性能优化、Jmter 性能压测

news2024/11/29 10:49:24

目录

树形分类菜单(递归查询,强扩展)

1)需求

2)数据库表设计

3)实现

4)关于 asSequence 优化

性能压测

1)Jmeter 安装使用说明

2)中间件对性能的影响

三级分类数据查询性能优化

需求分析

1)未优化

2)第一次优化(数据库一次查询)

3)第二次优化(SpringCache 整合 Redis)

4)3 种不同实现性能测试


树形分类菜单(递归查询,强扩展)


1)需求

展示如下属性分类菜单

实际上是一个三级分层目录,并且考虑到将来可能会拓展成为 四级、五级... 目录.

2)数据设计

a)表设计如下

CREATE TABLE `pms_category` (
  `cat_id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类id',
  `name` char(50) DEFAULT NULL COMMENT '分类名称',
  `parent_cid` bigint DEFAULT NULL COMMENT '父分类id',
  `cat_level` int DEFAULT NULL COMMENT '层级',
  `show_status` tinyint DEFAULT NULL COMMENT '是否显示[0-不显示,1显示]',
  `sort` int DEFAULT NULL COMMENT '排序',
  `icon` char(255) DEFAULT NULL COMMENT '图标地址',
  `product_unit` char(50) DEFAULT NULL COMMENT '计量单位',
  `product_count` int DEFAULT NULL COMMENT '商品数量',
  PRIMARY KEY (`cat_id`),
  KEY `parent_cid` (`parent_cid`)
) ENGINE=InnoDB AUTO_INCREMENT=1433 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品三级分类';

b)vo 设计如下

data class CategoryTreeVo (
    val catId: Long,
    val name: String,
    val parentCid: Long, //父分类 id
    val catLevel: Int, //层级
    val showStatus: Int, //是否显示 (0不显示 1显示)
    val sort: Int,
    val icon: String?, //图标地址,
    val productUnit: String?, //计量单位
    val productCount: Int, //商品数量
    var children: List<CategoryTreeVo> //孩子节点
)

3)实现

这里我们可以先将 pms_category 表中所有数据取出来,然后在内存中通过流的方式来操作数据.

具体的流操作,如下:

  1. filter:过滤出一级菜单分类.
  2. map:通过递归的方式来查询子菜单分类,包装到 vo 中
    1. filter:先从所有的数据中过滤出当前菜单的子菜单
    2. map:递归的方式继续查询子菜单,包装到 vo 中.
      1. ......
  3. sortedBy:按照 sort 字段来进行升序排序.
@Service
class CategoryServiceImpl(
    private val categoryRepo: CategoryRepo,
): CategoryService {

    override fun listWithTree(): List<CategoryTreeVo> {
        //1.获取所有分类
        val vos = categoryRepo.queryAll().map(::map)
        //2.分级
        val result = vos.asSequence() //数据量较大,使用 asSequence 有优化
            .filter { category -> //1) 找到所有一级分类
                category.catLevel == 1
            }.map { category -> //2) 递归的去找 children 分类
                category.children = getCategoryChildrenDfs(category, vos)
                return@map category
            }.sortedBy { // 降序 sortedByDescending { it.sort }
                it.sort
            }.toList()
        return result
    }

    /**
     * 递归的去找 children 分类
     */
    private fun getCategoryChildrenDfs(
        root: CategoryTreeVo,
        all: List<CategoryTreeVo>
    ): List<CategoryTreeVo> {
        //递归终止条件: 当 filter 过滤出来的数据为空,就直接返回 空list,不会走下一个 map 逻辑了
        val result = all
            .filter { category -> //1.从所有分类中找出父节点为当前节点(找到当前节点的所有孩子节点)
                category.parentCid == root.catId
            }.map { category -> //2.递归的去找孩子
                category.children = getCategoryChildrenDfs(category, all)
                return@map category
            }.sortedBy { category ->
                category.sort
            }.toList()
        return result
    }

    fun map(obj: Category) = with(obj) {
        CategoryTreeVo (
            catId = catId,
            name = name,
            parentCid = parentCid,
            catLevel = catLevel,
            showStatus = showStatus,
            sort = sort,
            icon = icon,
            productUnit = productUnit,
            productCount = productCount,
            children = emptyList(),
        )
    }

}

4)关于 asSequence 优化

这里我也做了一个压测(1min/100用户并发)

没有使用 asSequence 如下:

使用 asSequence 如下:

Ps:asSequence 在处理大数据量时速度更快的原因主要是因为它采用了惰性求值策略,避免了不必要的多次迭代和中间集合的创建(原本的集合操作,每进行例如 filter 就会创建中间集合),从而减少了内存和处理时间的消耗。这种优化在处理大数据集时尤其显著

性能压测


1)Jmeter 安装使用说明

a)安装

Apache JMeter - Download Apache JMeter

解压后点击 \apache-jmeter-5.6.3\bin 目录下的 jmeter.bat 即可.

b)参数说明

吞吐量:每秒处理的请求个数 

一般自己测的时候,主要观察这两个指标即可.

关于吞吐量,这里可以给出一个业界的标准~

  • 电商网站

    • 小型:几十到几百 RPS (10-500)
    • 中型:几百到几千 RPS (500-5,000)
    • 大型:几千到数万 RPS (5,000-50,000)
  • 社交媒体平台

    • 中型:几千到几万 RPS (5,000-50,000)
    • 大型:数万到数十万 RPS (50,000-500,000)
  • 流媒体服务

    • 小型:几百到几千 RPS (500-5,000)
    • 大型:数千到数万 RPS (5,000-50,000)
  • 在线游戏服务器

    • 小型:几十到几百 RPS (10-500)
    • 大型:几千到几万 RPS (5,000-50,000)

2)中间件对性能的影响

这里我们对当前系统进行一个性能测试,先来看看中间件(例如 nginx、网关...)对系统的影响.

测试内容线程数吞吐量/s
网关(直接将请求打到网关端口即可,404 也算正常返回)5025262
简单服务(直接将请求打到对应的微服务即可)5039234
网关 + 简单服务(服务就简单的返回一个 hello 字符串即可)5012072
  • 分析:引入中间件会带来更大的网络开销. 
    • 起初,只需要客户端和服务之间进行通讯.
    • 引入网关后,需要 客户端先和网关通讯,再有网关和服务通讯,最后在原路返回响应.
  • 结论:中间件越多,性能损耗越大.
  • 优化:考虑跟高效的网络协议、买更好的网卡,增加网络带宽...

三级分类数据查询性能优化


需求分析

a)需要给前端返回的结果是一个 json 结构数据,格式如下:

最外层是一个 Map<String, List<Any>> 的结构. key 就是一级分类id. value 是一个对象数组

这个对象就是 二级分类 的数据

二级分类中又包含该分类下的三级分类列表

对应 data class 如下:

//二级分类数据
data class Catalog2Vo (
    val catalog1Id: String, //一级分类 id (父分类 id)
    val catalog3List: List<Catalog3Vo>, //三级子分类
    val id: String,
    val name: String,
)

//三级分类数据
data class Catalog3Vo (
    val catalog2Id: String, //二级分类 id (父分类 id)
    val id: String,
    val name: String,
)

最后给前端返回 Map<String, List<Catalog2Vo>> 数据.  key 是一级分类 id

1)未优化

a)实现方式:

最直接的方法就是先从数据库中查到所有一级分类数据,然后再拿着每一个一级分类 id 去查对应的二级分类数据,最后拿着每个二级分类的 id 去查对应的三级分类数据.

    override fun getCatalogJson(): Map<String, List<Catalog2Vo>> {
        //1.查询所有一级分类
        val level1List = categoryRepo.queryLevel1CategoryAll()
        //2.封装 二级 -> 三级 分类
        val result = level1List.associate { l1 -> l1.catId.toString() to run {
            //1) 查到这个一级分类中的所有二级分类
            val level2Vos = categoryRepo.queryCategoryByParentId(l1.catId)
                .map { l2 ->
                    //2) 查到这个二级分类中所有三级分类
                    val leve3Vos = categoryRepo.queryCategoryByParentId(l2.catId)
                        .map { l3 -> Catalog3Vo(l2.catId.toString(), l3.catId.toString(), l3.name) }
                    //3) 将 三级分类List 整合到 二级分类List
                    return@map Catalog2Vo(l1.catId.toString(), leve3Vos, l2.catId.toString(), l2.name)
                }
            return@run level2Vos
        } }
        return result
    }

b)问题:

查询效率非常低,循环套循环频繁的和数据建立和断开连接,带来很大一部分网络开销.

2)第一次优化(数据库一次查询)

a)实现方式:

为了避免大量数据库连接,可以换一个思路~

一开始就从数据库中拿到 分类表 中的所有数据,然后在内存中操作,过滤出每一个一级分类下的所有二级分类数据.......

    override fun getCatalogJson(): Map<String, List<Catalog2Vo>> {
        //1.查询所有分类数据
        val all = categoryRepo.queryAll()
        //2.查询所有一级分类数据
        val level1List = getCategoryByParentId(all, 0L)
        //2.封装 二级 -> 三级 分类
        val result = level1List.associate { l1 -> l1.catId.toString() to run {
            //1) 查到这个一级分类中的所有二级分类
            val level2Vos = getCategoryByParentId(all, l1.catId)
                .map { l2 ->
                    //2) 查到这个二级分类中所有三级分类
                    val leve3Vos = getCategoryByParentId(all, l2.catId)
                        .map { l3 -> Catalog3Vo(l2.catId.toString(), l3.catId.toString(), l3.name) }
                    //3) 将 三级分类List 整合到 二级分类List
                    return@map Catalog2Vo(l1.catId.toString(), leve3Vos, l2.catId.toString(), l2.name)
                }
            return@run level2Vos
        } }
        return result
    }

    //从所有数据中过滤出指定 parentId 的数据
    private fun getCategoryByParentId(all: List<Category>, parentId: Long): List<Category> {
        return all.filter { it.parentCid == parentId }
    }

3)第二次优化(SpringCache 整合 Redis)

对于分类数据这种每次在内存中计算很耗时,并且更新频率低的数据就非常适合保存到 Redis 缓存中.

a)实现方式:

直接使用 SpringCache 整合 Redis 对数据进行缓存

    @Cacheable(value = ["category"], key = "#root.methodName")
    override fun getCatalogJson(): Map<String, List<Catalog2Vo>> {
        println("查询了数据库...")
        return getCatalogJsonFromDb()
    }

    //三级分类查询(数据库一次查询)
    fun getCatalogJsonFromDb(): Map<String, List<Catalog2Vo>> {
        //1.查询所有分类数据
        val all = categoryRepo.queryAll()
        //2.查询所有一级分类数据
        val level1List = getCategoryByParentId(all, 0L)
        //2.封装 二级 -> 三级 分类
        val result = level1List.associate { l1 -> l1.catId.toString() to run {
            //1) 查到这个一级分类中的所有二级分类
            val level2Vos = getCategoryByParentId(all, l1.catId)
                .map { l2 ->
                    //2) 查到这个二级分类中所有三级分类
                    val leve3Vos = getCategoryByParentId(all, l2.catId)
                        .map { l3 -> Catalog3Vo(l2.catId.toString(), l3.catId.toString(), l3.name) }
                    //3) 将 三级分类List 整合到 二级分类List
                    return@map Catalog2Vo(l1.catId.toString(), leve3Vos, l2.catId.toString(), l2.name)
                }
            return@run level2Vos
        } }
        return result
    }

    //从所有数据中过滤出指定 parentId 的数据
    private fun getCategoryByParentId(all: List<Category>, parentId: Long): List<Category> {
        return all.filter { it.parentCid == parentId }
    }

4)3 种不同实现性能测试

50 个线程并发

a)未优化

b)第一次优化 (数据库一次查询)

c)第二次优化(SpringCache 整合 Redis)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python内存优化的实战技巧详解

概要 Python是一种高级编程语言,以其易读性和强大的功能而广受欢迎。然而,由于其动态类型和自动内存管理,Python在处理大量数据或高性能计算时,内存使用效率可能不如一些低级语言。本文将介绍几种Python内存优化的技巧,并提供相应的示例代码,帮助在开发中更高效地管理内…

uniapp启动安卓模拟器mumu

mumu模拟器下载 ADB&#xff1a; android debug bridge &#xff0c; 安卓调试桥&#xff0c;是一个多功能的命令行工具&#xff0c;他使你能够与连接的安卓设备进行交互 # adb连接安卓模拟器 adb connect 127.0.0.1:port # 查看adb设备 adb deviceshubuilderx 有内置的adb&a…

【鸿蒙学习笔记】@Link装饰器:父子双向同步

官方文档&#xff1a;Link装饰器&#xff1a;父子双向同步 目录标题 [Q&A] Link装饰器作用 [Q&A] Link装饰器特点样例&#xff1a;简单类型样例&#xff1a;数组类型样例&#xff1a;Map类型样例&#xff1a;Set类型样例&#xff1a;联合类型 [Q&A] Link装饰器作用…

锂电池寿命预测 | Matlab基于改进的遗传算法优化BP神经网络的锂离子电池健康状态SOH估计

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 主要流程如下: 1、首先提取“放电截止电压时间”作为锂电池间接健康因子&#xff1b; 2、然后引入改进的遗传算法对BP神经网络的模型参数进行优化。 3、最后 NASA 卓越预测中心的锂电池数据集 B0005、B0006、B0007对…

VSCode设置字体大小

方法1&#xff1a;Ctrl 和 Ctrl -&#xff0c;可以控制整个VSCode界面的整体缩放&#xff0c;但是不会调整字体大小 方法2&#xff1a;该方法只能设置编辑器界面的字号&#xff0c;无法改变窗口界面的字号。 &#xff08;1&#xff09;点开左下角如下图标&#xff0c;进入…

【JVM基础篇】Java垃圾回收器介绍

垃圾回收器&#xff08;垃圾回收算法实现&#xff09; 垃圾回收器是垃圾回收算法的具体实现。由于垃圾回收器分为年轻代和老年代&#xff0c;除了G1&#xff08;既能管控新生代&#xff0c;也可以管控老年代&#xff09;之外&#xff0c;新生代、老年代的垃圾回收器必须按照ho…

【Python】组合数据类型:序列,列表,元组,字典,集合

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️Python】 文章目录 前言组合数据类型序列类型序列常见的操作符列表列表操作len()append()insert()remove()index()sort()reverse()count() 元组三种序列类型的区别 集合类型四种操作符集合setfrozens…

tongweb 部署软航流版签一体化应用示例 提示跨域错误CORS ERROR

目录 问题现象与描述 解决办法 原理解析 什么是CORS 浏览器跨域请求限制 跨域问题解决方法 跨域请求流程 浏览器请求分类解析 http请求方法简介 问题现象与描述 重庆软航科技有限公司提供了一套针对针对word、excel等流式文件转换成PDF版式文件并进行版式文件在线签章…

什么是 DDoS 攻击及如何防护DDOS攻击

自进入互联网时代&#xff0c;网络安全问题就一直困扰着用户&#xff0c;尤其是DDOS攻击&#xff0c;一直威胁着用户的业务安全。而高防IP被广泛用于增强网络防护能力。今天我们就来了解下关于DDOS攻击&#xff0c;以及可以防护DDOS攻击的高防IP该如何正确选择使用。 一、什么是…

Apache Seata分布式事务启用Nacos做配置中心

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Seata分布式事务启用Nacos做配置中心 Seata分布式事务启用Nacos做配置中心 项目地址 本文作…

matlab 有倾斜的椭圆函数图像绘制

matlab 有倾斜的椭圆函数图像绘制 有倾斜的椭圆函数图像绘制xy交叉项引入斜线负向斜线成分正向斜线成分 x^2 y^2 xy 1 &#xff08;负向&#xff09;绘制结果 x^2 y^2 - xy 1 &#xff08;正向&#xff09;绘制结果 有倾斜的椭圆函数图像绘制 为了确定椭圆的长轴和短轴的…

复现YOLO_ORB_SLAM3_with_pointcloud_map项目记录

文章目录 1.环境问题2.遇到的问题2.1编译问题1 monotonic_clock2.2 associate.py2.3 associate.py问题 3.运行问题 1.环境问题 首先环境大家就按照github上的指定环境安装即可 环境怎么安装网上大把的资源&#xff0c;自己去找。 2.遇到的问题 2.1编译问题1 monotonic_cloc…

STMF4学习笔记RTC(天空星)

前言&#xff1a;本篇笔记参考嘉立创文档&#xff0c;连接放在最后 #RTC相关概念定义 Real-Time Clock 缩写 RTC 翻译 实时时钟&#xff0c;是单片机片内外设的一种&#xff0c;作用于提供准确的时间还有日期&#xff0c;这个外设有独立的电源&#xff0c;当单片机停止供电…

C++之static关键字

文章目录 前提正文多重定义extern关键字使用staticstatic 全局变量(在.cpp文件中定义)static变量存放在哪里static变量可不可以放在.h文件中 static 函数static局部变量static 成员变量static 成员函数 总结参考链接 前提 好吧&#xff0c;八股&#xff0c;我又回来了。这次想…

【wordpress教程】wordpress博客网站添加非法关键词拦截

有的网站经常被恶意搜索&#xff0c;站长们不胜其烦。那我们如何屏蔽恶意搜索关键词呢&#xff1f;下面就随小编一起来解决这个问题吧。 后台设置预览图&#xff1a; 设置教程&#xff1a; 1、把以下代码添加至当前主题的 functions.php 文件中&#xff1a; add_action(admi…

I2C接口+高度集成的电源管理芯片(PMIC)-iML1942

电源管理芯片 - iML1942是一个高度集成的电源管理IC为TFT液晶面板。它具有完整的I2C接口来编程各种参数。该设备包括一个针对AVDD的电流模式升压调节器&#xff0c;一个针对VBK1的同步升压转换器。VGL可选的反相转换器或负电荷泵调节器&#xff0c;VSS1负线性调节器&#xff0c…

基于python的数据分解-趋势-季节性-波动变化

系列文章目录 前言 时间序列数据的分解&#xff0c;一般分为趋势项&#xff0c;季节变化项和随机波动项。可以基于加法或者乘法模型。季节变化呈现出周期变化&#xff0c;因此也叫季节效应(周期&#xff09;。 一、数据分解步骤 &#xff08;1&#xff09;估计时间序列的长期…

相关向量机RVM算法介绍继承sklearn-SVM-API实现回归预测算例

一、相关向量机RVM与支持向量机SVM对比 1、相关向量机&#xff08;RVM&#xff09; ①定义与原理 相关向量机&#xff08;Relevance Vector Machine, RVM&#xff09;是一种基于概率模型的机器学习算法&#xff0c;主要用于分类和回归分析。基于稀疏贝叶斯学习框架&#xff…

杰理科技AD142A语音芯片,语音玩具方案—云信通讯

语音玩具产品市场的需求量比较大&#xff0c;从前简单的发光玩具&#xff0c;到各种动作的电子玩具&#xff0c;再到如今的语音录音灯光动作玩具&#xff0c;可见玩具行业也是在不断地演变。 杰理语音芯片AD142A4的优势主要是支持录音、录变音、语音播放&#xff0c;广泛应用于…

kubernetes集群部署:node节点部署和CRI-O运行时安装(三)

关于CRI-O Kubernetes最初使用Docker作为默认的容器运行时。然而&#xff0c;随着Kubernetes的发展和OCI标准的确立&#xff0c;社区开始寻找更专门化的解决方案&#xff0c;以减少复杂性和提高性能。CRI-O的主要目标是提供一个轻量级的容器运行时&#xff0c;它可以直接运行O…