it-source

웹 앱의 다른 모든 빈이 파괴되기 전에 어떻게 Spring 작업 실행자/스케줄러 풀을 종료할 수 있습니까?

criticalcode 2023. 7. 25. 21:06
반응형

웹 앱의 다른 모든 빈이 파괴되기 전에 어떻게 Spring 작업 실행자/스케줄러 풀을 종료할 수 있습니까?

Spring 웹 애플리케이션에는 여러 DAO 및 서비스 계층 콩이 있습니다.한 서비스 계층 빈에는 @Async / @Scheduled 메서드에 주석이 달렸습니다.이러한 방법은 다른 (자동 배선된) 콩에 의존합니다.XML에서 두 개의 스레드 풀을 구성했습니다.

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
     <property name="corePoolSize" value="2" />
     <property name="maxPoolSize" value="5" />
     <property name="queueCapacity" value="5" />
     <property name="waitForTasksToCompleteOnShutdown" value="true" />
     <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
        </property>
    </bean>

<bean id="taskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
     <property name="poolSize" value="10" />
     <property name="waitForTasksToCompleteOnShutdown" value="true" />
     <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
        </property>
    </bean>

    <task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>

모든 것이 예상대로 작동합니다.문제는 작업 풀을 완전히 종료할 수 없다는 것입니다.태스크는 데이터베이스와 파일 시스템에서 작동합니다.웹 애플리케이션을 중지하면 중지될 때까지 시간이 걸립니다.이것은 다음을 나타냅니다.waitForTasksToCompleteOnShutdown 불법 상태 됩니다.그러나 로그에 일부 빈은 이미 제거되었지만 일부 작업자 작업 스레드는 여전히 실행 중이며 종속성이 제거되어 실패함을 나타내는 불법 상태 예외가 표시됩니다.

관련될 수 있는 JIRA 문제가 있습니다. SPR-5387

제 질문은: Spring에게 작업 실행자/스케줄러 빈을 마지막으로 초기화하라고 말할 수 있는 방법이 있습니까? 아니면 Spring에게 먼저 제거하라고 말할 수 있는 방법이 있습니까?

제가 알기로는 파괴는 역순으로 일어나는 것으로 알고 있습니다.그러므로 마지막으로 시작된 콩이 먼저 파괴될 것입니다.스레드 풀 빈이 먼저 제거되면 현재 실행 중인 모든 작업이 완료되고 종속 빈에 액세스할 수 있습니다.

또한 @Async 및 @Scheduled 주석이 있는 서비스 빈을 참조하는 스레드 풀에서 의존 속성을 사용해 보았습니다.그러면 실행되지 않는 것 같고 컨텍스트 초기화 오류가 발생하지 않습니다.주석이 달린 서비스 빈은 어떻게든 먼저 이러한 스레드 풀을 초기화해야 하고, dependent-on을 사용하면 순서를 반대로 하여 작동하지 않게 만듭니다.

두 가지 방법:

  1. 을 사용해 .ApplicationListener<ContextClosedEvent>.onApplicationEvent()상황이 발생하기 전에 전화가 걸려 모든 콩이 파괴됩니다.

  2. 라이프사이클 또는 스마트 라이프사이클을 구현해야 합니다.stop()상황이 발생하기 전에 전화가 걸려 모든 콩이 파괴됩니다.

어느 쪽이든 콩 파괴 메커니즘이 발생하기 전에 작업을 종료할 수 있습니다.

예:

@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
    @Autowired ThreadPoolTaskExecutor executor;
    @Autowired ThreadPoolTaskScheduler scheduler;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        scheduler.shutdown();
        executor.shutdown();
    }       
}

(편집: 고정 메소드 서명)

당신이 사용할 수 있는 작업을 종료하기 위해 아래 코드를 추가했습니다.재시도 횟수를 변경할 수 있습니다.

package com.xxx.test.schedulers;

import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;

import com.xxx.core.XProvLogger;

@Component
class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> , ApplicationContextAware,BeanPostProcessor{


private ApplicationContext context;

public Logger logger = XProvLogger.getInstance().x;

public void onApplicationEvent(ContextClosedEvent event) {


    Map<String, ThreadPoolTaskScheduler> schedulers = context.getBeansOfType(ThreadPoolTaskScheduler.class);

    for (ThreadPoolTaskScheduler scheduler : schedulers.values()) {         
        scheduler.getScheduledExecutor().shutdown();
        try {
            scheduler.getScheduledExecutor().awaitTermination(20000, TimeUnit.MILLISECONDS);
            if(scheduler.getScheduledExecutor().isTerminated() || scheduler.getScheduledExecutor().isShutdown())
                logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has stoped");
            else{
                logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has not stoped normally and will be shut down immediately");
                scheduler.getScheduledExecutor().shutdownNow();
                logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has shut down immediately");
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    Map<String, ThreadPoolTaskExecutor> executers = context.getBeansOfType(ThreadPoolTaskExecutor.class);

    for (ThreadPoolTaskExecutor executor: executers.values()) {
        int retryCount = 0;
        while(executor.getActiveCount()>0 && ++retryCount<51){
            try {
                logger.info("Executer "+executor.getThreadNamePrefix()+" is still working with active " + executor.getActiveCount()+" work. Retry count is "+retryCount);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if(!(retryCount<51))
            logger.info("Executer "+executor.getThreadNamePrefix()+" is still working.Since Retry count exceeded max value "+retryCount+", will be killed immediately");
        executor.shutdown();
        logger.info("Executer "+executor.getThreadNamePrefix()+" with active " + executor.getActiveCount()+" work has killed");
    }
}


@Override
public void setApplicationContext(ApplicationContext context)
        throws BeansException {
    this.context = context;

}


@Override
public Object postProcessAfterInitialization(Object object, String arg1)
        throws BeansException {
    return object;
}


@Override
public Object postProcessBeforeInitialization(Object object, String arg1)
        throws BeansException {
    if(object instanceof ThreadPoolTaskScheduler)
        ((ThreadPoolTaskScheduler)object).setWaitForTasksToCompleteOnShutdown(true);
    if(object instanceof ThreadPoolTaskExecutor)
        ((ThreadPoolTaskExecutor)object).setWaitForTasksToCompleteOnShutdown(true);
    return object;
}

}

저는 스프링빈에서 시작되는 스레드와 비슷한 문제를 겪었습니다.실행자를 호출한 후 이 스레드가 제대로 닫히지 않았습니다.@PreDestroy 메서드에서 shutdownNow().그래서 저는 @PreDestroy가 호출되면 IO가 포함된 스레드가 이미 시작되고 더 이상 IO가 시작되지 않도록 하는 것이 해결책이었습니다.그리고 여기 @PreDestroy 방법이 있습니다.저의 신청은 1초간의 기다림이 허용되었습니다.

@PreDestroy
    public void beandestroy() {
        this.stopThread = true;
        if(executorService != null){
            try {
                // wait 1 second for closing all threads
                executorService.awaitTermination(1, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

여기에서는 스레드를 닫으려는 동안 발생한 모든 문제에 대해 설명했습니다.http://programtalk.com/java/executorservice-not-shutting-down/

웹 기반 응용프로그램이 될 경우, ServletContextListener 인터페이스도 사용할 수 있습니다.

public class SLF4JBridgeListener implements ServletContextListener {

   @Autowired 
   ThreadPoolTaskExecutor executor;

   @Autowired 
   ThreadPoolTaskScheduler scheduler;

    @Override
    public void contextInitialized(ServletContextEvent sce) {

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
         scheduler.shutdown();
         executor.shutdown();     

    }

}

두 작업 모두에 대해 "WaitTerminationSeconds" 속성을 추가할 수 있습니다.아래와 같은 실행자 및 작업 스케줄러,

<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />

<property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />

종료가 호출될 때 "Wait For TasksToCompleteOnShutdown" 속성에 대한 문서에 다음과 같이 표시됩니다.

"스프링의 컨테이너 폐쇄는 진행 중인 작업이 완료되는 동안 계속됩니다. 이 실행자가 나머지 컨테이너가 계속 종료되기 전에 작업이 차단되고 종료될 때까지 기다리려면(예: 작업에 필요한 다른 리소스를 유지하기 위해) 이 속성 대신 또는속성에 추가하여 "waitTerminationSeconds" 속성을 설정하십시오.

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.html#setWaitForTasksToCompleteOnShutdown-boolean-

따라서 항상 waitForTasksToCompleteOnShutdown을 사용하고 대기하는 것이 좋습니다.TerminationSeconds 속성이 함께 표시됩니다.대기값TerminationSeconds는 애플리케이션에 따라 다릅니다.

언급URL : https://stackoverflow.com/questions/6603051/how-can-i-shutdown-spring-task-executor-scheduler-pools-before-all-other-beans-i

반응형