Spring에서 비동기 쉽게 구현하기
쓰려면 알아야 하니까 배워본다
최근 일을 하다가 비동기를 써야만 하는 일이 발생했다.
팀장님이 @Async 어노테이션을 언급하시며 일단 비즈니스 문제를 해결하기 위해 붙이라고 하셨다.
그래서 약간의 테스트 후 바로 실서버에 붙여넣었다.
근데 잘 모르고 쓰니 찝찝해...😓
그래서 혼자 이것저것 해본 것을 정리해본다.
미래의 내가 이 글을 또 볼 것이다.
(여러 블로그를 참고했고, 그 글이 훨씬 정리가 잘 되어있으니 만약 다른 사람들이 와서 보고 계시다면 가장 밑으로 내려가서 참고 링크들을 보시는 게 빨라요~)
- 구현하려는 Application에 @EnableAsync 어노테이션을 붙인다.
- 비동기로 동작하고 싶은 메서드에 @Async 어노테이션을 붙인다.
그럼 끝...
이긴 한데요,
딱 이렇게만 하면 비동기 작업을 새로 해야 할 때
스레드 풀에서 처리하는 것이 아니라 새로운 스레드를 매번 생성해서 처리한다고 한다.
만약 비동기 작업이 연속해서 많이 들어온다면
그때마다 스레드를 생성해야 하고 오히려 부하를 만들 것이다.......
미리 생성해놓은 스레드 풀에서 동작할 수 있도록 해야 한다.
우선 비동기가 되긴 되는지 확인해봤다.
이건 일할 때 잠깐 확인해본 방식이기도 하다.
@EnableAsync
@SpringBootApplication
public class BackToBasicApplication {
public static void main(String[] args) {
SpringApplication.run(BackToBasicApplication.class, args);
}
}
어플리케이션에 @EnableAsync 어노테이션을 붙인다.
이 어플리케이션은 비동기가 가능하다, 라는 뜻이겠지?
public void simpleTestWithNotAsync() throws InterruptedException {
Thread.sleep(10);
}
@Async
public void simpleTestWithAsync() throws InterruptedException {
Thread.sleep(10);
}
동일하게 sleep으로 10초를 걸어주었다.
그리고 하나에만 @Async 어노테이션을 붙였다. 그 메서드만 비동기로 실행되겠지.
@Test
public void simpleTest() throws InterruptedException {
StopWatch stopWatch = new StopWatch();
stopWatch.start("async");
asyncService.simpleTestWithAsync();
stopWatch.stop();
stopWatch.start("not async");
asyncService.simpleTestWithNotAsync();
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
그리고 테스트로 돌려보았다.
StopWatch '': running time = 60393209 ns
---------------------------------------------
ns % Task name
---------------------------------------------
018131292 030% async
042261917 070% not async
이 정도의 차이가 났다.
근데 다른 분들 글을 보니 이렇게 테스트 하시지 않더라고?
그래서 나도 따라해봤다.
일단, 어떻게 돌아가는지 티를 내기 위한 코드를 하나 만든다.
아래의 메서드가 비동기로 실행될 것이다.
private static final Logger logger = LoggerFactory.getLogger(AsyncService.class);
@Async
public void asyncTest(int i) {
logger.info("async test: " + i);
}
그리고 이 메서드를 1000번 돌려본다.
@GetMapping("/async")
public void testAsync() {
for(int i=0; i<1000; i++) {
asyncService.asyncTest(i);
}
}
이렇게 적고 localhost:8081/async를 실행해본다.
(나는 8081이다... 하도 겹쳐서)
그러면 이렇게!
마지막에 찍힌 숫자를 보면 순서대로 찍히지 않은 것을 볼 수 있다.
그리고 여기서 유심히 봐야 할 것은... 저 앞에 쓰레드 이름이다.
좀 잘렸지만 내가 직접 할당한 쓰레드 풀에서 가져온 쓰레드다.
어떻게 했는지 밑에서 적어보려고 한다.
@EnableAsync
@Configuration
public class AsyncThreadConfiguration {
@Bean
public Executor asyncThreadTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(8);
threadPoolTaskExecutor.setMaxPoolSize(8);
threadPoolTaskExecutor.setThreadNamePrefix("seul-executor-v1-");
return threadPoolTaskExecutor;
}
}
Configuration 클래스를 만들어서 Bean으로 등록해줬다.
저 안에 설정들이 쓰레드 풀 설정이다.
이렇게 하면 자동으로 내가 설정한 쓰레드 풀에서 할당해갈 것이다.
그런데 참고한 글을 보니 yaml 파일로 설정이 가능하다고 해서 함 해봤다.
spring:
task:
execution:
pool:
core-size: 8
max-size: 8
thread-name-prefix: "executor-v2-"
이렇게 설정해줬더니,
동일한 기능이 가능하다.
신기하죠?
이 외에 참고한 글에 적혀있는 내용으로는,
private 메서드에서는 사용이 불가능하다는 것,
스스로 호출... 그니까 내부에서 호출한 메서드로는 불가능하다는 내용이 있다.
이 내용은 이해는 하나 설명은 못 하니 참고 글을 다시 한번 보기를!
참고글
1. 가장 먼저 눌러본 글, exception 설명 있음
https://velog.io/@gillog/Spring-Async-Annotation%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
2. 설명 정말 잘하셨음
https://jeong-pro.tistory.com/187
3. 스프링 가이드
https://spring.io/guides/gs/async-method/
4. yaml 설정 방법
https://godekdls.github.io/Spring%20Boot/task-execution-and-scheduling/