Mock模拟数据动态字节码编译插件优化

news2024/9/22 12:21:09

模块介绍

dmc-plugin-java

动态编译字节码

关于动态编译字节码技术参考:

https://blog.csdn.net/huxiang19851114/article/details/127881616

优化如下:

  • 动态文本类改为界面配置及数据库保存
    请添加图片描述

    数据库表结构:

    DROP TABLE IF EXISTS `compiler_info`;
    CREATE TABLE `compiler_info`  (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` varchar(100) NOT NULL COMMENT '最后更新用户id',
      `class_name` varchar(100) NOT NULL COMMENT '类全路径名,如com.paratera.console.biz.model.User(唯一)',
      `info` text NOT NULL COMMENT '动态类内容',
      `description` varchar(200) COMMENT '动态类说明',
      `sign` varchar(100) COMMENT '签名标记',
      `old_sign` varchar(100) COMMENT '上一次签名标记',
      `create_at` datetime NULL ,
      `updated_at` datetime NULL ,
      PRIMARY KEY (`id`) USING BTREE,
      UNIQUE INDEX `class_name`(`class_name`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact COMMENT = '字节码动态编译配置表';
    
  • 类加载对象引入缓存机制

通过compiler_info.sign签名标记(其实就是一个UUID)来判断,避免频繁调用生成大量的Object执行对象。

核心代码如下:

/**
 * 类加载对象缓存,key:compiler_info.sign
 */
private static Map<String,Class> classMap = new HashMap<>();

/**
 * Class执行对象缓存 key:compiler_info.sign
 */
private static Map<String,Object> objMap = new HashMap<>();

------------------------------------------------------------------------------
/**
  * 目标方法反射执行
  *
  * @param className 全路径类名,与字节码文本配置的保持一致
  * @param methodName 执行方法名,与需要调度的字节码文本类方法名保持一致
  * @param args 方法参数,可以多个,根据字节码文本类具体情况来传
  * @return
  *
  * @throws Exception
*/
public Object invoke(String className, String methodName, Object... args) throws Exception {
    //获取字符串代码内容
    Map compilerInfo = getCompilerInfo(className);
    String sign = (String) compilerInfo.get("sign");
    Class<?> clazz = null;
    Object obj = null;
    if(classMap.containsKey(sign)){
        clazz = classMap.get(sign);
        obj = objMap.get(sign);
    }else {
        try {
            //字节码编译处理,得到Class对象和执行对象
            clazz = this.compileToClass(className, (String) compilerInfo.get("info"));
            obj = clazz.getDeclaredConstructor().newInstance();
            classMap.put(sign,clazz);
            objMap.put(sign,obj);
            String oldSign = (String) compilerInfo.get("oldSign");
            classMap.remove(oldSign);
            objMap.remove(oldSign);
        } catch (Exception e) {
            throw new Exception("反射获取对象实例异常:" + e.getMessage());
        }
    }
    //反射调用目标方法
    Method[] test = clazz.getDeclaredMethods();
    List<Method> methods = Arrays.stream(test).filter(app ->
                                                      StringUtils.equals(app.getName(), methodName)).toList();
    try {
        return methods.get(0).invoke(obj, args);
    } catch (Exception e) {
        throw new Exception("没有该动态编译运行方法或则参数不匹配");
    }
}

Mock模拟测试数据

基于使用Spring MVC拦截器的方式弊端:

1、request body只能getReader()、getInputStream()一次,不重写preHandle后执行目标方法会报错

2、拦截器只能限定在项目内部使用,做成jar包插件无法实现拦截(已实验证明)

所以改成通过Filter过滤器来处理,Filter处理当然也有他的弊端,那就是任何请求都会进入doFilter方法,如果不做好排除判断,性能是比较低的

参考之前关于Mock模拟测试数据文章:

https://blog.csdn.net/huxiang19851114/article/details/127771679

我们做了如下优化:

  • 模拟数据改为界面配置及数据库保存

    主要增加了服务名称,用来进行服务隔离(避免大家的测试地址相同)

在这里插入图片描述

数据库表结构:

DROP TABLE IF EXISTS `mock_info`;
CREATE TABLE `mock_info`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `on_off` varchar(6) NOT NULL COMMENT '开关,ON 开启,OFF 关闭',
  `server` varchar(50) NOT NULL COMMENT '服务名称,与引用服务配置的dmc.space相同',
  `method` varchar(20) NOT NULL COMMENT '方法类型 请使用全大写,如GET,POST,PUT等',
  `uri` varchar(255) NOT NULL COMMENT '访问路径',
  `profile` varchar(20) NOT NULL COMMENT 'Mock数据环境',
  `user_id` varchar(100) NOT NULL COMMENT '配置用户id',
  `create_at` datetime NULL ,
  `updated_at` datetime NULL ,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact COMMENT = 'mock模拟数据主表';

DROP TABLE IF EXISTS `mock_detail`;
CREATE TABLE `mock_detail`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `info_id` int(11) NOT NULL COMMENT 'mock_info表id',
  `header` varchar(500) COMMENT 'header传参,JSON结构字符串',
  `param` varchar(500) COMMENT 'param传参,JSON结构字符串',
  `body` varchar(500) COMMENT 'body传参,JSON结构字符串',
  `value` text COMMENT 'response返回,字符串,不限格式',
  `create_at` datetime NULL ,
  `updated_at` datetime NULL ,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact COMMENT = 'mock模拟数据入参返回明细表';
  • 拦截器实现

    实现逻辑大同小异,主要增加了dmc.space来作为开关,确定是否开启Mock模拟测试

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.paratera.dmc.plugin.facade.DmcClient;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.util.CollectionUtils;
    import org.springframework.web.filter.GenericFilterBean;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.concurrent.atomic.AtomicReference;
    
    
    /**
     * Mock模拟数据服务 过滤器
     */
    @ConditionalOnExpression("#{environment.getProperty('dmc.space') != null}")
    @Configuration
    @Slf4j
    public class MockFilter extends GenericFilterBean implements Ordered {
    
        @Autowired
        private DmcClient dmcClient;
    
        @Value("${spring.profiles.active}")
        private String profile;
    
        @Value("${dmc.space}")
        private String server;
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
                throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            String methodType = request.getMethod();
            String uri = request.getRequestURI();
            //如果发现是css、js、图片文件以及mock请求(避免dmcClient 请求进入死循环),直接放行
            if (uri.contains(".css") || uri.contains(".js") || uri.contains(".png")
                    || uri.contains(".jpg") || uri.indexOf("/s-api") != -1) {
                filterChain.doFilter(request, response);
                return;
            }
            //兼容dmc服务本身请求不带server参数
            server = StringUtils.isEmpty(server) ? "dmc" : server;
            ObjectMapper mapper = new ObjectMapper();
            //获取模拟数据配置信息
            Map mockInfo = dmcClient.getMockInfo(server, methodType, uri, profile);
    
            //如果没有录入该数据,则放过
            if (mockInfo == null) {
                filterChain.doFilter(request, response);
                return;
            }
            //判断开关是否开启,未开启,放过
            if (!StringUtils.equals("ON", (CharSequence) mockInfo.get("onOff"))) {
                filterChain.doFilter(request, response);
                return;
            }
            List<Map> mockDetails = (List<Map>) mockInfo.get("info");
            //如果没配置出入参详情,则直接返回空
            if (CollectionUtils.isEmpty(mockDetails)) {
                response.getWriter().write("");
                return;
            }
    
            //获取请求的header参数
            Enumeration<String> headerNames = request.getHeaderNames();
            Map<String, String> headers = new HashMap();
            while (headerNames.hasMoreElements()) {
                String nextElement = headerNames.nextElement();
                headers.put(nextElement, request.getHeader(nextElement));
            }
            //获取请求的param参数
            Map<String, String> params = new HashMap();
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String nextElement = parameterNames.nextElement();
                params.put(nextElement, request.getParameter(nextElement));
            }
            //获取请求的body参数
            Map<String, String> bodys = new HashMap();
            BufferedReader br;
            try {
                //这个地方重写了getInputStream和getReader方法,否则会报错
                br = request.getReader();
                String str;
                String wholeParams = "";
                while ((str = br.readLine()) != null) {
                    wholeParams += str;
                }
                if (StringUtils.isNotBlank(wholeParams)) {
                    bodys = mapper.readValue(wholeParams, Map.class);
                }
            } catch (IOException e) {
                log.error("IO流异常-1:" + e.getMessage());
            }
            Map<String, String> finalBodys = new HashMap<>();
            for (String s : bodys.keySet()) {
                finalBodys.put(s, String.valueOf(bodys.get(s)));
            }
    
            //开始匹配传参
            AtomicReference<String> value = new AtomicReference<>();
            AtomicBoolean flag = new AtomicBoolean(true);
            //遍历每条Mock配置信息
            mockDetails.stream().forEach(app -> {
                //比对请求参数和配置参数,如果请求参数包含配置参数,输出value
                AtomicBoolean headerFlag = new AtomicBoolean(true);
                AtomicBoolean paramFlag = new AtomicBoolean(true);
                AtomicBoolean bodyFlag = new AtomicBoolean(true);
                Map header = (Map) app.get("header");
                Map param = (Map) app.get("param");
                Map body = (Map) app.get("body");
    
                //判断header
                if (header != null) {
                    header.keySet().stream().forEach(app3 -> {
                        //传参header只要有一项不包含配置项中的header,则退出
                        if (!headers.containsKey(app3)
                                || !headers.containsValue(String.valueOf(header.get(app3)))) {
                            headerFlag.set(false);
                            return;
                        }
                    });
                }
                //判断param
                if (param != null) {
                    param.keySet().stream().forEach(app3 -> {
                        if (!params.containsKey(app3) || !params.containsValue(String.valueOf(param.get(app3)))) {
                            paramFlag.set(false);
                            return;
                        }
                    });
                }
                //判断body
                if (body != null) {
                    body.keySet().stream().forEach(app3 -> {
                        if (!finalBodys.containsKey(app3)
                                || !finalBodys.containsValue(String.valueOf(body.get(app3)))) {
                            bodyFlag.set(false);
                            return;
                        }
                    });
                }
    
                //条件都满足,获取配置项value,设置flag为false,拦截器生效,不再访问目标接口,同时退出循环
                if (headerFlag.get() && paramFlag.get() && bodyFlag.get()) {
                    flag.set(false);
                    value.set(StringUtils.isEmpty((String) app.get("value")) ? "" : (String) app.get("value"));
                    return;
                }
            });
            //接口输出配置项value
            if (!flag.get()) {
                try {
                    response.getWriter().write(value.get());
                    return;
                } catch (IOException e) {
                    log.error("IO流异常-2:" + e.getMessage());
                }
            }
            filterChain.doFilter(request, response);
        }
    
        @Override
        public int getOrder() {
            return Ordered.LOWEST_PRECEDENCE;
        }
    }
    

存储数据获取

因为我们的数据中心在dmc-core,所以使用openfeign,增加一个对外访问dmc-core数据存储的接口

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

/**
 * dmc远程调用
 *
 * @author huxiang
 */

@FeignClient(name = "dmcClient", url = "${dmc.mcs.url.dmc-core}")
public interface DmcClient {

    @GetMapping("/mock/s-api/info")
    public Map getMockInfo(@RequestParam("server") String server,
                           @RequestParam("method") String method,
                           @RequestParam("uri") String uri,
                           @RequestParam("profile") String profile);

    @GetMapping("/compiler/s-api/info")
    public Map getCompilerInfo(@RequestParam("className") String className);
}

dmc-core

插件数据的维护中心,主要就是围绕上面的表进行增删改查,方便用户可视化操作,没什么可说的

dmc-dict

维护数据平台一些公用并且固定的数据项,关于数据字典的实现可以参考:

https://blog.csdn.net/huxiang19851114/article/details/127556003

使用说明

插件引入配置

1、引入jar包依赖

<dependency>
    <groupId>com.paratera.dmc</groupId>
    <artifactId>dmc-plugin-java</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

2、配置扫描路径

@SpringBootApplication(scanBasePackages = {"com.paratera.dmc"})
@EnableFeignClients(basePackages = {"com.paratera.dmc"})

3、配置服务空间

根据自己服务名称配置,注意与界面数据维护中server保持一致(取数据字典值),如:

dmc.space=Console

4、配置dmc-core服务地址

dmc.mcs.url.dmc-core=http://localhost:25000

使用范例

1、Mock模拟数据

实现无需后台提供接口,前端通过配置化实现接口模拟数据,无阻塞开发。

  • 配置模拟数据主题,包括服务名,请求方式,接口路径,生效环境

在这里插入图片描述

  • 配置request和response,可以配置0-N组,request传参为JSON格式字符串,response为任意字符串

在这里插入图片描述

  • 测试,如果请求的主题和入参都匹配,返回结果,如:

    @Test
    public void wheAgentByUserId() throws Exception {
        String url = "/sys/wheAgentByUserId";
        MvcResult mvcResult = mockMvc
            .perform(MockMvcRequestBuilders.get(url)
                     .contentType(MediaType.APPLICATION_JSON)
                     .header("userid", "SELF-rQYRjc9mfQPP7F7apE8gGx6xyAAZYw6GNCTjS6wKPH8")
                    ).andReturn();
        int status = mvcResult.getResponse().getStatus();
        System.out.println("请求状态码:" + status);
        String result = mvcResult.getResponse().getContentAsString();
        System.out.println("接口返回结果:" + result);
    }
    

在这里插入图片描述

如果匹配不到配置项,则继续进入项目访问目标方法,目标方法不存在,则报404

2、字节码动态编译

  • 配置动态类文本信息

在这里插入图片描述

  • 调用动态编译的方法执行解析
@Autowired
private DynamicCompiler dynamicCompiler;

@Test
public void getQuota1() throws Exception {
    Object obj = dynamicCompiler.invoke("com.paratera.dmc.core.service.clustercoop.CLusterService","getQuota", "sc001");
                                        System.out.println(obj);
                                        }

在这里插入图片描述

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

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

相关文章

leetcode刷题 log day56(编辑距离总结篇~

583. 两个字符串的删除操作 【思路】这道题只有删除操作&#xff0c;两个字符串相等时&#xff0c;步数不变&#xff0c;不相等时&#xff0c;只能做删除操作&#xff0c;删除有三种情况&#xff1a;删除 word1 或删除 word2 或者两个字符串都删除&#xff0c;取三种情况的最小…

sytem clock for ctrl ms task and us/ms delay

Cortex-M3 的内核中包含一个 SysTick 时钟。SysTick 为一个 24 位递减计数器&#xff0c;SysTick 设定初值并使能后&#xff0c;每经过 1 个系统时钟周期&#xff0c;计数值就减 1。计数到 0 时&#xff0c;SysTick 计数器自动重装初值并继续计数&#xff0c;同时内部的 COUNTF…

网页性能优化

网页性能优化 文章目录网页性能优化[TOC](文章目录)前言1.前端脚本优化1.1 减少重绘、回流1.2 缓存dom选择与计算1.3 使用事件委托而不是批量绑定2.渲染优化2.1 使用CSS3开启GPU硬件加速提升网站动画渲染性能2.2 touchmove、scroll、input事件添加防抖3.加载优化3.1 合并小图片…

怎么修改图片分辨率提高清晰度?如何调整图片dpi分辨率?

下载的图片有时候会比较模糊&#xff0c;想要改变图片清晰度的话就需要调整图片分辨率&#xff0c;很多小伙伴都不知道怎么去修改分辨率&#xff08;在线修改照片分辨率&#xff08;DPI&#xff09; 调整图片DPI 照片dpi修改工具-压缩图&#xff09;。今天小编就教大家一个非常…

关系抽取(三)实体关系联合抽取:TPlinker

参考&#xff1a; NLP系列之封闭域联合抽取&#xff1a;CasRel、TPLinker、PRGC、PURE、OneRel&#xff0c;实在是太卷了&#xff01; - 知乎 (zhihu.com)NLP 关系抽取 — 概念、入门、论文、总结 TPlinker 论文&#xff1a;PLinker: Single-stage Joint Extraction of Entit…

乐观锁思想在 JAVA 中的实现——CAS

前言 生活中我们看待一个事物总有不同的态度&#xff0c;比如半瓶水&#xff0c;悲观的人会觉得只有半瓶水了&#xff0c;而乐观的人则会认为还有半瓶水呢。很多技术思想往往源于生活&#xff0c;因此在多个线程并发访问数据的时候&#xff0c;有了悲观锁和乐观锁。 悲观锁认为…

FinClip11月产品更新:FIDE 插件开发功能优化;开发者文档英文版上线

不知不觉 22 年进入尾声&#xff0c;通过一年的不断打磨&#xff0c;FinClip 也在不断成长&#xff0c;现在&#xff0c;让我们看看过去的 11 月&#xff0c;FinClip 又有了哪些新的变化。 产品方面的相关动向&#x1f447;&#x1f447;&#x1f447; FIDE 插件开发功能优化…

【LeetCode每日一题:1775. 通过最少操作次数使数组的和相等~~~贪心+思维题】

题目描述 给你两个长度可能不等的整数数组 nums1 和 nums2 。两个数组中的所有值都在 1 到 6 之间&#xff08;包含 1 和 6&#xff09;。 每次操作中&#xff0c;你可以选择 任意 数组中的任意一个整数&#xff0c;将它变成 1 到 6 之间 任意 的值&#xff08;包含 1 和 6&a…

另一种在ARM/x86架构处理器上部署WebDAV服务器的方法

引言 最近搞了个矿渣&#xff0c;处理器是国产的RK3328&#xff0c;四核A53架构&#xff0c;64位的&#xff0c;性能太好了&#xff0c;装了个OpenWRT&#xff0c;想用来当nas用&#xff0c;但是我发现&#xff0c;竟然没有合适的文件服务器&#xff0c;局域网内用SMB确实可以…

Java并发编程—CompletableFuture的常用方法(建议收藏)

在过去的一段时间里&#xff0c;博主一直在给大家分享多线程并发编程里面的关键CompletableFfuture类的各种技术点&#xff0c;并发编程作为java开发里面关键点之一&#xff0c;也是大家向上提升重要的一点&#xff1b;对于CompletableFuture的学习一定要学到位&#xff0c;前面…

盘点 JDK 中基于 CAS 实现的原子类

前言 JDK 中提供了一系列的基于 CAS 实现的原子类&#xff0c;CAS 的全称是Compare-And-Swap&#xff0c;底层是lock cmpxchg指令&#xff0c;可以在单核和多核 CPU 下都能够保证比较交换的原子性。所以说&#xff0c;这些原子类都是线程安全的&#xff0c;而且是无锁并发&…

第4季5:图像sensor的驱动源码的编译

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 本文演示如何修改sensor的驱动源码&#xff0c;修改之后如何编译与运行。 一、sensor的注册接口分析 这部分内容见第4季4&#xff1a;图像sensor的驱动源码解析。 二、黑电平 关于黑电平的概念&a…

[附源码]计算机毕业设计抗疫医疗用品销售平台Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【电巢】电源管理芯片:国产化替代厂家竞逐千亿黄金赛道

前言 整个2022年三季度&#xff0c;全国新能源电动车的起火已高达600多起&#xff0c;同比上升了30%多&#xff0c;如果具体到每天来看&#xff0c;平均每天都有超过7起新能源电动车火灾发生。 7月22日&#xff0c;台湾省专业赛车手林某颖驾驶着一辆白色特斯拉Model X&#xff…

es与mysql之间的数据同步

常见的数据同步方案有三种&#xff1a; 同步调用 异步通知 监听binlog 方案一&#xff1a;同步调用&#xff08;基本不会用&#xff0c;问题太多&#xff09; 基本步骤如下&#xff1a; hotel-demo对外提供接口&#xff0c;用来修改elasticsearch中的数据 酒店管理服务在完成…

活动预告 | DataFunSummit 2022-MLOps 及 AI 工程化落地论坛

11月19日 13&#xff1a;30-17:30&#xff0c;OpenMLDB 项目发起人、第四范式技术副总裁 郑曌作为出品人发起 DataFunSummit 2022 MLOps 及 AI 工程化落地论坛。OpenMLDB PMC、第四范式资深系统架构科师 卢冕&#xff0c;将在论坛中为大家带来议题为《开源机器学习数据库 OpenM…

吃透阿里P8推荐424页Java服务端研发知识图谱后,直接入职蚂蚁P6

前言 蓦然回首自己做开发已经十年了&#xff0c;这十年中我获得了很多&#xff0c;技术能力、培训、出国、大公司的经历&#xff0c;还有很多很好的朋友。但再仔细一想&#xff0c;这十年中我至少浪费了五年时间&#xff0c;这五年可以足够让自己成长为一个优秀的程序员&#…

看场景、重实操,实时数仓不是“纸上谈兵”

本文转载自阿里云Hologres产品负责人合一在ITPUB的访谈&#xff0c;谈谈他眼中的实时数仓&#xff0c; 原文链接&#xff1a; https://mp.weixin.qq.com/s/RZMWf9r4fKV9mNoGGUtaVw 这两年&#xff0c;企业IT领域掀起实时数仓热潮。然而&#xff0c;只要稍做梳理就会发现&#…

【笔试强训】Day 6

&#x1f308;欢迎来到笔试强训专栏 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句鸡汤&#x…

Redis - Redis为什么这么快?

1. Redis为什么这么快&#xff1f; 数据结构简单&#xff0c;对数据操作也简单&#xff0c;Redis 中的数据结构是专门进行设计的&#xff1b;基于内存实现&#xff0c;读写速度快&#xff1b;Redis 是单线程的&#xff0c;避免了不必要的上下文切换和竞争条件&#xff0c;也不…