首页 > 其他分享 >高性能手段之合并请求

高性能手段之合并请求

时间:2024-04-01 12:29:44浏览次数:17  
标签:请求 org 合并 util 高性能 new import orderRequest

上篇文章说到了“三高”系统设计的手段,详情请移步至
如何设计高并发、高性能、高可用的系统

那么这篇文章主要讲讲其中的一个手段----请求合并


文章目录


一、什么是请求合并

请求合并顾名思义就是将客户端产生的多个请求归并到一个一次对下游的请求

二、为什么要进行请求合并

假如有100个订单入库的操作,如果不合并请求的话,即便利用了数据库连接池的技术,也要对数据库操作100次。而这100次请求完全可以合并成一个或者几个请求去操作数据库,以减少对数据库的压力,同时也可以减少连接数据库带来系统损耗,提高网络I/O能力

三、实现

请求合并本身是用时间换空间的思为方法,就是牺牲一定的时长来换取西能提升的一种手段。我们可以用jdk 1.8的 CompleteFuture来实现我们的需求。
基本的实现思路是,单个入库请求过来后给请求添加一个CompleteFuture监听,然后将请求放入一个缓冲队列,然后起一个线程每隔一段时间去队列拉取请求组装成批量参数去请求批量接口。
我们以订单入库为例来实现一下:

package order;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.*;

@Service
public class Order {

    private LinkedBlockingQueue<OrderRequest> orderRequestPool;

    @Autowired
    private RemoteCall remoteCall;

    @PostConstruct
    private void init() {
        orderRequestPool = new LinkedBlockingQueue<>();
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(() -> {
            int size = orderRequestPool.size();
            if (0 == size) {
                return;
            }
            List<Map<String,String>> requests = new ArrayList<Map<String,String>>();
            Map<String,CompletableFuture<Map<String,String>>> futures = new HashMap<>();
            for (int i = 0; i < size; i++) {
                OrderRequest orderRequest = orderRequestPool.poll();
                Map<String,String> map = new HashMap<>();
                map.put("orderNo", orderRequest.getOrderNo());
                map.put("orderId", orderRequest.getOrderId());
                requests.add(map);
                futures.put(orderRequest.getOrderNo(), orderRequest.getFuture());
            }
            List<Map<String,String>> responses = remoteCall.batchRequest(requests);
            for (Map<String,String> response : responses) {
                futures.get(response.get("orderNo")).complete(response);
            }
            System.out.println("共处理请求个数:" + responses.size());
        }, 0, 10, TimeUnit.MILLISECONDS);
    }

    public Map<String,String> addOrder(String orderId) throws ExecutionException, InterruptedException {
        CompletableFuture<Map<String,String>> future = new CompletableFuture<>();
        OrderRequest orderRequest = new OrderRequest();
        orderRequest.setOrderId(orderId);
        orderRequest.setOrderNo(UUID.randomUUID().toString());
        orderRequest.setFuture(future);
        orderRequestPool.add(orderRequest);
        return future.get();
    }

}

addOrder是客户端请求的当入库请求,服务端收到请求后会将请求封装成入库请求放到缓冲队列,并通过future.get来监听返回结果,如果有返回结果立刻返回给客户端。

init方法在spring创建Order Bean调用构造方法后执行,合并逻辑就在这里,每隔10ms就去缓冲队列里取数据(这里没做请求数量限制,一直拿到队列为空为止。可以根据需要限制每次获取的最大请求量),取完数据后调用远程批量接口。

远程批量接口如下:

package order;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class RemoteCall {
    private AtomicLong atomicCounter;
    private final static Logger logger = LoggerFactory.getLogger(RemoteCall.class);
    @PostConstruct
    private void init() {
        atomicCounter = new AtomicLong(0);
    }
    public List<Map<String,String>> batchRequest(List<Map<String,String>> requests) {
        long currentNo = atomicCounter.addAndGet(1);
        logger.info("批量接口被调用了,当前是第{}次调用,本次请求共合并{}个", currentNo, requests.size());

        List<Map<String,String>> response = new ArrayList<>();
        for (int i = 0; i < requests.size(); i++) {
            Map<String, String> map = new HashMap<String,String>();
            map.put("orderNo", requests.get(i).get("orderNo"));
            map.put("orderId",i + "");
            response.add(map);
        }
        return response;
    }
}

atomicCounter用来统计请求的次数,为了方便这里就mock了接口来模拟数据库的操作,这里直接返回操作结果。

我们来写个单元测试来测试一下这个接口,模拟1000个并发来看看实际操作数据库的次数:

package order;

import main.ApplicationStart;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationStart.class)
public class OrderTest {
    @Autowired
    private Order order;

    @Test
    public void testAddOrder() throws InterruptedException {
        int count = 1000;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        for (int i = 0; i < count; i++) {
            int finalI = i;
            Thread t = new Thread(() -> {
                try {
                    countDownLatch.countDown();
                    countDownLatch.await();
                    Map<String,String> response = order.addOrder(finalI + "");
                } catch (ExecutionException e) {
                    throw new RuntimeException(e);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
        Thread.sleep(2000);
    }
}

下面是执行结果:
在这里插入图片描述
如上图:本来是请求1000次的数据库调用最后只调用了6次。当然用CompleteFuture实现也有问题,它不支持超时机制,假如下游接口挂掉了,它会一直在等待接口返回。可以用LinkBlockQueue来替换CompleteFuture,LinkBlockQueue的poll提供了一个超时机制,可以自行去实现一下,这里不再赘述

总结

请求合并本质上是时间换空间(性能)的一个思路,利用时间的消耗来积累请求并实现合并请求提高性能的目的,本身也是一种优化手段。有人会说时间延长时间不是让请求时间处理变长了吗?未必,这要结合业务场景以及业务量来综合考量。时间换空间、空间换时间最终的目的都是提升我们系统性能的手段,没有哪个是绝对好的,我们要综合考虑,结合系统现状看哪个带来的收益大就用哪个手段

标签:请求,org,合并,util,高性能,new,import,orderRequest
From: https://blog.csdn.net/am_danger/article/details/137222938

相关文章

  • 【前端面试3+1】07vue2和vue3的区别、vue3响应原理及为什么使用proxy、vue的生命周期
    一、vue2和vue3的区别1.性能优化:        Vue3在性能方面有很大的提升,主要是通过虚拟DOM的优化和响应式系统的改进实现的。虚拟DOM重构:Vue3中对虚拟DOM进行了重构,使得更新算法更加高效,减少了更新时的开销,提升了性能。静态树提升:Vue3可以通过静态树提升技术......
  • springboot 监听请求
    加个这个类就可以了importorg.springframework.stereotype.Component;importjavax.servlet.*;importjavax.servlet.http.HttpServletRequest;importjava.io.BufferedReader;importjava.io.IOException;@ComponentpublicclassLoggingFilterimplementsFilter{@Overri......
  • 代码随想录算法训练营第36天| 435. 无重叠区间、763.划分字母区间、56. 合并区间
    435.无重叠区间题目链接:无重叠区间题目描述:给定一个区间的集合intervals,其中intervals[i]=[starti,endi]。返回需要移除区间的最小数量,使剩余区间互不重叠。解题思想:这道题目和射气球很像。*“需要移除区间的最小数量,使剩余区间互不重叠”*等效于求重叠区......
  • 蓝桥杯2020年第十三届省赛真题-合并检查
    一、题目合并检测新冠疫情由新冠病毒引起,最近在A国蔓延,为了尽快控制疫情,A国准备给大量民众进病毒核酸检测。然而,用于检测的试剂盒紧缺。为了解决这一困难,科学家想了一个办法:合并检测。即将从多个人(k个)采集的标本放到同一个试剂盒中进行检测。如果结果为阴性,则说明这k......
  • AcWing刷题-区间合并
    校门外的树区间合并:fromtypingimportListdefmerge(intervals:List[List[int]])->List[List[int]]:#按照第一个元素从小到大进行排序intervals.sort(key=lambdax:x[0])#初始化一个新的数组new_list=list()foriinintervals:......
  • 【Java编程】【算法面试题】【数组合并】以数组 intervals 表示若干个区间的集合,其中
    原始题目:以数组intervals表示若干个区间的集合,其中单个区间为intervals[i]=[starti,endi]。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。......
  • 为什么Java接口接收请求整型数据需要使用包装类Long
    在Java中,接口(Interface)是抽象方法的集合,它定义了对象之间交互的契约。但是,接口本身并不直接处理数据。当你说“Java接口接收请求整型数据需要使用包装类Long”,我猜你可能是指在某个方法签名中,接口定义了一个接收Long类型参数的方法,而不是基本类型int。原因主要有以下几点:可......
  • 优雅的发送http请求
    做项目的时候,请求第三方接口是常用的功能:一、get常用的方法及传参1、使用占位符:适用于参数已知Stringurl="https://q.stock.sohu.com/hisHq?code={code}8&start={start}&end={end}&stat={stat}&order={order}&period={period}&callback={callback}&rt={rt}";Map<......
  • 如果有100个请求,如何控制并发?
    题目现有100个请求需要发送,请设计一个算法,使用Promise来控制并发(并发数量最大为10),来完成100个请求;首先先模拟下100个请求://请求列表constrequestList=[];//为了方便查看,i从1开始计数for(leti=1;i<=100;i++){requestList.push(()=>new......
  • 说说 HTTP 常见的请求头有哪些? 作用?
    一、是什么HTTP头字段(HTTPheaderfields),是指在超文本传输协议(HTTP)的请求和响应消息中的消息头部分它们定义了一个超文本传输协议事务中的操作参数HTTP头部字段可以自己根据需要定义,因此可能在 Web服务器和浏览器上发现非标准的头字段下面是一个HTTP请求的请求头:GET/hom......