需求:在同一个页面下同时管理三家运营商(例如中国电信、中国联通、中国移动)的数据,涉及到调用三家不同的API接口,并在前端页面上统一展示和管理这些数据。
串行化查询:
执行过程如下,伪代码:
//电信
JSONObject result = restTemplateUtils.getHttp(telecomUrl, params, headersMap);
//联通
JSONObject result = restTemplateUtils.getHttp(unicomUrl, params, headersMap);
// 移动
JSONObject result = restTemplateUtils.getHttp(mobileUrl, params, headersMap);
串行化查询,即依次执行每个查询请求,这种做法在某些情况下是必要的,尤其是在不同请求间有依赖关系或资源限制的情况下。然而,当查询独立且可以并行执行时,串行查询存在一些明显的弊端,特别是在高并发和性能敏感的应用场景下。以下是串行化查询的一些主要弊端:
延迟增加:每次查询都需要等待前一个查询完成后才开始执行。这意味着总的响应时间等于各个查询响应时间之和,这可能导致用户感知到的延迟显著增加,尤其是在网络条件不佳或服务器响应慢的情况下。
吞吐量受限:由于每个请求必须等待前一个请求完成,系统的整体吞吐量(单位时间内处理的请求数量)受到限制。在高并发场景下,这可能导致系统无法充分利用其处理能力,从而影响整体性能。
资源浪费:在等待某个查询执行时,CPU和其他系统资源可能处于闲置状态,这降低了资源利用率。特别是在多核处理器环境中,串行执行无法利用多个核心并行处理的能力。
扩展性差:随着查询数量的增加,串行查询的总执行时间将线性增长,这使得系统很难通过简单增加硬件资源来提高性能。在需要处理大量数据或高并发请求的场景下,串行查询的扩展性成为一个瓶颈。
优化方案:并行执行
为了解决这些问题,通常推荐使用并行查询或异步查询的策略,通过使用线程池、异步I/O、并行流处理等技术,可以显著减少总响应时间和提高系统的吞吐量。
例如,在Java中,可以使用CompletableFuture API来并行执行多个HTTP请求,并通过CompletableFuture.allOf().get()来等待所有请求完成,这样可以有效地减少总延迟并提高资源利用率。
执行过程如下,伪代码:
//电信
CompletableFuture<JSONObject> telecomFuture = CompletableFuture.supplyAsync(() -> {
return restTemplateUtils.getHttp(telecomUrl, params, headersMap);
});
//联通
CompletableFuture<JSONObject> unicomFuture = CompletableFuture.supplyAsync(() -> {
return restTemplateUtils.getHttp(unicomUrl, params, headersMap);
});
//移动
CompletableFuture<JSONObject> unicomFuture = CompletableFuture.supplyAsync(() -> {
return restTemplateUtils.getHttp(mobileUrl, params, headersMap);
});
CompletableFuture.allOf(telecomFuture,unicomFuture,mobileFuture).get();
上面的方式并没有使用线程池,结合我们之前的文章,可以通过nacos实现一个动态线程池。
继续优化:使用动态线程池
基于 Nacos 配置的动态线程池管理功能,可以根据配置的变化来动态调整线程池的参数,同时监控线程池的状态并动态添加任务到线程池中。
核心代码如下:
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.listener.Listener;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@RefreshScope
@Configuration
public class DynamicThreadPool implements InitializingBean {
@Value("${core.size}")
private String coreSize;
@Value("${max.size}")
private String maxSize;
private static ThreadPoolExecutor threadPoolExecutor;
@Autowired
private NacosConfigManager nacosConfigManager;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
public void afterPropertiesSet() throws Exception {
//按照nacos配置初始化线程池
threadPoolExecutor = new ThreadPoolExecutor(Integer.parseInt(coreSize), Integer.parseInt(maxSize), 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new ThreadFactoryBuilder().setNameFormat("c_t_%d").build(),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("rejected!");
}
});
//nacos配置变更监听
nacosConfigManager.getConfigService().addListener("service-dev.yml", nacosConfigProperties.getGroup(),
new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
//配置变更,修改线程池配置
System.out.println(configInfo);
changeThreadPoolConfig(Integer.parseInt(coreSize), Integer.parseInt(maxSize));
}
});
}
/**
* 打印当前线程池的状态
*/
public String printThreadPoolStatus() {
return String.format("core_size:%s,thread_current_size:%s;" +
"thread_max_size:%s;queue_current_size:%s,total_task_count:%s", threadPoolExecutor.getCorePoolSize(),
threadPoolExecutor.getActiveCount(), threadPoolExecutor.getMaximumPoolSize(), threadPoolExecutor.getQueue().size(),
threadPoolExecutor.getTaskCount());
}
/**
* 给线程池增加任务
*
* @param count
*/
public void dynamicThreadPoolAddTask(int count) {
for (int i = 0; i < count; i++) {
int finalI = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(finalI);
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
/**
* 修改线程池核心参数
*
* @param coreSize
* @param maxSize
*/
private void changeThreadPoolConfig(int coreSize, int maxSize) {
threadPoolExecutor.setCorePoolSize(coreSize);
threadPoolExecutor.setMaximumPoolSize(maxSize);
}
}
伪代码:
//电信
CompletableFuture<JSONObject> telecomFuture = CompletableFuture.supplyAsync(() -> {
return restTemplateUtils.getHttp(telecomUrl, params, headersMap);
}, DynamicThreadPool.threadPoolExecutor);
//联通
CompletableFuture<JSONObject> unicomFuture = CompletableFuture.supplyAsync(() -> {
return restTemplateUtils.getHttp(unicomUrl, params, headersMap);
}, DynamicThreadPool.threadPoolExecutor);
//移动
CompletableFuture<JSONObject> unicomFuture = CompletableFuture.supplyAsync(() -> {
return restTemplateUtils.getHttp(mobileUrl, params, headersMap);
}, DynamicThreadPool.threadPoolExecutor);
CompletableFuture.allOf(telecomFuture,unicomFuture,mobileFuture).get();
至于线程池参数设置多少合适呢?对于IO密集型任务和计算密集型任务,线程池的设置略有不同:
IO密集型任务:
对于IO密集型任务,通常建议设置较大的线程池大小,以便充分利用CPU等资源,同时能够处理大量的IO操作。
可以考虑设置线程池大小为2 * CPU核心数或更大,这样可以充分利用系统资源并提高IO操作的并发处理能力。
计算密集型任务:
对于计算密集型任务,由于任务主要耗费在CPU计算上,因此需要限制线程池的大小,避免过多线程竞争CPU资源而导致性能下降。
建议将线程池的大小设置为CPU核心数加1或2,这样可以充分利用CPU资源而又不至于引起过多的线程切换导致性能损失。
170万+

被折叠的 条评论
为什么被折叠?



