大并发下请求合并(并发处理技巧)

news2024/9/27 12:12:15

大并发下请求合并

  • 一次请求消耗的资源
  • 旧的方式
  • 改造后
      • 批量请求处理器
      • 批量请求包装类
      • 使用
  • 性能测试
        • 旧的
        • 改造后的

一次请求消耗的资源

我们经常碰到查询请求的操作,例如根据用户id查询该用户的信息,接口仓储层查询用户正常的做法是通过id去数据库查询该用户id,例如:

where id =xxx

这样的一次请求可能会涉及tomcat、数据库连接池的链接获取,以及可能存在的链接创建和销毁

链接资源是宝贵的,并发比较高的情况1000个请求进来,每一个请求都会去尝试获取数据库链接,打开并执行查询然后关闭。这会导致链接资源挤兑,同时也提高了数据库压力,因此解决以上问题,可以通过批处理请求合并的方式

旧的方式

@GetMapping("/test/user")
public ResponseResult<IamUserContext> user(@RequestParam String loginId) {
return ResponseResult.success(iamUserMapper.selectByPrimaryKey(Long.parseLong(uid)););
}

改造后

批量请求处理器

/**
 * 解决大并发下,各类连接池链接快速消耗及创建销毁带来的性能损耗
 * 将单个请求合并为多个请求,批量请求仓储层,增加并发性能
 * @classDesc: 批量请求合并处理器
 * @author: cyjer
 * @date: 2023/7/25 13:28
 */
@Slf4j
public abstract class BatchCollapseProcessor<T, R> implements InitializingBean {
    private static final ScheduledExecutorService SCHEDULE_EXECUTOR = Executors.newScheduledThreadPool(1);
    private final LinkedBlockingDeque<BatchCollapseRequest<T, R>> batchContainer = new LinkedBlockingDeque<>(defaultCountThreshold() * 5);

    /**
     * 默认数量阈值
     *
     * @return
     */
    int defaultCountThreshold() {
        int maxCountThreshold = 1000;
        if (countThreshold() == null) {
            return 500;
        }
        return Math.min(countThreshold(), maxCountThreshold);
    }

    /**
     * 默认时间阈值
     *
     * @return
     */
    int defaultTimeThresholdMilliSeconds() {
        return timeThresholdMilliSeconds() == null ? 50 : timeThresholdMilliSeconds();
    }

    /**
     * 使用者自定义数量阈值
     *
     * @return
     */
    public abstract Integer countThreshold();

    /**
     * 时间阈值(秒)
     *
     * @return
     */
    public abstract Integer timeThresholdMilliSeconds();

    /**
     * 处理用户请求
     *
     * @param requests
     * @return
     */
    Map<String, R> fetchResult(List<BatchCollapseRequest<T, R>> requests) {
        List<T> reqParam = requests.stream()
                .map(BatchCollapseRequest::getReqParam)
                .collect(Collectors.toList());

        List<R> data = fetchData(reqParam);

        return transferResult(requests, data);
    }

    /**
     * 用户自行实现具体请求方法
     *
     * @param requests
     * @return
     */
    public abstract List<R> fetchData(List<T> requests);

    /**
     * 用户自行实现
     *
     * @param requests
     * @return
     */
    public abstract Map<String, R> transferResult(List<BatchCollapseRequest<T, R>> requests, List<R> res);

    @Override
    public void afterPropertiesSet() {
        SCHEDULE_EXECUTOR.scheduleAtFixedRate(() -> {
            List<BatchCollapseRequest<T, R>> requests = Collections.synchronizedList(new ArrayList<>(defaultCountThreshold()));
            batchContainer.drainTo(requests, defaultCountThreshold());
            if (requests.size() < 1) {
                return;
            }
            log.info("合并处理[{}]个请求", requests.size());
            Map<String, R> response = fetchResult(requests);
            for (BatchCollapseRequest<T, R> request : requests) {
                request.getCompletableFuture().complete(response.get(request.getReqId()));
            }
        }, defaultTimeThresholdMilliSeconds(), defaultTimeThresholdMilliSeconds(), TimeUnit.MILLISECONDS);
    }

    public R doRequest(T input) {
        BatchCollapseRequest<T, R> request = new BatchCollapseRequest<>();
        request.setReqId(IdGenerator.ins().generator().toString());
        CompletableFuture<R> future = new CompletableFuture<>();
        request.setCompletableFuture(future);
        request.setReqParam(input);
        batchContainer.offer(request);
        try {
            return future.get(1, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("合并获取结果异常 error:", e);
            throw BusinessException.build(AdviceErrorCode.OTHER_EX, "系统有点拥堵,请稍后重试~");
        }
    }
}

批量请求包装类

/**
 * @classDesc: 批量请求
 * @author: cyjer
 * @date: 2023/9/20 10:34
 */
@Data
public class BatchCollapseRequest<T, R> {
    /**
     * 请求id 唯一
     */
    private String reqId;
    /**
     * 请求体
     */
    private T reqParam;
    /**
     * 异步处理
     */
    private CompletableFuture<R> completableFuture;
}

使用

@Component
@Slf4j
@RequiredArgsConstructor
public class TestService extends BatchCollapseProcessor<String, IamUserContext> {
    private final IamUserMapper iamUserMapper;
    private final BizConvert bizConvert;


    @Override
    public Integer countThreshold() {
        return null;
    }

    @Override
    public Integer timeThresholdMilliSeconds() {
        return null;
    }

    @Override
    public List<IamUserContext> fetchData(List<String> requests) {
        List<Long> collect = requests.stream().map(Long::parseLong).collect(Collectors.toList());
        List<IamUser> iamUsers = iamUserMapper.selectBatchByPrimaryKey(collect);
        return bizConvert.iamUserPOS2VOS(iamUsers);
    }

    @Override
    public Map<String, IamUserContext> transferResult(List<BatchCollapseRequest<String, IamUserContext>> batchCollapseRequests, List<IamUserContext> res) {
        Map<String, IamUserContext> collect = res.stream()
                .collect(Collectors.toMap(IamUserContext::getUid, Function.identity()));
        Map<String, IamUserContext> result = new HashMap<>();
        batchCollapseRequests.forEach(val -> {
            IamUserContext userContext = collect.getOrDefault(val.getReqParam(), null);
            result.put(val.getReqId(), userContext);
        });
        return result;
    }
}
private final TestService testService;

@GetMapping("/test/user/batch")
public ResponseResult<IamUserContext> batch(@RequestParam String loginId) {
    IamUserContext iamUserContext = testService.doRequest(loginId);
    return ResponseResult.success(iamUserContext);
}

性能测试

旧的

在这里插入图片描述

改造后的

在这里插入图片描述

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

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

相关文章

小县城蔬菜配送小程序

在这个数字化时代&#xff0c;越来越多的人选择在线上购物。而果蔬作为日常生活中不可或缺的一部分&#xff0c;也越来越多的人选择在线上进行购买。那么如何开发一个果蔬配送小程序&#xff0c;轻松开启线上销售呢&#xff1f;下面就让我们来一起探讨一下。 首先&#xff0c;为…

【JAVA-Day30】 为什么稀疏数组能在Java中有效地节省内存空间?

为什么稀疏数组能在Java中有效地节省内存空间&#xff1f; 为什么稀疏数组能在Java中有效地节省内存空间&#xff1f;摘要引言一、什么是稀疏数组二、稀疏数组的应用场景和优势2.1 应用场景2.2 优势 三、如何定义稀疏数组四、总结参考资料 博主 默语带您 Go to New World. ✍ 个…

python项目2to3方案预研

目录 官方工具2to3工具安装参数解释基本使用工具缺陷 future工具安装参数解释基本使用工具缺陷 python-modernize工具安装参数解释基本使用工具缺陷 pyupgrade工具安装参数解释基本使用工具缺陷 对比 官方工具2to3 2to3 是Python官方提供的用于将Python 2代码转换为Python 3代…

滚雪球学Java(24):Java反射

&#x1f3c6;本文收录于「滚雪球学Java」专栏&#xff0c;专业攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎大家关注&&收藏&#xff01;持续更新中&#xff0c;up&#xff01;up&#xff01;up&#xff01;&#xf…

【操作系统笔记】进程和线程

进程的组成 进程要读取 ELF 文件&#xff0c;那么&#xff1a; ① 要知道文件系统的信息&#xff0c;fs_struct② 要知道打开的文件的信息&#xff0c;files_struct 一个进程除了需要读取 ELF 文件外&#xff0c;还可以读取其他的文件中的数据。 进程中肯定有一个 mm_struct…

QGIS怎么修改源代码?持续更新...

修改配置文件保存位置 修改目的&#xff1a;放着和本地安装的其他QGIS共用一份配置文件 修改文件&#xff1a;core/qgsuserprofilemanager.cpp 修改位置&#xff1a;第37行 return basePath QDir::separator() "my_profiles";修改完毕后&#xff0c;再次生成一下…

MYSQL的多版本并发控制MVCC(Multi-Version Concurrency Control)

1、概述 MVCC是一种用于数据库管理系统的并发控制技术&#xff0c;允许多个事务同时访问数据库&#xff0c;而不会导致读写冲突。也就是说在读写的时候&#xff0c;线程不用去争抢读写锁。因为加锁的过程比较耗性能。 当然很多时候还是必须的&#xff0c;不能避免&#xff0c;…

Axure官网下载+使用技巧大揭秘,成为原型设计高手!

Axure 是一款高效的原型设计工具&#xff0c;可以用于创建应用程序和网页的原型、框架图和结构图等。Axure 内置了各种小部件&#xff0c;使用户能够轻松构建动态交互&#xff0c;即使是初学者&#xff0c;也可以在 Axure 中创建复杂、动态且功能丰富的原型&#xff0c;而无需了…

RT Preempt linux学习笔记

RT Preempt linux学习笔记 一、实时操作系统&#xff08;Realtime Operating System&#xff09; 1. 什么是实时操作系统 A real-time system is a time-bound system which has well-defined, fixed time constraints. Processing must be done within the defined constra…

win10系统 C++环境 安装编译GRPC

第一步 下载源码、更新、cmake编译&#xff1a; 为了依赖的成功安装&#xff0c;采用gitee进行下载与更新。记得需要安装git软件。 安装命令&#xff1a; 在自己指定的目录下&#xff0c;鼠标右键&#xff0c;选择 git Bash Here 打开命令行 git clone -b v1.34.0 https://gi…

[激光原理与应用-68]:如何消除50Hz工频干扰和差分信号应对工频干扰

目录 一、什么工频干扰 1.1 什么工频干扰 1.2 工频干扰的幅度 1.3 工频干扰如何进入设备 1.4 工频干扰的负面影响 二、如何消除工频干扰 2.1 要消除工频干扰&#xff0c;可以考虑以下方法&#xff1a; 2.2 要具体消除工频干扰&#xff0c;可以采取以下措施 2.3 使用差…

【探索C语言中VS调试技巧】:提高效率和准确性

文章目录 前言1. 什么是bug&#xff1f;2. 调试是什么&#xff1f;有多重要&#xff1f;2.1 调试是什么&#xff1f;2.2 调试的基本步骤2.3 Debug和Release的介绍 3. Windows环境调试介绍3.1 调试环境的准备3.2 学会快捷键3.3 调试的时候查看程序当前信息3.3.1 查看临时变量的值…

第 4 章 串(串的块链存储实现)

1. 背景说明 该实现和链表的实现极为相似&#xff0c;只是将链接的内存拆分为具体的大小的块。 2. 示例代码 1). status.h /* DataStructure 预定义常量和类型头文件 */#ifndef STATUS_H #define STATUS_H#define CHECK_NULL(pointer) if (!(pointer)) { \printf("FuncN…

Neoj4 cypher脚本基本操作

输入网址后&#xff0c;进入默认数据库 操作一&#xff0c;创建一个节点 CREATE (node:Label {property: value})#解释 CREATE (变量名:类名 {节点属性名: 属性值})#举例 CREATE (node1:Person {name: xiao ming}) CREATE (node2:Person {name: xiao hong}) 操作二&#xff0…

行业追踪,2023-09-20

自动复盘 2023-09-20 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

@Valid注解的作用及@Valid注解与@Validated的区别

1.Valid注解 导入依赖 <dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId></dependency><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate…

【Java 基础篇】Java后台线程和守护线程详解

在Java多线程编程中&#xff0c;有两种特殊类型的线程&#xff1a;后台线程&#xff08;Daemon Thread&#xff09;和守护线程&#xff08;Daemon Thread&#xff09;。这两种线程在一些特定的场景下非常有用&#xff0c;但也需要谨慎使用。本文将详细介绍后台线程和守护线程的…

交换机端口汇聚详解

交换机端口汇聚是一种网络设计技术&#xff0c;用于将多个物理端口汇集成一个逻辑链路&#xff0c;以提供更高的带宽和冗余。通过端口汇聚&#xff0c;可以增加网络的吞吐量&#xff0c;并提高链路的可靠性和可用性。以下是关于交换机端口汇聚的详细介绍&#xff1a; 工作原理&…

【测试开发】用例篇 · 熟悉黑盒测试用例设计方法(1)等价类划分法、边界值法、判定表法

【测试开发】用例篇&#xff08;1&#xff09; 文章目录 【测试开发】用例篇&#xff08;1&#xff09;1. 测试用例的基本要素2. 测试用例的设计方法2.1 基于需求的设计方法&#xff08;设计测试点&#xff09;2.2 等价类划分法&#xff08;测试点>测试用例&#xff09;2.2.…

解锁 zkSync Era:开创全新的 Layer 2 扩展时代

作者: stellafootprint.network 数据来源: zkSync Dashboard 在解决以太坊扩展性问题方面&#xff0c;Layer 2 解决方案备受关注。这些解决方案旨在通过引入 Rollups, State Channels 或 Nested Blockchains 等技术来克服 Layer 1 的局限性。在 Layer 2 扩展领域&#xff0c;…