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

目录

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

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.mfbz.cn/a/777369.html

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

相关文章

昇思25天学习打卡营第5天|GAN图像生成

文章目录 昇思MindSpore应用实践基于MindSpore的生成对抗网络图像生成1、生成对抗网络简介零和博弈 vs 极大极小博弈GAN的生成对抗损失 2、基于MindSpore的 Vanilla GAN3、基于MindSpore的手写数字图像生成导入数据数据可视化模型训练 Reference 昇思MindSpore应用实践 本系列…

01 企业网站架构部署于优化之Web基础与HTTP协议

目录 1.1 Web基础 1.1.1 域名和DNS 1. 域名的概念 2. Hosts文件 3. DNS 4. 域名注册 1.1.2 网页与HTML 1. 网页概述 2. HTML概述 3. HTML基本标签 4. 网站和主页 5. Web1.0与Web2.0 1.1.3 静态网页与动态网页 1. 静态网页 2. 动态网页 3. 动态网页语言 1.2 HTTP协议 1…

如何快速开展每日待办工作 待办任务高效管理

每天&#xff0c;我们都需要处理大量的待办工作&#xff0c;如何高效有序地开展这些工作成为了我们必须要面对的问题。仅仅依靠个人的记忆和脑力去管理这些繁杂的事务&#xff0c;显然是一项艰巨的挑战。在这个时候&#xff0c;如果能有一款实用的待办工具来辅助我们&#xff0…

7年跨境业务资深从业者的代理IP使用经验分享

作为一个拥有7年跨境业务经验的资深从业者&#xff0c;今天大家分享一下在使用各种代理IP服务中的宝贵经验。无论你是新手还是老手&#xff0c;这篇文章都能为你带来一些新的启发和实用技巧。 在我刚开始跨境业务的那几年&#xff0c;最大的挑战之一就是如何跨境访问&#xff0…

ORB 特征点提取

FAST关键点 选取像素p&#xff0c;假设它的亮度为Ip&#xff1b; . 设置一个阈值T&#xff08;比如Ip的20%&#xff09;&#xff1b; 以像素p为中心&#xff0c;选取半径为3的圆上的16个像素点&#xff1b; 假如选取的圆上&#xff0c;有连续的N个点的亮度大于IpT或小于…

CSS实现图片裁剪居中(只截取剪裁图片中间部分,图片不变形)

1.第一种方式&#xff1a;&#xff08;直接给图片设置&#xff1a;object-fit:cover;&#xff09; .imgbox{width: 100%;height:200px;overflow: hidden;position: relative;img{width: 100%;height: 100%; //图片要设置高度display: block;position: absolute;left: 0;right…

【Python基础篇】你了解python中运算符吗

文章目录 1. 算数运算符1.1 //整除1.2 %取模1.3 **幂 2. 赋值运算符3. 位运算符3.1 &&#xff08;按位与&#xff09;3.2 |&#xff08;按位或&#xff09;3.3 ^&#xff08;按位异或&#xff09;3.4 ~&#xff08;按位取反&#xff09;3.5 <<&#xff08;左移&#…

【JavaWeb程序设计】JSP编程II

目录 一、输入并运行下面的import_test.jsp页面 1.1 代码运行结果 1.2 修改编码之后的运行结果 二、errorPage属性和isErrorPage属性的使用 2.1 下面的hello.jsp页面执行时将抛出一个异常&#xff0c;它指定了错误处理页面为errorHandler.jsp。 2.1.2 运行截图 2.2 下面…

压测工具---Ultron

压测工具&#xff1a;Ultron 类型&#xff1a;接口级和全链路 接口级 对于接口级别的压测我们可以进行 http接口压测、thrift压测、redis压测、kafka压测、DDMQ压测、MySQL压测等&#xff0c;选对对应的业务线、选择好压测执行的时间和轮数就可以执行压测操作了 全链路 对…

Java新特性梳理——Java15

highlight: xcode theme: vuepress 概述 2020 年 9 月 15 日&#xff0c;Java 15 正式发布&#xff0c;(风平浪静的一个版本)共有 14 个 JEP&#xff0c;是时间驱动形式发布的第六个版本。相关文档&#xff1a;https://openjdk.java.net/projects/jdk/15/ 语法层面变化 密封类 …

【机器学习】基于密度的聚类算法:DBSCAN详解

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 基于密度的聚类算法&#xff1a;DBSCAN详解引言DBSCAN的基本概念点的分类聚类过…

JVM原理(十七):JVM虚拟机即时编译器详解

编译器无论在何时、在何种状态下把Class文件转换成与本地基础设施相关的二进制机器码&#xff0c;他都可以视为整个编译过程的后端。 后端编译器编译性能的好坏、代码优化质量的高低却是衡量一款商用虛拟机优秀与否的关键指标之一。 1. 即时编译器 即时编译器是一个把Java的…

19.【C语言】初识指针(重难点)

内存&#xff1a;所有程序的运行在内存中 用Cheat Engine查看任意程序的内存(16进制&#xff09;&#xff1a; 显示大量的数据 想要定位某个数字 &#xff0c;需要知道地址(类比二维坐标) 如F8的地址为00BCB90008,所以是00BCB908(偏移) ctrlG 则有 内存单元的说明&#xff1…

动态颤抖的眼睛效果404页面源码

动态颤抖的眼睛效果404页面源码&#xff0c; 源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 动态颤抖的眼睛效果404页面源码

Portainer 是一个开源的容器管理平台-非常直观好用的Docker图形化项目

在这个容器化技术大行其道的时代&#xff0c;Docker和Kubernetes几乎成了技术圈的新宠。可是管理起容器来&#xff0c;有时候还是有点头大。命令行操作对于某些小伙伴来说&#xff0c;可能还是有点不太友好。 今天开源君分享一个叫 Portainer 的开源项目&#xff0c;一个用来简…

AI大模型时代的存储发展趋势

从2022年下半年&#xff0c;大模型和AIGC这两个词变得极其火热&#xff0c;而GPU的市场也是一卡难求。对于这种迷乱和火热&#xff0c;让我想起了当年的比特币挖矿和IPFS。似乎世界一年一个新风口&#xff0c;比特币、元宇宙、NFT、AIGC&#xff0c;金钱永不眠&#xff0c;IT炒…

【React】React18 Hooks 之 useReducer

目录 useReducer案例1&#xff1a;useReducer不带初始化函数案例2&#xff1a;useReducer带初始化函数注意事项1&#xff1a;dispatch函数不会改变正在运行的代码的状态注意事项2&#xff1a;获取dispatch函数触发后 JavaScript 变量的值注意事项3&#xff1a;触发了reducer&am…

【MotionCap】pycharm 远程在wsl2 ubuntu20.04中root的miniconda3环境

pycharm wsl2 链接到pycharmsbin 都能看到内容,/root 下内容赋予了zhangbin 所有,pycharm还是看不到/root 下内容。sudo 安装了miniconda3 引发了这些问题 由于是在 root 用户安装的miniconda3 所以安装路径在/root/miniconda3 里 这导致了环境也是root用户的,会触发告警 WA…

Xilinx原语

1. 原语介绍 原语是 Xilinx 器件底层硬件中的功能模块&#xff0c;它使用专用的资源来实现一系列的功能。相比于 IP 核&#xff0c;原语的调用方法更简单&#xff0c;但是一般只用于实现一些简单的功能。本章主要用到了 BUFG、 BUFIO、 IDDR、 ODDR、IDELAYE2 和 IDELAYCTRL。…

14-29 剑和诗人3 – 利用知识图谱增强 LLM 推理能力

知识图谱提供了一种结构化的方式来表示现实世界的事实及其关系。通过将知识图谱整合到大型语言模型中&#xff0c;我们可以增强它们的事实知识和推理能力。让我们探索如何实现这一点。 知识图谱构建 在利用知识图谱进行语言模型增强之前&#xff0c;我们需要从可靠的来源构建…