前面针对graceful shutdown写了两篇文章
第一篇:
https://blog.csdn.net/chenshm/article/details/139640775
只思量了阻塞线程,没有思量异步线程
第二篇:
https://blog.csdn.net/chenshm/article/details/139702105
第二篇思量了多线程的安全性,包括异步线程。
1. 为什么还需要优化呢?
因为第二篇的写法还不够优美,它存在以下缺陷。
- 只在一个service bean 里面临ExecutorService做predestroy,只能对一个service类的异步线程提供安全保障,其他service类的异步业务需要重写predestroy的逻辑,造成代码冗余。
- 异步方法的写法比较贫苦,其他程序员并不常用。如今用springboot的程序员喜欢用@Async注解,随时随地可以把方法酿成异步执行。
从架构师的角度思量的话,写代码只管满意多数环境可用,易用,最好照旧全局有效的,让其他程序员专注于写业务代码。
接下来让我们实现@Async注解的异步方法在app graceful shutdow时保持线程安全。
2. 代码优化
- 确认graceful shutdown settings
- 添加第一个servcie 的异步方法
- package com.it.sandwich.service.impl;
- import com.it.sandwich.service.Demo2Service;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.scheduling.annotation.Async;
- import org.springframework.stereotype.Component;
- import org.springframework.stereotype.Service;
- /**
- * @Author 公众号: IT三明治
- * @Date 2024/6/16
- * @Description:
- */
- @Slf4j
- @Service
- @Component
- public class Demo2ServiceImpl implements Demo2Service {
- @Override
- @Async
- public void feedUserInfoToOtherService(String userId) throws InterruptedException {
- for (int i = 0; i < 40; i++) {
- log.info("Demo2Service update {} login info to other services, service num: {}", userId, i+1);
- Thread.sleep(1000);
- }
- }
- }
复制代码
- package com.it.sandwich.service.impl;
- import com.it.sandwich.service.Demo2Service;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.scheduling.annotation.Async;
- import org.springframework.stereotype.Component;
- import org.springframework.stereotype.Service;
- /**
- * @Author 公众号: IT三明治
- * @Date 2024/6/16
- * @Description:
- */
- @Slf4j
- @Service
- @Component
- public class Demo1ServiceImpl implements Demo1Service {
- @Override
- @Async
- public void feedUserInfoToOtherService(String userId) throws InterruptedException {
- for (int i = 0; i < 35; i++) {
- log.info("Demo1Service update {} login info to other services, service num: {}", userId, i+1);
- Thread.sleep(1000);
- }
- }
- }
复制代码 添加两个@Async方法,验证全局生效。
- package com.it.sandwich.controller;
- import com.it.sandwich.base.ResultVo;
- import com.it.sandwich.service.Demo1Service;
- import com.it.sandwich.service.Demo2Service;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- import javax.annotation.Resource;
- /**
- * @Author 公众号: IT三明治
- * @Date 2024/6/16
- * @Description:
- */
- @Slf4j
- @RestController
- @RequestMapping("/api")
- public class DemoController {
- @Resource
- Demo1Service demo1Service;
- @Resource
- Demo2Service demo2Service;
- @GetMapping("/{userId}")
- public ResultVo<Object> getUserInfo(@PathVariable String userId) throws InterruptedException {
- log.info("userId:{}", userId);
- demo1Service.feedUserInfoToOtherService(userId);
- demo2Service.feedUserInfoToOtherService(userId);
- for (int i = 0; i < 30; i++) {
- log.info("updating user info for {}, waiting times: {}", userId, i+1);
- Thread.sleep(1000);
- }
- return ResultVo.ok();
- }
- }
复制代码
- package com.it.sandwich.config;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.scheduling.annotation.EnableAsync;
- import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
- import java.util.concurrent.Executor;
- /**
- * @Author 公众号: IT三明治
- * @Date 2024/6/16
- * @Description:
- */
- @Configuration
- @EnableAsync
- public class AsyncConfig {
- @Bean
- public Executor taskExecutor() {
- ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
- executor.setCorePoolSize(2); // 设置核心线程数
- executor.setMaxPoolSize(5); // 设置最大线程数
- executor.setQueueCapacity(100); // 设置队列容量
- executor.setThreadNamePrefix("sandwich-async-pool-"); // 自定义线程名称前缀
- executor.setWaitForTasksToCompleteOnShutdown(true); // 设置线程池关闭时是否等待任务完成
- executor.setAwaitTerminationSeconds(60); // 设置等待时间,如果你需要所有异步线程的安全退出,请根据线程池内敢长线程处理时间配置这个时间
- return executor;
- }
- }
复制代码 3. 验证代码
- Administrator@USER-20230930SH MINGW64 /d/git/micro-service-logs-tracing
- $ curl http://localhost:8080/api/sandwich
复制代码
- shutdown app(Ctrl+F2)
- 验证日志
查看日志前我们先分析一下代码,我们一个api请求里面一共有三个线程,一个阻塞线程,两个@Async注解修饰的异步线程。阻塞线程的循环计数日志从1到30,Demo1Service 异步线程的循环计数日志从1到35,Demo2Service异步线程的循环计数日志从1到40。我们期待的结果是提前shutdown之后三个线程的计数日志都完备打印出来。
日志完美验证了我们的期待。 我设置的“sandwich-async-pool-”线程名前缀也在两个线程日志中体现了。进一步证明AsyncConfig对全部@Async注解修饰的异步线程全局有效。
这是为什么呢?
4. AsyncConfig设置代码分析
当我在Spring设置中通过@Bean界说了一个ThreadPoolTaskExecutor实例,而且在同一设置类或其他被扫描到的设置类中启用了@EnableAsync注解时,这个自界说线程池会自动与Spring的异步任务执行机制关联起来。这一过程背后的原理涉及到Spring的异步任务执行器(AsyncConfigurer接口)的自动设置和代理机制,具体原因如下:
- Spring的自动装配(Auto Configuration): Spring Boot利用自动设置(auto-configuration)机制来简化设置。当它检测到@EnableAsync注解时,会自动寻找并设置一个TaskExecutor(线程池)来执行@Async标记的方法。假如在应用上下文中存在多个TaskExecutor的Bean,Spring通常会选择一个合适的Bean作为默认的异步执行器。自界说的ThreadPoolTaskExecutor Bean由于是明确设置的,因此优先级较高,天然成为首选。
- AsyncConfigurer接口: 当我使用@EnableAsync时,现实上是在告诉Spring去查找实现了AsyncConfigurer接口的设置类。假如我没有直接实现这个接口并提供自界说设置,Spring会使用默认的设置。但是,假如我提供了自界说的ThreadPoolTaskExecutor Bean,Spring会以为这是我盼望用于异步任务的线程池。
- Spring AOP代理: @Async注解的方法在运行时会被Spring的AOP(面向切面编程)机制代理。这个代理逻辑会检查是否有设置好的TaskExecutor,假如有(好比我自界说的ThreadPoolTaskExecutor),就会使用这个线程池来执行方法,从而实现了异步调用。
- Bean的命名和范例匹配: 默认环境下,Spring在查找执行器时会优先思量那些名为taskExecutor的Bean,这也是为什么在设置ThreadPoolTaskExecutor时通常会使用这个名字。当然,即使不叫这个名字,也可以通过实现AsyncConfigurer接口并重写getAsyncExecutor方法来指定使用的线程池。
综上所述,自界说的ThreadPoolTaskExecutor之以是能成为Spring异步任务执行的默认线程池,是因为Spring的自动设置逻辑、AOP代理机制以及通过设置明确指定了这个线程池的使用。
至此,graceful shutdown已经可以使多线程,高并发的项目在做release的时候,线程安全性得到保障。 特别是一些长处置惩罚的schedul job项目(此中好多job为了提交效率,用了异步机制),经过这样优化之后,release的信心是不是加强了好多。
写文章不容易,假如对您有用,请点个关注支持一下博主再走。谢谢。
假如有更好见解的朋友,请在评论区给出您的引导意见,感谢!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |