✨ 우리 회사에서 주요한 일 중 하나는 정기적으로 데이터에 대한 업데이트를 수행하는 것이다. 아직은 인원이 나 혼자! (온리 원!) 상태이기 때문에 반 자동 형태로 완전한 배치를 만드는 것이 목표인데, 저번에 웹 api를 통해서 데이터를 받아오고, 그것을 정제하는 과정을 만들게 되면서(이것도 기회가 되면 블로깅을 해야지) WebFlux 를 사용했다. 그런데 웬걸? 이걸 사용하니까 배치 프로세스 종료 후 자동으로 8080 포트가 닫히지 않는 것이다! 참고로, 원래 저 어노테이션을 사용하지 않았을 때는 포트도 사용을 하지 않았다(...!) 그래서 WebFlux을 왜 사용하는지, 이걸 사용하고 나서 닫히지 않는 이유가 무엇인지, 어떻게 하면 닫을 수 있는지까지 작성해보고자 한다
🪡 WebFlux ? 그게 뭔데
WebFlux는 비동기 방식으로 웹 애플리케이션을 개발할 수 있는 스프링 모듈이다. 이렇게 말하면... 당연히 이해 안 된다!
주요 특징은 다음과 같다.
1. 비동기 논블로킹 처리:
• 요청과 응답을 비동기적으로 처리하여 시스템 자원을 효율적으로 사용이 가능하다.
• 백엔드 서비스와의 통신이나 파일 I/O 등 시간이 오래 걸리는 작업을 비동기적으로 처리할 수 있다.
2. 함수형 프로그래밍 지원:
• WebFlux는 함수형 프로그래밍 스타일을 지원하여 선언적이고 읽기 쉬운 코드를 작성할 수 있다.
• RouterFunction과 HandlerFunction을 사용하여 라우팅과 핸들러를 정의가 가능하다.
3. 높은 유연성:
• WebFlux는 다양한 서버와 통합할 수 있다. 예를 들어, Netty, Undertow, Jetty 등을 사용할 수 있다.
• 또한, WebFlux는 스프링 MVC와 함께 사용할 수도 있다. 기존의 블로킹 방식의 코드와 비동기 논블로킹 방식을 혼용할 수 있다.
WebFlux 을 사용하는 이유는 다음과 같다.
1. 비동기 논블로킹 프로그래밍:
• WebFlux는 논블로킹 I/O를 통해 고성능의 비동기 웹 애플리케이션을 개발이 가능하다. 이는 특히 높은 동시성을 요구하는 시스템에서 효과적이다.
• 전통적인 블로킹 방식의 서블릿 API와 달리, WebFlux는 Reactor 기반의 비동기 논블로킹 스트림을 사용.
2. 고성능과 확장성:
• 비동기 논블로킹 방식은 적은 리소스로 많은 요청을 처리할 수 있어 성능과 확장성이 뛰어나다.. 이는 특히 많은 동시 요청을 처리해야 하는 애플리케이션에 적합하다.
• WebFlux는 Netty와 같은 비동기 논블로킹 서버를 지원한다.
3. 리액티브 프로그래밍 모델:
• WebFlux는 리액티브 프로그래밍 모델을 채택하여 데이터 스트림과 이벤트 기반 프로그래밍을 쉽게 구현할 수 있다.. 이를 통해 복잡한 비동기 흐름을 선언적으로 처리할 수 있다.
• Reactor 라이브러리의 Mono와 Flux를 사용하여 비동기 시퀀스를 처리한다.
그러니까, WebFlux을 사용하게 되면 비동기적으로 다양한 환경의 api를 호출하고, I/O 처리를 할 수 있다는 것이다. 비동기 방식이기 때문에 충돌 위험이 적고, 다양하고 복잡한 소스를 효율적으로 사용이 가능하면서도 다양한 서버와의 호환이 가능하다. 굉장히 좋다!
🧶 근데 무엇이 문제가 되나요?
문제는 WebFlux의 경우, 내부적으로 사용되는 비-데몬(non-daemon) 스레드이기 때문이다. 이게 왜 문제냐고? 이건 JVM이 종료될 때까지 실행이 된다. 그러니까, 내가 직접 종료를 직접적으로 명시하지 않는 한, JVM이 종료될 때까지 실행되기에 내가 배치를 종료했다고 해서 애플리케이션까지 자동으로 동작을 끝내지 않는다.
앗 그럼...? 데몬과 비데몬의 차이는 뭘까?
데몬 스레드의 경우에는 JVM이 종료될 때 자동으로 종료가 된다. 그렇기 때문에 백그라운드 작업을 사용할 때 자주 사용된다.
비데몬 스레드의 경우, 웹 서버와 연결할 때 사용이 되는데, 웹 서버 요청을 받고 전송하기 위해 계속 실행 상태로 유지하는 것이 기본이다. 그렇기 때문에 JVM이 종료될 때까지 실행되며, 그렇기 때문에 모든 비데몬 스레드가 종료되기 전까지 JVM이 종료되지 않는다.
결론은 JVM -> 비데몬 스레드가 종료될 때까지 종료 안 함 / 비데몬 스레드 -> JVM 종료될 때까지 종료 안 함의 상태이기 때문에 둘 중 하나가 명시적 종료가 되기 전까진 둘 다 계속해서 활성 상태를 유지하는 것이다.
🏹 명시적으로 종료를 하는 방법?
그렇담 명시적으로 종료를 하는 방법도 알고 가자.
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import reactor.netty.DisposableServer
import reactor.netty.http.server.HttpServer
@Configuration
class WebFluxConfig {
@Bean
fun httpServer(): DisposableServer {
return HttpServer.create()
.port(8080)
.route { routes ->
routes.get("/api") { req, res ->
res.sendString(Mono.just("Hello, World!"))
}
}
.bindNow()
}
}
이런 식의 구조로 되어있는 설정파일이 있다고 했을 때,
배치 작업이 완료되고 나면
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.SpringApplication
import org.springframework.context.ApplicationContext
import org.springframework.stereotype.Component
@Component
class JobCompletionNotificationListener(
private val applicationContext: ApplicationContext,
private val server: DisposableServer
) : JobExecutionListener {
override fun afterJob(jobExecution: JobExecution) {
if (jobExecution.status == BatchStatus.COMPLETED) {
// 서버 종료
server.disposeNow()
// 애플리케이션 종료
SpringApplication.exit(applicationContext, { 0 })
}
}
}
이렇게 서버를 종료하고, 애플리케이션을 명시적으로 종료할 수 있다.
또는, 서버 종료 없이 애플리케이션을 종료한다고 한다면 JVM이 종료되기 때문에 자연스럽게 같이 종료를 할 수도 있다.
'JAVA&KOTLIN' 카테고리의 다른 글
[Spring Batch] S3 버킷 스트리밍 방식 vs 로컬 다운로드 방식 (0) | 2024.08.23 |
---|---|
VO? DTO? 둘의 차이가 뭔데 (0) | 2024.07.31 |
[Spring Boot & kotlin] bean과 JobScope (0) | 2024.07.26 |