背景:某资源的查询接口,一共有四个渠道,均要调用查询接口落缓存,且均有QPS限制,现要通过线程池配置来控制查询频率。
渠道A | 渠道B | 渠道C | 渠道D | |
QPS | 5 | 2 | 10 | 2 |
思路:通过涉及线程池中的核心线程数、最大线程数、队列长度来达到限流的目的,四个渠道因QPS要求不一,则分别配置四个池子。
示例:
@Bean(name = "XxPoolExecutor")
public ThreadPoolTaskExecutor xxQueryPoolExecutor() {
String channelPoolSize = annotatedBean.getChannelPoolSize();
ChannelPoolConfig channelPoolConfig = JSONObject.parseObject(channelPoolSize, ChannelPoolConfig.class);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(channelPoolConfig.getFlightMasterCorePoolSize());
executor.setMaxPoolSize(channelPoolConfig.getFlightMasterMaxThreadNum());
// 线程池维护线程所允许的空闲时间
executor.setKeepAliveSeconds(60);
// 队列容量
executor.setQueueCapacity(1000);
// 等待任务执行完成在关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
//该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
executor.setAwaitTerminationSeconds(60);
// 线程前缀名称
executor.setThreadNamePrefix("async-query-service-");
// 配置拒绝策略:如果队列满了,继续往队列增加数据,则由调用线程处理该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
try {
executor.initialize();
}catch (Exception e){
e.printStackTrace();
}
return executor;
}
线程池配置有以下几个关键的参数:
更具体的细节及示例见: 手写线程池及四种拒绝策略解析
1.常驻核心线程数
保证有多少个线程一直在等待新任务的到来,当任务到来时,线程池不用新建线程而是直接利用空闲的核心线程执行任务。
2.队列长度
当核心线程数打满且有新任务提交时,任务会放在队列等待执行。
3.最大线程数
线程池能够容纳同时执行的最大线程数,当核心线程及队列都被占满任务时,线程池会判断新提交的任务是否大于最大线程数,若是则触发拒绝策略;反之则新建线程来执行。
有些晦涩,举例来说:核心线程数为2,队列长度为5,最大线程数为3。
当第一个,第二个任务被提交到线程池后,可以立即执行;
若此时前两个任务未执行结束,又过来5个任务,此时队列已满;
若再提交一个任务,则触发最大线程数(新开一个线程来执行);
若此时再提交任务,只能触发拒绝策略了。