Java 如何优雅地结束spring@Schedule任务?

Java 如何优雅地结束spring@Schedule任务?,java,spring,spring-boot,rabbitmq,spring-scheduled,Java,Spring,Spring Boot,Rabbitmq,Spring Scheduled,我正在尝试让spring引导服务优雅地结束。它有一个带有@Scheduled注释的方法。该服务将spring数据用于DB,将spring云流用于RabbitMQ。在计划的方法结束之前,DB和RabbitMQ是可访问的,这一点至关重要。有一个autoscaler,它经常启动/停止服务实例,停止时崩溃不是一个选项 从这篇文章中,我认为这应该足以补充 @Bean TaskSchedulerCustomizer taskSchedulerCustomizer() { re

我正在尝试让spring引导服务优雅地结束。它有一个带有
@Scheduled
注释的方法。该服务将spring数据用于DB,将spring云流用于RabbitMQ。在计划的方法结束之前,DB和RabbitMQ是可访问的,这一点至关重要。有一个autoscaler,它经常启动/停止服务实例,停止时崩溃不是一个选项

从这篇文章中,我认为这应该足以补充

    @Bean
    TaskSchedulerCustomizer taskSchedulerCustomizer() {
        return taskScheduler -> {
            taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
            taskScheduler.setAwaitTerminationSeconds(30);
        };
    }
spring应该等待关闭应用程序,直到计划的方法完成或30秒过期

当服务在执行调度方法时停止时,我可以从日志中看到以下内容

  • SpringCloudStream正在关闭连接而不等待方法完成
  • spring data也正在立即关闭db连接
  • 该方法未停止并尝试完成,但由于无法再访问数据库而失败
  • 如何让计划好的方法(包括db连接和rabbitMq访问)顺利完成

    这是我的应用程序类:

    @SpringBootApplication(scanBasePackages = {
        "xx.yyy.infop.dao",
        "xx.yyy.infop.compress"})
    @EntityScan("ch.sbb.infop.common.entity")
    @EnableJpaRepositories({"xx.yyy.infop.dao", "xx.yyy.infop.compress.repository"})
    @EnableBinding(CompressSink.class)
    @EnableScheduling
    public class ApplicationCompress {
    
        @Value("${max.commpress.timout.seconds:300}")
        private int maxCompressTimeoutSeconds;
    
        public static void main(String[] args) {
            SpringApplication.run(ApplicationCompress.class, args);
        }
    
        @Bean
        TaskSchedulerCustomizer taskSchedulerCustomizer() {
            return taskScheduler -> {
                taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
                taskScheduler.setAwaitTerminationSeconds(maxCompressTimeoutSeconds);
            };
        }
    
    }
    
    这就是豆子:

    @Component
    @Profile("!integration-test")
    public class CommandReader {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CommandReader.class);
    
        private final CompressSink compressSink;
        private final CommandExecutor commandExecutor;
    
        CommandReader(CompressSink compressSink, CommandExecutor commandExecutor) {
            this.compressSink = compressSink;
            this.commandExecutor = commandExecutor;
        }
    
        @PreDestroy
        private void preDestory() {
            LOGGER.info("preDestory");
        }
    
        @Scheduled(fixedDelay = 1000)
        public void poll() {
            LOGGER.debug("Start polling.");
            ParameterizedTypeReference<CompressCommand> parameterizedTypeReference = new ParameterizedTypeReference<>() {
            };
            if (!compressSink.inputSync().poll(this::execute, parameterizedTypeReference)) {
                compressSink.inputAsync().poll(this::execute, parameterizedTypeReference);
            }
            LOGGER.debug("Finished polling.");
        }
    
        private void execute(Message<?> message) {
            CompressCommand compressCommand = (CompressCommand) message.getPayload();
    
            // uses spring-data to write to DB
            CompressResponse compressResponse = commandExecutor.execute(compressCommand);
    
            // Schreibt die Anwort in Rensponse-Queue
            compressSink.outputResponse().send(MessageBuilder.withPayload(compressResponse).build());
        }
    
    }
    

    我已经测试了这个配置,它应该与您的
    任务调度程序的配置相同:

    spring.task.scheduling.shutdown.await-termination=true
    spring.task.scheduling.shutdown.await-termination-period=30s
    
    Spring在所有服务可用的情况下等待30秒,然后关闭任何活动任务。如果没有活动任务,则立即关闭

    值得一提的是,让我想到这个问题的是以非常类似的方式配置的
    @Async
    方法的优雅关闭:

    spring.task.execution.shutdown.await-termination=true
    spring.task.execution.shutdown.await-termination-period=1s
    
    或在代码中:

    @Bean
    public TaskExecutorCustomizer taskExecutorCustomizer() {
        // Applies to @Async tasks, not @Scheduled as in the question
        return (customizer) -> {
            customizer.setWaitForTasksToCompleteOnShutdown(true);
            customizer.setAwaitTerminationSeconds(10);
        };
    }
    
    回到您的案例中,我猜想
    任务调度程序rcustomizer
    没有实际执行,或者在执行后被其他内容覆盖

    对于第一个选项,通过添加日志语句或在
    taskSchedulerCustomizer()
    中设置断点进行验证

    对于第二个选项,我建议在
    TaskSchedulerBuilder::configure()
    中设置一个断点以查看发生了什么。一旦调试器中断了该方法,请在
    taskScheduler
    ExecutorConfigurationSupport::awaitTerminationMillis
    属性上添加一个数据断点,以查看该属性是否在其他地方被修改


    您可以在方法
    ExecutorConfigurationSupport::WaitiveTerminationIfEssential

    中看到关机过程中使用的最终终止期。如果不需要,则无法强制执行此操作。任何事情都可能发生。比如说,如果停电怎么办?是的,当然。我说的是正常关机,spring的关机钩子应该确保以友好的方式关机。如果你使用AWS的autoscaler,它会在缩小应用程序和关闭JVM之前向你的虚拟机发送SIGKILL。也许在程序中添加一个?
    @Bean
    public TaskExecutorCustomizer taskExecutorCustomizer() {
        // Applies to @Async tasks, not @Scheduled as in the question
        return (customizer) -> {
            customizer.setWaitForTasksToCompleteOnShutdown(true);
            customizer.setAwaitTerminationSeconds(10);
        };
    }