비동기 구현을 하려는 이유
프로젝트에서 서버에서 발생하는 5xx Exception을 모니터링하기 위해서 Slack WebHook을 사용해서 구현했습니다.
현재 로직은 5xx 예외가 발생 -> Slack 메시지 전송 -> 클라이언트에게 응답까지의 과정이 동기적으로 진행됩니다.
여기서 사용하고 있는 Slack 서버는 제가 제어할 수 없는 부분이기 때문에 만약 Slack 서버에 지연이 생겨 응답을 늦게 준다면 어떻게 될지 생각을 해봤습니다.
Slack 메시지 전송이 지연되어 정작 더 중요한 다음 과정인 "클라이언트에게 응답"이 제대로 수행되지 않는다면... 끔찍한 상황이 벌어질 수도 있다고 생각했습니다.
실제로 Slack 메시지를 보내는 push() 메서드에서 5초의 지연 시간을 주고 테스트 해보니 클라이언트에게 응답이 가는 시간도 그만큼 지연이 되는 것을 확인했습니다.
그래서 Slack으로 메시지를 보내는 부분을 비동기로 처리하여 클라이언트에게 응답을 제공하는 부분에 지연이 생기지 않도록 구현하기로 했습니다.
🔎 Spring의 @Async
구현을 하기 전에 @Async를 조금 알아보면, 비동기 처리를 간단하게 할 수 있도록 Spring에서 제공해주는 어노테이션입니다.
사용 방법은 @Async를 사용하겠다고 @EnableAsyc를 붙여주고, 비동기로 처리할 메서드 위에 @Async를 붙여주면 됩니다.
@Async를 사용할 때는 주의 해야할 점이 2가지 있습니다.
- private 메서드에 붙여서는 안 됩니다. (컴파일 에러로 확인 가능)
- 같은 클래스 내부에서 스스로 호출(self-invocation)을 하면 안 된다. (컴파일 에러로 확인 불가)
1번은 private 메서드에 @Async를 붙여보면 IntelliJ에서 컴파일 에러를 확인해 볼 수 있습니다.
2번은 아래와 같이 컴파일로 확인이 되지 않습니다.
이제 외부에서 MonitoringProvider의 push() 메서드를 호출 했을 때, 내부에 있는 test()가 당연히 비동기 처리 되겠지 하고 생각하고 놔두면 이제 디버깅 지옥을 맛볼 수 있습니다.
실제로 외부에서 push() 메서드를 여러 번 요청해서 비동기 처리가 되는지 확인해보면 다음과 같이 순차적으로 동기적으로 처리되는 것을 확인할 수 있습니다.
자가 호출을 하면 동작하지 않는 이유는 Spring 컨테이너에 등록되어 있는 스프링 빈의 메서드가 호출되었을 때, @Async가 붙어있다면 AOP로 처리되어 스프링이 메서드 요청을 가로채서 다른 Thread Pool에서 실행시켜주는 구조이기 때문이라고 합니다.
그래서 @Async를 private에 붙이거나 내부 호출을 하게 되면 스프링이 가로채서 프록시 처리를 할 수 없기 때문에 2가지 주의점이 있습니다.
✨ 적용하기
이런 주의점을 확인하고, 기존 로직에 비동기 처리를 진행해줍니다.
먼저, @Async를 사용할 수 있도록 @EnableAsync를 붙여줍니다.
@EnableAsync
@SpringBootApplication
public class PraisePushApplication {
public static void main(String[] args) {
SpringApplication.run(PraisePushApplication.class, args);
}
}
비동기 처리를 원했던 메시지를 보내는 push() 메서드에 @Async를 붙여줍니다.
public class SlackMonitoringProvider implements MonitoringProvider {
@Value("${error.webhook.url}")
private String webHookUrl;
@Async
@Override
public void push(final Exception exception, final HttpServletRequest request) {
SlackApi slackApi = new SlackApi(webHookUrl);
slackApi.call(createSlackMessage(exception, request));
}
... 생략 ...
}
요청 시간 로그를 설정하고, push 로직에서 5초의 지연을 두고 요청을 확인해 보면 메시지를 보내는 로직이 비동기적으로 처리되는 것을 확인할 수 있습니다.
'🧑🏻💻 Dev > SpringBoot' 카테고리의 다른 글
JackSon DTO 역직렬화 파헤치기 (JackSon과 Getter) (0) | 2023.09.25 |
---|---|
[Spring | LetsEncrypt] Http 서버를 Https로 바꿔보자 (0) | 2023.08.07 |
[Spring] 스프링에서 캐시를 사용하는 방법 (with. Redis) (0) | 2023.04.10 |
[Spring] application.yml 설정값 가져오기 (0) | 2023.04.07 |
[Spring] ExceptionHandler가 작동안 되는 오류 (컴포넌트 스캔) (0) | 2023.04.03 |