====== Spring @Async ====== * [[http://www.baeldung.com/spring-async|Spring Async]] * [[https://docs.spring.io/spring/docs/4.1.x/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html|ThreadPoolTaskExecutor]]로 비동기 코드 실행 * [[java:concurrent:executorservice|Java ExecutorService]] * 비동기 작업시 [[springframework:bean_lifecycle|Spring Framework Bean Lifecycle]]에 있는 ''SmartLifeCycle''을 구현해줘야만 한다. ===== 설정 ===== * Java 8 이후 버전에서는 [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/AsyncConfigurer.html|AsyncConfigurer]] interface를 구현하면 된다. default method 라서 원하는 것만 선택구현가능함. @EnableAsync public class SpringAsyncConfig extends AsyncConfigurerSupport { // 기본 taskExecutor @Bean // @Bean 필수 @Override public Executor getAsyncExecutor() { return new ThreadPoolTaskExecutor(); // 객체 설정해줄것. } } ===== ThreadPoolTaskExecutor 설정 ===== * [[java:concurrent:executorservice|Java ExecutorService]] 참조 Spring 의 ThreadPool * wait 설정으로 서버 종료시 남은 작업을 기다릴 시간 확보해야함. 아래를 안해주면 서버가 그냥 종료 돼 버린다. * ''waitForTasksToCompleteOnShutdown=true'' * ''awaitTerminationSeconds=초'' * ''beanName''을 지정해주면 로그에 이름이 찍힘. * [[https://github.com/kwon37xi/java-spring-thread-pool-test|kwon37xi/java-spring-thread-pool-test: Java와 SpringFramework 의 Thread Pool 작동 방식 테스트]] * fixed? cached? * 서버 종료시 올바로 종료됨을 보장하려면 [[springframework:bean_lifecycle|Spring Framework Bean Lifecycle]] [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/SmartLifecycle.html|SmartLifeCycle]]도 구현해야한다. ===== Cached Thread Pool 효과 ===== * corePoolSize 는 적게 혹은 0, maxPoolSize=''Interger.MAX_VALUE'', queueCapacity를 0으로 만들면 Cached Thread Pool 처럼 작동한다. * 기본 CorePoolSize 만큼 풀을 생성하고, * 필요하면 쓰레드풀을 최대 maxPoolSize 만큼 증가시키고 * 안 사용하는 시간이 KeepAliveSeconds가 지나면 쓰레드를 없앤다. * cachedThreadPool 은 느려지는 태스크 실행시 쓰레드 갯수가 폭증해서 시스템을 다운시킬 수도 있다. ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setThreadNamePrefix("XXXX-"); threadPoolTaskExecutor.setCorePoolSize(0); threadPoolTaskExecutor.setMaxPoolSize(Integer.MAX_VALUE); threadPoolTaskExecutor.setQueueCapacity(0); threadPoolTaskExecutor.setKeepAliveSeconds(60); threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true); threadPoolTaskExecutor.setAwaitTerminationSeconds(15); ===== MDC 사용하기 ===== * ''@Async''로 새로 생성된 쓰레드에서 호출자 쓰레드의 [[java:slf4j:mdc|Slf4j MDC]]를 복제하도록 할 수 있다. * [[https://moelholm.com/2017/07/24/spring-4-3-using-a-taskdecorator-to-copy-mdc-data-to-async-threads/|Spring 4.3: Using a TaskDecorator to copy MDC data to @Async threads]] ==== ThreadPoolTaskExecutor 설정에 Decorator 지정 ==== @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(new MdcTaskDecorator()); executor.initialize(); return executor; } ==== MdcTaskDecorator.java ==== class MdcTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { // Right now: Web thread context ! // (Grab the current thread MDC data) Map contextMap = MDC.getCopyOfContextMap(); return () -> { try { // Right now: @Async thread context ! // (Restore the Web thread context's MDC data) if (contextMap != null) { MDC.setContextMap(contextMap); } runnable.run(); } finally { MDC.clear(); } }; } } ===== CompletableFuture 지원 ===== * [[java:8:completable_future|Java 8 CompletableFuture]] * [[https://www.javacodegeeks.com/2016/04/spring-async-javas-8-completablefuture.html|Spring Async and Java's 8 CompletableFuture]] * [[https://spring.io/guides/gs/async-method/|Getting Started | Creating Asynchronous Methods]] * [[https://dzone.com/articles/spring-boot-async-methods|Spring Boot - Async methods - DZone Java]] * 아래와 같이 ''CompletableFuture.completedFuture()''를 리턴하게 만들면 Spring 이 내부적으로 [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/interceptor/AsyncExecutionInterceptor.html|AsyncExecutionInterceptor]]에서 진짜 ''CompletableFuture.supplyAsync''로 변환해서 리턴한다. [[https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java|AsyncExecutionAspectSupport.java 소스]] [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/concurrent/ListenableFuture.html|ListenableFuture]]와 [[https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html|Future]]도 마찬가지로 작동함. @Async public CompletableFuture findUser(String user) throws InterruptedException { logger.info("Looking up " + user); String url = String.format("https://api.github.com/users/%s", user); User results = restTemplate.getForObject(url, User.class); Thread.sleep(1000L); return CompletableFuture.completedFuture(results); //IT IS completedFuture }