본문 바로가기
개발 이야기/Springboot

Springboot Webflux에서 blocking 감지하기(with BlockHound)

by 농개 2024. 2. 8.

1. BlockHound란?

Webflux를 도입하고 로깅 관련 리서치를 하던 중 우연히 blocking을 감지하는 라이브러리가 있다는 것을 알게 되었습니다.

출처: https://stackoverflow.com/questions/63788067/spring-webflux-and-non-blocking-logging-with-log4j2

 

로깅 관련 Stackoverflow질문의 채택된 답변에서 처음 접하게 되었습니다.

 

BlockHound란 리액티브 애플리케이션에서 Blocking 코드를 감지하고 식별하는 라이브러리입니다.

Project Reactor와 호환되며, Blocking 코드를 감지하고 StackTrace를 통해 그 원인을 식별합니다.

 

<참고>
Project Reactor
를 기반으로 하는 Webflux는 기본적으로 비동기, Non-Blocking의 특징을 가집니다.
하여 Blocking 지점이 있으면 애플리케이션에 문제를 유발할 가능성이 있습니다.

 

이번 포스팅에서는 BlockHound를 사용해봅니다.

 

공식 문서: https://github.com/reactor/BlockHound

 

 

2. 의존성 추가

먼저 아래와 같이 의존성을 추가해줍니다.

dependencies {
	...(중략)
    implementation("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
}

 

 

3. main 수정

아래 코드처럼 Application 실행 코드에 BlockHound 코드를 추가해줍니다.

import reactor.blockhound.BlockHound

@SpringBootApplication
class WebfluxTestApplication

fun main(args: Array<String>) {
    BlockHound.install()
    runApplication<WebfluxTestApplication>(*args)
}

 

그리고 실행해봅니다.

아래와 같이 에러가 표시되며 실행되지 않았습니다.

OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
Exception in thread "main" java.lang.IllegalStateException: The instrumentation have failed.
It looks like you're running on JDK 13+.
You need to add '-XX:+AllowRedefinitionToAddDeleteMethods' JVM flag.
See https://github.com/reactor/BlockHound/issues/33 for more info.
	at reactor.blockhound.BlockHound$Builder.testInstrumentation(BlockHound.java:527)
	at reactor.blockhound.BlockHound$Builder.install(BlockHound.java:490)

 

메시지를 보니 JDK 13이상에서는 VM Options 추가가 필요해보입니다.

아래 옵션을 추가해서 다시 실행 해봅니다.

  • -XX:+AllowRedefinitionToAddDeleteMethods

 

4. 실행 결과

Application을 띄운 뒤, 특정 API를 실행하다보니

아래와 같이 에러메시지가 표시되었습니다.

[2024-02-07 23:54:34] [ERROR] [CustomExceptionHandler:61] - Blocking call! java.io.RandomAccessFile#readBytes
reactor.blockhound.BlockingOperationError: Blocking call! java.io.RandomAccessFile#readBytes
	at java.io.RandomAccessFile.readBytes(RandomAccessFile.java) ~[?:?]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ me.exam.ktwebfx.filter.LoggingFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ org.springframework.web.filter.reactive.ServerHttpObservationFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP GET "/api/hello/redis?key=test&field=2" [ExceptionHandlingWebHandler]
Original Stack Trace:
		at java.io.RandomAccessFile.readBytes(RandomAccessFile.java) ~[?:?]
		at java.io.RandomAccessFile.read(RandomAccessFile.java:405) ~[?:?]
        ...(생략)

 

블로킹 코드가 감지된 것입니다!

위에서는 생략되었지만 실제로는 더 자세하게 에러에 대한 StackTrace가 표시될 것입니다.

 

checkpoint로 LoggingFilter가 표시되네요...

Logging 관련 서치를 하다가 발견한 BlockHound가 Logging 쪽에 블로킹 코드가 있다고 말해주네요...ㅎㅎ

Log4j2와 Webflux에서 블로킹 코드의 연관성에 대해서는 다음 기회에 포스팅 해보도록 하겠습니다.

 

 

5. 블로킹 예외 허용

Application 코드를 작성하다보면 어쩔수 없이(?) 블로킹 탐지에서 예외 처리 및 허용해야 할 수 있습니다.

그 때는 아래와 같이 허용을 해주면 됩니다.

fun main(args: Array<String>) {
    BlockHound.builder()
            .allowBlockingCallsInside(
                    "me.exam.webfluxtest.TestService", "blockingMethod"
            )
            .install()
    runApplication<WebfluxTestApplication>(*args)
}

allowBlockingCallsInside를 사용해서 ClassNameMethodName을 기입합니다.

그렇게하면 해당 메서드 실행 시에는 Blocking이 발생하지만 에러를 발생시키지는 않을 것입니다.