Java 由自己的执行人替换默认SimpleAsynctaskeExecutor的drawnbacks和风险是什么
个人知识:我读到:“…SimpleAsyncTaskExecutor对于玩具项目来说是可以的,但是对于任何比它更大的项目,它都有点风险,因为它不限制并发线程,也不重用线程。所以为了安全起见,我们还将添加一个任务执行器bean…”以及如何添加我们自己的任务执行器的一个非常简单的示例。但我可以找到任何解释后果的指南,以及一些值得应用的案例 个人愿望:我正在努力为我们的微服务日志提供一个企业架构,并将其发布在卡夫卡主题上。“由于不限制并发线程和不重用它而导致的风险”这句话似乎是合理的,主要是针对我基于日志的案例 我正在本地桌面上成功地运行下面的代码,但是我想知道我是否正确地提供了自定义任务执行器 我的问题:考虑到我已经在使用kafkatempla(即,默认情况下,同步化、单例和线程安全,至少在理解的范围内,用于生成/发送消息),是否会使用下面的配置在使用SimpleAsynctaskeExecutor时,是否真的朝着正确的方向重用线程并避免意外传播线程创建 生产者配置Java 由自己的执行人替换默认SimpleAsynctaskeExecutor的drawnbacks和风险是什么,java,spring,multithreading,spring-boot,apache-kafka,Java,Spring,Multithreading,Spring Boot,Apache Kafka,个人知识:我读到:“…SimpleAsyncTaskExecutor对于玩具项目来说是可以的,但是对于任何比它更大的项目,它都有点风险,因为它不限制并发线程,也不重用线程。所以为了安全起见,我们还将添加一个任务执行器bean…”以及如何添加我们自己的任务执行器的一个非常简单的示例。但我可以找到任何解释后果的指南,以及一些值得应用的案例 个人愿望:我正在努力为我们的微服务日志提供一个企业架构,并将其发布在卡夫卡主题上。“由于不限制并发线程和不重用它而导致的风险”这句话似乎是合理的,主要是针对我基于
@EnableAsync
@Configuration
public class KafkaProducerConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(KafkaProducerConfig.class);
@Value("${kafka.brokers}")
private String servers;
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("KafkaMsgExecutor-");
executor.initialize();
return executor;
}
@Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
return props;
}
}
这是
SimpleAsyncTaskExecutor的默认实现
protected void doExecute(Runnable task) {
Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
thread.start();
}
为每个任务创建新线程,Java中的线程创建并不便宜:()
线程对象使用大量内存,在大型应用程序中,分配和取消分配许多线程对象会产生大量内存管理开销
=>使用此任务执行器重复执行任务将对应用程序性能产生负面影响(此外,默认情况下,此执行器不限制并发任务的数量)
这就是为什么建议您使用线程池实现,线程创建开销仍然存在,但由于线程被重用而不是创建,因此大大减少了线程创建开销
配置ThreadPoolTaskExecutor
时,应根据应用程序负载正确定义两个值得注意的参数:
private int maxPoolSize=Integer.MAX_值
这是池中的最大线程数
private int queueCapacity=Integer.MAX_值代码>
这是排队的最大任务数。当队列已满时,默认值可能会导致OutOfMemory异常
使用默认值(Integer.MAX\u value
)可能会导致服务器资源不足/崩溃
您可以通过增加最大池大小setMaxPoolSize()
的数量来提高吞吐量,要减少加载增加时的预热,请将core poolsize设置为更高的值setCorePoolSize()
(当加载增加时,将初始化maxPoolSize-corePoolSize
之间不同的任何线程数量)我猜你必须使用Spring,而不是像Akka Streams或Lagom这样的反应式框架?是的,Spring是我们微服务的基础框架。这个问题太广泛和模糊;这里不合适。也许它适用于上面的“@Bean public Executor taskExecutor()…”我的方向正确吗?关于maxPoolSize和queueCapacity,非常值得您的评论。但是,你错过了一些额外的配置运行你的眼睛以上?您是否在我的上述陈述中看到任何奇怪的概念或冲突:“我已经在使用kafkatemplate(即,默认情况下,同步、单例和线程安全,至少用于生成/发送消息)”?在我的公司中,目前有5000万客户在使用我们的移动应用程序,该应用程序使用多个微服务,我们每秒发送150个请求,要求记录每个呼叫。执行器池与kafkatemplate无关。您已经使用了kafkatemplate的异步版本(非阻塞)所以性能应该很快,150个RPS很小。看看这个,谢谢你的反馈,每秒150个请求很小。P/Read只是假设另一个你认为高的数字。我不明白你的意思。kafkatemplate完全依赖于executor,不是吗?我知道kafkatemplate异步版本很快,但Manh的评论对我来说很有意义“分配和释放许多线程对象会造成很大的内存管理开销……默认情况下,该executor不会限制并发任务的数量”。您似乎看到我走错了方向。您能更清楚一点吗?根据“生产者的实现是异步的。消息存储在内部队列中,等待通过内部线程发送,这将通过潜在的批处理提高效率”.这样的内线程不是executor提供的吗?如果是的话,拥有一个池我相信在一些负面场景中会有帮助(例如,MicroDevice容器和Kafka容器之间的连接降低,消息意外激增等等。我说的“这样的内线程正是executor poool提供的”对吗?
@SpringBootApplication
public class KafkaDemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(KafkaDemoApplication.class, args);
}
@Autowired
private Producer p;
@Override
public void run(String... strings) throws Exception {
p.send("test", " qualquer messagem demonstrativa");
}
}
protected void doExecute(Runnable task) {
Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
thread.start();
}