记一次springcloud多线程请求API的实践

针对SpringCloud架构下服务调用耗时问题,本文介绍了通过多线程和线程池进行优化的实践方案。首先避免在Service层直接配置线程池,然后创建异步线程池和IO密集型任务线程池。接着,定义Callable任务包装类,并使用while循环分批查询,每次查询2000个订单号。最终,通过Future获取多线程执行结果,显著降低了请求时间从20-30s降至5-7s。
摘要由CSDN通过智能技术生成

前因

最近在做一些业务数据大屏统计,springcloud 架构,统计代码中需要调用别的服务获取数据,在传统的同步请求下获取所有的数据需要20-30s,有时候时间更长还会导致超时,故想到了使用mq和多线程2种解决方案。

由于任务比较急,而且别的服务并不是自己项目组负责,所以使用mq的方式就显得麻烦许多,最后评估后决定使用多线程的请求方式进行请求拆解优化,将一次请求分成多次请求,每次携带的一定个数的参数,使用多线程后,请求时间由20-30s降到了5-7s。

下面讲解大概的实现流程。

 

实现

1、避免在service层直接定义线程池,手动配置一个线程池。

package com.xxx.xxx.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * 异步线程池配置
 * 可直接注入xxxExecutor,或者通过@Async("xxxExecutor")引用
 *
 * @author TianXieZuoMaiKong
 */
@Slf4j
@EnableAsync
@Configuration
public class ExecutorConfig implements AsyncConfigurer {
    /**
     * 阻塞因子
     */
    private static final double BLOCKING_COEFFICIENT = 0.9;

    private static final int CPU_PROCESSORS_COUNT = Runtime.getRuntime().availableProcessors();

    /**
     * 默认连接池
     * 用于CPU密集型任务
     */
    @Bean("taskExecutor")
    @Override
    public TaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("thread-async-");
        executor.setCorePoolSize(CPU_PROCESSORS_COUNT + 1);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.initialize();
        return executor;
    }

    /**
     * 耗时任务连接池
     * 用于IO密集型任务
     */
    @Bean("threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor timeConsumingExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("thread-io-");
        executor.setCorePoolSize(CPU_PROCESSORS_COUNT);
        executor.setMaxPoolSize((int) (CPU_PROCESSORS_COUNT / (1 - BLOCKING_COEFFICIENT)));
        executor.setQueueCapacity(200);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return ((throwable, method, objects) -> log.error("Occurring an exception during async task [{}] invocation!\n{}", method.getName(), throwable.getMessage()));
    }
}

2、定义一个任务包装类,实现Callable接口并重写其call方法,用于回调接收查询返回数据。

package com.xxx.xxx.pojo;

import com.xxx.xxx.api.OrderServiceApi;

import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * 根据订单号获取订单信息 handler
 *
 * @author TianXieZuoMaiKong
 */
public class OrderInfoHandler implements Callable<List<Map<String, Object>>> {
    private Map<String, Object> paramMap;
    private OrderServiceApi serviceApi;

    /**
     * 多参构造函数
     *
     * @param paramMap      订单号map参数。一次查询2000个
     * @param serviceApi serviceApi,这里不想再通过反射去拿了,所以干脆当参数传进来
     */
    public OrderInfoHandler(Map<String, Object> paramMap, OrderServiceApi serviceApi) {
        this.paramMap = paramMap;
        this.serviceApi = serviceApi;
    }

    @Override
    public List<Map<String, Object>> call() {
        return serviceApi.findOrderInfo(paramMap);
    }
}

3、使用while循环拆解参数个数,一次传2000个订单号查询,订单号循环传完为止。

import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.xxx.xxx.api.OrderServiceApi;


/**
 * 业务处理类
 *
 * @author TianXieZuoMaiKong
 */
@Slf4j
@AllArgsConstructor
@Service
public class DataScreenService {
    private final OrderServiceApi orderServiceApi;
    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
    

    /**
     * 批量查询订单信息
     *
     * @param orderIds 要查询的订单id
     */
	private List<Map<String, Object>> getOrderInfoList(List<String> orderIds) {
        List<Future<List<Map<String, Object>>>> futureList = new ArrayList<>();
 
        int size = orderIds.size();
        int orderNum = 0;
        while (orderNum < size) {
            // 分批查询,一次最多能查2000个元素,超过则in查询报错
            int temp = Math.min((orderNum + 2000), size);
            Map<String, Object> paramMap = MapBuilder.<String, Object>init("orderIds", orderIds.subList(orderNum, temp)).getMap();
            OrderInfoHandler  orderInfoListHandler = new OrderInfoHandler(paramMap, orderServiceApi);
            // 这里存一下Future 任务,用于下面接收回调数据。调用 submit 方法,让线程池安排去执行
            futureList.add(threadPoolTaskExecutor.submit(orderInfoListHandler));

            log.info("while 执行orderNum:[{}] 到 temp:[{}] 的查询。", orderNum, temp);
            orderNum = temp;
        }

        List<Map<String, Object>> orderInfoList = new ArrayList<>(size);
        for (Future<List<Map<String, Object>>> f : futureList) {
            List<Map<String, Object>> r = null;
            try {
                r = f.get(30, TimeUnit.SECONDS);
            } catch (InterruptedException | ExecutionException e) {
                log.error("#getorderInfoListList 多线程Future执行异常:{}", e.getMessage());
            } catch (TimeoutException e) {
                log.error("#getorderInfoListList 多线程Future超时。线程名:[{}]", Thread.currentThread().getName());
            }
            log.info("## get之后拿到结果size:[{}]", r.size());
            orderInfoList.addAll(r);
        }

        return orderInfoList;
    }

}

到这里,最后返回的 orderInfoList 就是多线程请求回来的数据集合,在自己的业务逻辑里去处理这个集合就可以了。

 

 

学无止境,生生不息。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值