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

2023-11-12

一次请求消耗的资源

我们经常碰到查询请求的操作,例如根据用户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);
}

性能测试

旧的

在这里插入图片描述

改造后的

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

大并发下请求合并(并发处理技巧) 的相关文章

随机推荐

  • buuctf [struts2]s2-009

    漏洞描述 这个漏洞跟s2 003 s2 005 属于一套的 Struts2对s2 003的修复方法是禁止 号 于是s2 005通过使用编码 u0023或 43来绕过 于是Struts2对s2 005的修复方法是禁止 等特殊符号 使用户不能提
  • vue-router 基本使用

    路由 其实就是指向的意思 当我点击页面上的home按钮时 页面中就要显示home的内容 如果点击页面上的about 按钮 页面中就要显示about 的内容 Home按钮 gt home 内容 about按钮 gt about 内容 也可以说
  • xp无法远程计算机共享,解决XP局域网共享不能访问的问题

    1 检查guest帐户是否开启 XP默认情况下是不开启guest帐户的 因些为了其他的人能浏览你的计算机 请启用guest帐户 为了安全请为guest设置密码或相应的权限 你也可以为每一台机器设置一个用户名和密码以便计算机之间的互相访问 2
  • 面向数据流的方法设计系统的软件结构(储蓄系统)

    RT 事务流
  • mongodb安装

    mongodb安装 提示 ubuntu 18 04 mongodb 4 0 28 文章目录 mongodb安装 前言 一 下载解压安装包 二 在 etc profile文件中添加如下内容 生效环境变量 三 创建数据库目录 前言 MongoD
  • 面试总结-2023届安全面试题总汇

    2023届安全面试题总汇 文章目录 2023届安全面试题总汇 前言 0x01 秋招目录 随时更新 0x02 各大安全厂商面试题 资料链接 一个2023届毕业生在毕业前持续更新 收集的安全岗面试题及面试经验分享 前言 最近发现一个宝贵的面试文
  • vector的find用法

    一 find函数存在于算法中 其头文件为 include
  • 驱动 - platform总线驱动

    include
  • 怎么求解100个正整数的最大公约数python

    答 你可以使用Python中的fractions模块来求解100个正整数的最大公约数 你需要先导入它 import fractions 然后你可以使用fractions gcd函数来求解 fractions gcd 100 200
  • Codeforces#808(Div.2)A-D题解

    目录 A Difference Operations B Difference of GCDs C Doremy s IQ D Difference Array A Difference Operations Problem A Codef
  • 2019年7款3D扫描仪APP(Android和iOS),让你手机秒变3D扫描仪!

    在我之前的一篇文章 教程 SolidWorks与3D扫描技术不得不说的故事 中 提到了SolidWorks和3D扫描技术之间的完美合作 今天就继续围绕3D扫描话题 为大家分享7款2019年3D扫描仪APP Android和iOS 喜欢就继续
  • Linux 解决sudo后接命令,仍旧权限不足的问题

    将本想执行的 sudo echo aa gt gt root text txt 改为 sudo sh c echo aa gt gt root text txt
  • Docker 1.9的新网络特性,以及Overlay详解

    本文转载自灵雀云技术博客 原文链接 http www alauda cn 2016 01 18 docker 1 9 network 作者简介 林帆 ThoughtWorks公司软件工程师及DevOps咨询师 具有丰富的持续交付和服务器运维
  • CCF 2104年3月第一题--相反数(java)

    代码如下 package com hsx ccf import java util Scanner public class Ccf20140301 public static void main String args SuppressW
  • Spring Cloud中的Hystrix的实现和使用

    Spring Cloud Hystrix 是 Spring Cloud 生态系统中的一个断路器组件 它可以帮助开发者优雅地处理分布式系统中的故障 提高系统的容错能力 下面介绍 Spring Cloud Hystrix 的实现和使用 引入依赖
  • QT获取lineEdit内容并写入文件中

    在ui中创建一个lineEdit 然后 QString sss ui gt lineEdit gt text 这样就获得了lineEdit的内容 并转为了QString格式 接下来参考 https editor csdn net md ar
  • 残差网络模型

    1 原始残差网络 最基本的残差块 中间的两层神经网络学习输入输出之间的残差 而旁边的链接就像一个高速公路 使得反向传播算法中的残差能通过这条路传到前边去 当网络变深时可以使得中间的输出为0 那么网络就能自适应的变成一个浅一点的网络 左边ba
  • Java 根据EXCEL下标获取EXCEL的列名

    通过根据EXCEL下标获取EXCEL的列名 用于给单元格设置公式用 num 是以0开头的下标 public static String getExcelColumn Integer num if num null return null S
  • 树的概念:层次、高度、深度、宽度

    目录 层次 宽度 深度 高度 其中只有层次是树原生的概念 其他都是从树中的结点来的 层次 从根节点开始算起 根节点算第一层 如图所示的树 第1层 A 第2层 B C 第3层 D E F 第4层 G H I 宽度 其实就是度 把结点的子树的棵
  • 大并发下请求合并(并发处理技巧)

    大并发下请求合并 一次请求消耗的资源 旧的方式 改造后 批量请求处理器 批量请求包装类 使用 性能测试 旧的 改造后的 一次请求消耗的资源 我们经常碰到查询请求的操作 例如根据用户id查询该用户的信息 接口仓储层查询用户正常的做法是通过id